树
树是一个神奇的数据结构……树的每个节点,都存在着多个指针,指向他的下一层,最后一层层下去通向叶子节点。
树是一个由有限个节点组成的一个具有层次关系的集合。他看起来像一棵倒着的树。
树里面的每个节点,会有一个父节点(上一层)和多个孩子节点(下一层)。
当然还会有数据域。。。数据域可以是多个。。
二叉树
其实重点就是二叉树,二叉树指的是,每个节点最多有2个分支,称为左孩子和右孩子。
所以,我们来看一下树的节点类。。。
我写的节点类里,包括了两个数据线(long和string,可以记录‘张三’,24岁之类的信息)
然后为了之后的一些乱七八糟的方法做准备。。。还有一个是标记有没有被访问过
public class Node { //long数据项 public long data; //String数据项 public String sData; //左孩子节点 public Node leftChild; //右子节点 public Node rightChild; //是否被访问 public boolean wasVisited; //构造方法 public Node(long data, String sData) { this.data = data; this.sData = sData; } }
之后再看一些常用的方法吧。。。
由于树的方法比较多,我分开讲。。。最后合起来的所有代码再合起来一遍放在最后(包括test)
首先是树的建立。。主要是,首先得有一个根节点,然后是插入节点的方法。插入按照排序插入,比父节点小的放在父节点的左边,比父节点大的放在右边,这样做的好处是后面讲的中序遍历可以直接排序。
public class Tree { //根节点 public Node root; //插入一个节点 public void insert(long data , String sData) { //封装节点 Node newNode = new Node(data , sData); //两个引用 //当前节点 Node current = root; //父节点 Node parent; //第一次插入直接赋值 if(current == null) { root = newNode; return; } //循环左走右走 while (true) { //父节点指向当前节点 parent = current; //比较大小左右走 if (current.data > data) { current = current.leftChild; if(current == null) { parent.leftChild = newNode; return; } } else { current = current.rightChild; if(current == null) { parent.rightChild = newNode; return; } } } } }
下面的方法我就不写calss了,大家主要看一下思路,整体代码在最后面。
这里是根据数据查找节点的算法
//查找节点 public Node find(long data) { //引用当前节点 Node current = root; while(current.data != data) { //进行比较和当前节点大小 if (current.data > data) { current = current.leftChild; } else { current = current.rightChild; } //查不到 if(current == null) { return null; } } return current; }
这里是一个重点,树的遍历,大家知道树的遍历有4种,前序,中序,后序,层序。
前序:先跟,再左孩子,再右孩子。如果左(右)孩子是一棵子树,就吧左(右)子树当成现在的一颗树,进行跟,左孩子,右孩子的顺序遍历,以此类推。如果不懂的话,我会再写一篇关于三种遍历的文章,大家可以到我文章里看看。
中序:先左孩子,再跟,再右孩子,和上面一样,如果有子树的情况,就吧子树当成当前状态继续中序遍历。
后序:左孩子,右孩子,跟。
层序:从上往下一层层遍历。。。。
这里提供了递归,和非递归的两种实现方法
/*前序遍历 * 1.访问根节点 * 2.递归前序遍历左子树 * 3.递归前序遍历右子树 */ public void frontOrder(Node localNode) { if(localNode != null) { System.out.print(localNode.data + "," + localNode.sData + " ; "); frontOrder(localNode.leftChild); frontOrder(localNode.rightChild); } } /* * 中序遍历 * 1.递归中序遍历左子树 * 2.访问根节点 * 3.递归中序遍历右子树 */ public void inOrder(Node localNode) { if (localNode != null) { inOrder(localNode.leftChild); System.out.print(localNode.data + "," + localNode.sData + " ; "); inOrder(localNode.rightChild); } } /* * 后序遍历 * 1.递归后序遍历左子树 * 2.递归后序遍历右子树 * 3.访问根节点 */ public void afterOrder(Node localNode) { if (localNode != null) { afterOrder(localNode.leftChild); afterOrder(localNode.rightChild); System.out.print(localNode.data + "," + localNode.sData + " ; "); } } //层序遍历 public void LevelOrder(Node root) { Queue<Node> queue = new LinkedList<Node>(); Node now = root; queue.add(root); while (!queue.isEmpty()) { now = queue.poll(); if (now != null) { System.out.print(now.data + "," + now.sData + " ; "); } if (now.leftChild != null) { queue.add(now.leftChild); } if (now.rightChild != null) { queue.add(now.rightChild); } } } //利用栈 //非递归遍历树_前序(根左右) public void FeiFrontOrder(Node root) { if (root == null) { return; } //定义栈,通过栈存放遍历的节点 Stack<Node> stack = new Stack<Node>(); stack.push(root); Node now = root.leftChild; while ( !stack.isEmpty()) { now = stack.pop(); if (now != null) { System.out.print(now.data + "," + now.sData + " ; "); stack.push(now.rightChild); stack.push(now.leftChild); } } } //非递归遍历树_中序(左根右) public void FeiInOrder(Node root) { if (root == null) { return; } //定义栈,通过栈存放遍历的节点 Stack<Node> stack = new Stack<Node>(); stack.push(root); Node now = root.leftChild; while (now != null || !stack.isEmpty()) { if (now != null) { stack.push(now); now = now.leftChild; } else { now = stack.pop(); System.out.print(now.data + "," + now.sData + " ; "); now = now.rightChild; } } } //非递归遍历树_后序(左右根) public void FeiAfterOrder(Node root) { if (root == null) { return; } Stack<Node> stack = new Stack<Node>(); stack.push(root); Node visited = null; Node now = root.leftChild; while (now != null || !stack.isEmpty()) { while (null != now) { stack.push(now); now = now.leftChild; } now = stack.pop(); if (now.rightChild == null || now.rightChild.wasVisited) { System.out.print(now.data + "," + now.sData + " ; "); now.wasVisited = true; now = null; } else { stack.push(now); now = now.rightChild; } } }
理解了遍历的话。。。再看删除操作。。。
删除操作是比较复杂的,之后我会再开一个文章详细说删除操作的内容,这里先告诉大家,删除树的节点的时候,我们得找到一个中序后继节点来替换要删除的节点,以此来保持二叉树插入时候的平衡。代码如下
//寻找中序后继节点 public Node getHouJiNode(Node delNode) { //定义中序后继节点 Node houji = delNode; //定义中序后继的父节点 Node houjiparent = delNode; //当前节点 Node current = delNode.rightChild; while(current != null) { houjiparent = houji; houji = current; current = current.leftChild;//往左边找 } //把删除节点替换成终须后继节点 //1.如果删除节点的右节点不是后继节点(正常右左左……找到的) if(houji != delNode.rightChild) { //中序后继的父节点的左子节点指向中序后继节点的右子节点(中序后继节点没有左子节点) houjiparent.leftChild = houji.rightChild; //后继的右边接上删除的右边,左边接上删除的左边 houji.rightChild = delNode.rightChild; houji.leftChild = delNode.leftChild; } //2.删除节点的右节点就是中序后继节点,右边不用动,吧左边接上就可以 else { houji.leftChild = delNode.leftChild; } return houji; } //删除节点delete public boolean delete(long data) { //引用当前节点 Node current = root; //引用当前节点的父节点 Node parent = root; //是左子节点还是右节点 boolean isLeftChild = true; //循环找到要删的节点 while (current.data != data) { parent = current; if (current.data > data) { current = current.leftChild; isLeftChild = true; } else { current = current.rightChild; isLeftChild = false; } //没有 if(current == null) { return false; } } //循环结束,current指向要删除节点,parent指向父节点 //进行删除current节点 //1.如果是叶子节点(没有子节点) if (current.leftChild == null &¤t.rightChild ==null) { if (current == root) { root = null; } //如果是左边的节点 else if (isLeftChild) { parent.leftChild = null; } //如果是右边的节点 else { parent.rightChild = null; } } //2.删除带有一个子节点的节点 else if (current.rightChild == null) { //子节点是左子节点 if (current == root) { root = current.leftChild; } else if (isLeftChild) {//删除的节点是左节点 //把父节点的左子节点指向删除节点的左子节点(接上去) parent.leftChild = current.leftChild; } else {//删除的节点是右节点 parent.rightChild = current.leftChild; } } else if (current.leftChild == null) { //子节点是右子节点 if (current == root) { root = current.rightChild; } else if (isLeftChild) {//删除的节点是左节点 parent.leftChild = current.rightChild; } else {//删除的节点是右节点 parent.rightChild = current.rightChild; } } //3.删除有2个子节点的节点,需要寻找中续后继节点来替换删除节点 else { Node houji = getHouJiNode(current); if (current == root) { root = houji; } else if (isLeftChild) { parent.leftChild = houji; } else { parent.rightChild = houji; } } return true; }
最后是一些乱七八糟的操作。。。
先是求k层有多少个,求叶子节点,求树的高度,没什么难度就不说了,主要靠递归遍历然后统计。。。
//求第k层节点的个数 public int GetKLevelNode(Node root, int k) { if(1 > k) { return 0; } if (1 == k) { return 1; } else { return GetKLevelNode(root.leftChild, k - 1) + GetKLevelNode(root.rightChild, k - 1); } } //求叶子节点个数 public int GetLeafCount(Node root) { if (root == null) { return 0; } if (root.leftChild == null && root.rightChild == null) { return 1; }else { return GetLeafCount(root.leftChild) + GetLeafCount(root.rightChild); } } //求树的高度 public int BinTreeHeight(Node root) { //定义左右子树高度 int leftHight = 0; int rightHight = 0; if (null == root.leftChild &&null == root.rightChild) { return 1; } //递归求左右子树高度 leftHight = BinTreeHeight(root.leftChild); rightHight = BinTreeHeight(root.rightChild); //取最大值 if (leftHight > rightHight) { return leftHight + 1; } else { return rightHight + 1; } }
下面是检测是否为完全二叉树,这里运用了层序遍历,设置一个boolean值after,完全二叉树层序遍历的话,从某个节点开始就会全是叶子节点,所以运用到这个思路,吧after改为true,这样之后遍历到的节点如果有孩子,就返回false。正常遍历下,如果有右孩子而没有左孩子的也不是完全二叉树,也返回false。具体看代码
//检测一颗树是不是完全二叉树 public boolean IsCompleteBinTree(Node root) { boolean after = false;//表示层遍历从哪个节点之后需要全是叶子节点 Queue<Node> queue = new LinkedList<Node>(); Node now = root; queue.add(root); while (!queue.isEmpty()) { now = queue.poll(); //如果必须要是叶子节点的时候,则如果有孩子节点就返回false if (after) { if (now.leftChild != null || now.rightChild != null) { return false; } } //如果某个节点有右孩子没有左孩子,false if (now.leftChild == null && now.rightChild != null) { return false; } if (now.leftChild != null) { queue.add(now.leftChild); } if (now.rightChild != null) { queue.add(now.rightChild); }else { //如果从某个节点开始没有右孩子了,需要之后都是叶子节点 after = true; } } return true; }
这些是树的基本操作了,其他的更加复杂的操作以后再更。。。下面吧代码都贴上
记得每个类建一个.java文件,方便查看,我这里也是按照每个类有一个.java文件去写的,如果直接拷贝需要吧public去掉
node类:在最上面,已经发了
test:(这里也可以自己去测试,屏蔽的是做了每个内容的测试,所以大家自己看看)
public class TestTree { public static void main(String[] args) { Tree aTree = new Tree(); Tree bTree = new Tree(); aTree.insert(25, "e"); aTree.insert(20, "d"); // aTree.insert(28, "c"); // aTree.insert(15, "c"); // aTree.insert(30, "f"); // aTree.insert(6, "c"); // aTree.insert(45, "i"); // aTree.insert(38, "c"); // aTree.insert(32, "c"); // aTree.insert(22, "c"); // aTree.insert(10, "b"); // aTree.insert(77, "c"); // aTree.insert(40, "h"); // aTree.insert(50, "j"); // aTree.insert(18, "c"); // aTree.insert(5, "a"); System.out.println(aTree.GetLeafCount(aTree.root)); System.out.println(aTree.IsCompleteBinTree(aTree.root)); // bTree.root = bTree.CopyBinTree(aTree.root); // aTree.frontOrder(aTree.root); // System.out.println(); // bTree.frontOrder(bTree.root); // aTree.LevelOrder(aTree.root); // aTree.frontOrder(aTree.root); // System.out.println(); // aTree.FeiFrontOrder(aTree.root); // System.out.println(); // aTree.inOrder(aTree.root); // System.out.println(); // aTree.FeiInOrder(aTree.root); // System.out.println(); // aTree.afterOrder(aTree.root); // System.out.println(); // aTree.FeiAfterOrder(aTree.root); // aTree.insert(35, "g"); // aTree.insert(10, "g"); // aTree.insert(20, "g"); // aTree.insert(15, "g"); // aTree.insert(3, "g"); // aTree.insert(4, "g"); // aTree.insert(90, "g"); // Node node = aTree.find(20); // System.out.println(node.data + " " + node.sData); // System.out.println(aTree.root.data);//10 // System.out.println(aTree.root.leftChild.data);//3 // System.out.println(aTree.root.rightChild.data);//20 // System.out.println(aTree.root.rightChild.leftChild.data);//15 // aTree.delete(10); // aTree.delete(20); // aTree.frontOrder(aTree.root); // System.out.println(); // aTree.afterOrder(aTree.root); // System.out.println(); // aTree.inOrder(aTree.root); // System.out.println(); // TreeSet<Integer> tree = new TreeSet<Integer>(); // // tree.add(Integer.valueOf(5)); // tree.add(Integer.valueOf(4)); // tree.add(Integer.valueOf(7)); // tree.add(Integer.valueOf(2)); // tree.add(Integer.valueOf(8)); // tree.add(Integer.valueOf(6)); // tree.add(Integer.valueOf(3)); // tree.add(Integer.valueOf(1)); // // // int a = tree.pollLast(); // int b = tree.pollLast(); // System.out.print(a +" " + b); } }
tree类(这里400多行建议还是在上面自取所需)
import java.util.Stack; import java.util.LinkedList; import java.util.Queue; //二叉树 public class Tree { //根节点 public Node root; //插入一个节点 public void insert(long data , String sData) { //封装节点 Node newNode = new Node(data , sData); //两个引用 //当前节点 Node current = root; //父节点 Node parent; //第一次插入直接赋值 if(current == null) { root = newNode; return; } //循环左走右走 while (true) { //父节点指向当前节点 parent = current; //比较大小左右走 if (current.data > data) { current = current.leftChild; if(current == null) { parent.leftChild = newNode; return; } } else { current = current.rightChild; if(current == null) { parent.rightChild = newNode; return; } } } } //二叉树拷贝,返回一个根 public Node CopyBinTree(Node root) { Node newroot = new Node(root.data, root.sData); // Node now = newroot; if (root.leftChild != null) { // Node lnode = new Node(root.leftChild.data, root.leftChild.sData); // newroot.leftChild = lnode; Node lNode = CopyBinTree(root.leftChild); newroot.leftChild = lNode; } if (root.rightChild != null) { // Node rnode = new Node(root.rightChild.data, root.rightChild.sData); // newroot.rightChild = rnode; Node rNode = CopyBinTree(root.rightChild); newroot.rightChild = rNode; } return newroot; } //查找节点 public Node find(long data) { //引用当前节点 Node current = root; while(current.data != data) { //进行比较和当前节点大小 if (current.data > data) { current = current.leftChild; } else { current = current.rightChild; } //查不到 if(current == null) { return null; } } return current; } /*前序遍历 * 1.访问根节点 * 2.递归前序遍历左子树 * 3.递归前序遍历右子树 */ public void frontOrder(Node localNode) { if(localNode != null) { System.out.print(localNode.data + "," + localNode.sData + " ; "); frontOrder(localNode.leftChild); frontOrder(localNode.rightChild); } } /* * 中序遍历 * 1.递归中序遍历左子树 * 2.访问根节点 * 3.递归中序遍历右子树 */ public void inOrder(Node localNode) { if (localNode != null) { inOrder(localNode.leftChild); System.out.print(localNode.data + "," + localNode.sData + " ; "); inOrder(localNode.rightChild); } } /* * 后序遍历 * 1.递归后序遍历左子树 * 2.递归后序遍历右子树 * 3.访问根节点 */ public void afterOrder(Node localNode) { if (localNode != null) { afterOrder(localNode.leftChild); afterOrder(localNode.rightChild); System.out.print(localNode.data + "," + localNode.sData + " ; "); } } //层序遍历 public void LevelOrder(Node root) { Queue<Node> queue = new LinkedList<Node>(); Node now = root; queue.add(root); while (!queue.isEmpty()) { now = queue.poll(); if (now != null) { System.out.print(now.data + "," + now.sData + " ; "); } if (now.leftChild != null) { queue.add(now.leftChild); } if (now.rightChild != null) { queue.add(now.rightChild); } } } //利用栈 //非递归遍历树_前序(根左右) public void FeiFrontOrder(Node root) { if (root == null) { return; } //定义栈,通过栈存放遍历的节点 Stack<Node> stack = new Stack<Node>(); stack.push(root); Node now = root.leftChild; while ( !stack.isEmpty()) { now = stack.pop(); if (now != null) { System.out.print(now.data + "," + now.sData + " ; "); stack.push(now.rightChild); stack.push(now.leftChild); } } } //非递归遍历树_中序(左根右) public void FeiInOrder(Node root) { if (root == null) { return; } //定义栈,通过栈存放遍历的节点 Stack<Node> stack = new Stack<Node>(); stack.push(root); Node now = root.leftChild; while (now != null || !stack.isEmpty()) { if (now != null) { stack.push(now); now = now.leftChild; } else { now = stack.pop(); System.out.print(now.data + "," + now.sData + " ; "); now = now.rightChild; } } } //非递归遍历树_后序(左右根) public void FeiAfterOrder(Node root) { if (root == null) { return; } Stack<Node> stack = new Stack<Node>(); stack.push(root); Node visited = null; Node now = root.leftChild; while (now != null || !stack.isEmpty()) { while (null != now) { stack.push(now); now = now.leftChild; } now = stack.pop(); if (now.rightChild == null || now.rightChild.wasVisited) { System.out.print(now.data + "," + now.sData + " ; "); now.wasVisited = true; now = null; } else { stack.push(now); now = now.rightChild; } } } //寻找中序后继节点 public Node getHouJiNode(Node delNode) { //定义中序后继节点 Node houji = delNode; //定义中序后继的父节点 Node houjiparent = delNode; //当前节点 Node current = delNode.rightChild; while(current != null) { houjiparent = houji; houji = current; current = current.leftChild;//往左边找 } //把删除节点替换成终须后继节点 //1.如果删除节点的右节点不是后继节点(正常右左左……找到的) if(houji != delNode.rightChild) { //中序后继的父节点的左子节点指向中序后继节点的右子节点(中序后继节点没有左子节点) houjiparent.leftChild = houji.rightChild; //后继的右边接上删除的右边,左边接上删除的左边 houji.rightChild = delNode.rightChild; houji.leftChild = delNode.leftChild; } //2.删除节点的右节点就是中序后继节点,右边不用动,吧左边接上就可以 else { houji.leftChild = delNode.leftChild; } return houji; } //删除节点delete public boolean delete(long data) { //引用当前节点 Node current = root; //引用当前节点的父节点 Node parent = root; //是左子节点还是右节点 boolean isLeftChild = true; //循环找到要删的节点 while (current.data != data) { parent = current; if (current.data > data) { current = current.leftChild; isLeftChild = true; } else { current = current.rightChild; isLeftChild = false; } //没有 if(current == null) { return false; } } //循环结束,current指向要删除节点,parent指向父节点 //进行删除current节点 //1.如果是叶子节点(没有子节点) if (current.leftChild == null &¤t.rightChild ==null) { if (current == root) { root = null; } //如果是左边的节点 else if (isLeftChild) { parent.leftChild = null; } //如果是右边的节点 else { parent.rightChild = null; } } //2.删除带有一个子节点的节点 else if (current.rightChild == null) { //子节点是左子节点 if (current == root) { root = current.leftChild; } else if (isLeftChild) {//删除的节点是左节点 //把父节点的左子节点指向删除节点的左子节点(接上去) parent.leftChild = current.leftChild; } else {//删除的节点是右节点 parent.rightChild = current.leftChild; } } else if (current.leftChild == null) { //子节点是右子节点 if (current == root) { root = current.rightChild; } else if (isLeftChild) {//删除的节点是左节点 parent.leftChild = current.rightChild; } else {//删除的节点是右节点 parent.rightChild = current.rightChild; } } //3.删除有2个子节点的节点,需要寻找中续后继节点来替换删除节点 else { Node houji = getHouJiNode(current); if (current == root) { root = houji; } else if (isLeftChild) { parent.leftChild = houji; } else { parent.rightChild = houji; } } return true; } //求第k层节点的个数 public int GetKLevelNode(Node root, int k) { if(1 > k) { return 0; } if (1 == k) { return 1; } else { return GetKLevelNode(root.leftChild, k - 1) + GetKLevelNode(root.rightChild, k - 1); } } //求叶子节点个数 public int GetLeafCount(Node root) { if (root == null) { return 0; } if (root.leftChild == null && root.rightChild == null) { return 1; }else { return GetLeafCount(root.leftChild) + GetLeafCount(root.rightChild); } } //求树的高度 public int BinTreeHeight(Node root) { //定义左右子树高度 int leftHight = 0; int rightHight = 0; if (null == root.leftChild &&null == root.rightChild) { return 1; } //递归求左右子树高度 leftHight = BinTreeHeight(root.leftChild); rightHight = BinTreeHeight(root.rightChild); //取最大值 if (leftHight > rightHight) { return leftHight + 1; } else { return rightHight + 1; } } //检测一个节点是否在二叉树中 public boolean IsInTree(Node root, Node pNode) { boolean is = false; Node aNode = find(pNode.data); if (aNode.sData.equals(pNode.sData)) { is = true; } return is; } //检测一颗树是不是完全二叉树 public boolean IsCompleteBinTree(Node root) { boolean after = false;//表示层遍历从哪个节点之后需要全是叶子节点 Queue<Node> queue = new LinkedList<Node>(); Node now = root; queue.add(root); while (!queue.isEmpty()) { now = queue.poll(); //如果必须要是叶子节点,则如果有节点就返回false if (after) { if (now.leftChild != null || now.rightChild != null) { return false; } } //如果某个节点有右孩子没有左孩子,false if (now.leftChild == null && now.rightChild != null) { return false; } if (now.leftChild != null) { queue.add(now.leftChild); } if (now.rightChild != null) { queue.add(now.rightChild); }else { //如果从某个节点开始没有右孩子了,需要之后都是叶子节点 after = true; } } return true; } }