java数据结构之二叉树
1概述
今天我们介绍一种新的数据结构二叉树,数组和链表这两种线性数据结构都有其不足之处,数组一经创建大小固定,且插入,删除都很慢,链表查询一定要从链表头开始遍历,链表的查找很慢,不管我们要找什么数据,都要从链表头开始遍历,我们就希望有那么一种数据结构,兼顾查找,插入,删除三种操作,于是二叉树应运而生。
树是一种抽象数据类型,有节点和边组成,节点一般代表一种实体,边就是连接节点的线,java中用引用表示,树有很多种,如下图是二叉树,二叉树每个节点最多有两个子节点,当然还有多路数(2-3-4树,后续讲解)
下面是树种常用概念:
①、路径:顺着节点的边从一个节点走到另一个节点,所经过的节点的顺序排列就称为“路径”。
②、根:树顶端的节点称为根。一棵树只有一个根,如果要把一个节点和边的集合称为树,那么从根到其他任何一个节点都必须有且只有一条路径。A是根节点。
③、父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;B是D的父节点。
④、子节点:一个节点含有的子树的根节点称为该节点的子节点;D是B的子节点。
⑤、兄弟节点:具有相同父节点的节点互称为兄弟节点;比如上图的D和E就互称为兄弟节点。
⑥、叶节点:没有子节点的节点称为叶节点,也叫叶子节点,比如上图的H、E、F、G都是叶子节点。
⑦、子树:每个节点都可以作为子树的根,它和它所有的子节点、子节点的子节点等都包含在子树中。
⑧、节点的层次:从根开始定义,根为第一层,根的子节点为第二层,以此类推。
⑨、深度:对于任意节点n,n的深度为从根到n的唯一路径长,根的深度为0;
⑩、高度:对于任意节点n,n的高度为从n到一片树叶的最长路径长,所有树叶的高度为0;
2 二叉树
二叉树种每个节点最多有两个子节点,二叉树的子节点分为左右子节点,如上图节点D是节点B的左子节点,节点E是节点B的右子节点;如果节点在二叉树种是按节点data大小排序插入的,并且左边节点值永远小于父节点,而右子节点的值永远大于父节点,这样的二叉树我们称之二叉搜索树(binary search tree)
二叉搜索树有着广泛的应用,下面我们介绍二叉搜索树是怎样查找、插入,删除节点的;
2.1查找节点
查找某个节点,我们必须从根节点开始遍历。
①、查找值比当前节点值大,则搜索右子树;
②、查找值等于当前节点值,停止搜索(终止条件);
③、查找值小于当前节点值,则搜索左子树;
用变量current来保存当前查找的节点,参数value是要查找的值,刚开始查找将根节点赋值到current。接在在while循环中,将要查找的值和current保存的节点进行对比。如果value小于当前节点,则搜索当前节点的左子节点,如果大于,则搜索右子节点,如果等于,则直接返回节点信息。当整个树遍历完全,即current == null,那么说明没找到查找值,返回null。
2.2 插入节点
要插入节点,必须先找到插入的位置。与查找操作相似,由于二叉搜索树的特殊性,待插入的节点也需要从根节点开始进行比较,小于根节点则与根节点左子树比较,反之则与右子树比较,直到左子树为空或右子树为空,则插入到相应为空的位置,在比较的过程中要注意保存父节点的信息 及 待插入的位置是父节点的左子树还是右子树,才能插入到正确的位置。
2.3 遍历二叉树
遍历树是根据一种特定的顺序访问树的每一个节点。比较常用的有前序遍历,中序遍历和后序遍历。而二叉搜索树用的是中序遍历。遍历上图代码样例:
//中序遍历 public void infixOrder(Node current){ if(current != null){ infixOrder(current.leftChild); System.out.print(current.data+" "); infixOrder(current.rightChild); } } //前序遍历 public void preOrder(Node current){ if(current != null){ System.out.print(current.data+" "); preOrder(current.leftChild); preOrder(current.rightChild); } } //后序遍历 public void postOrder(Node current){ if(current != null){ postOrder(current.leftChild); postOrder(current.rightChild); System.out.print(current.data+" "); } }
2.4 删除节点
二叉搜索树删除一个节点比较麻烦,分多钟情况
第一种:该节点是叶子节点
第二种:该节点有一个子节点
第三种:该节点有两个子节点
第一种情况:删除叶子节点,只需要改变该节点的父节点引用该节点的值,即将其引用改为 null 即可。要删除的节点依然存在,但是它已经不是树的一部分了
第二种情况:删除有一个子节点的节点,我们只需要将其父节点原本指向该节点的引用,改为指向该节点的子节点即可。
第三种情况:删除有两个子节点的节点
如图下图删除节点25,下面两个子节点该如何安置?
当删除的节点存在两个子节点,那么删除之后,两个子节点的位置我们就没办法处理了。既然处理不了,我们就想到一种办法,用另一个节点来代替被删除的节点,那么用哪一个节点来代替呢?
我们知道二叉搜索树中的节点是按照关键字来进行排列的,某个节点的关键字次高节点是它的中序遍历后继节点。用后继节点来代替删除的节点,显然该二叉搜索树还是有序的。
那么如何找到删除节点的中序后继节点呢?其实我们稍微分析,这实际上就是要找比删除节点关键值大的节点集合中最小的一个节点,只有这样代替删除节点后才能满足二叉搜索树的特性。后继节点也就是:比删除节点大的最小节点。在该二叉树中就是节点30
后继节点找了也要分两种情况:
情况1:后继节点就是删除节点的右子节点,这种情况比较简单,直接把该删除节点的后继节点所表示的子树挪到删除节点位置
情况2:后继节点不是删除节点的右子节点,而是右子节点所表示子树的左子树中一个最小节点,
后继节点处理代码:
public Node getSuccessor(Node delNode){ Node successorParent = delNode; Node successor = delNode; Node current = delNode.rightChild; while(current != null){ successorParent = successor; successor = current; current = current.leftChild; } //将后继节点替换删除节点 if(successor != delNode.rightChild){ successorParent.leftChild = successor.rightChild; successor.rightChild = delNode.rightChild; } return successor; }
2.5 删除节点必要性讨论:
通过上面的删除分类讨论,我们发现删除其实是挺复杂的,那么其实我们可以不用真正的删除该节点,只需要在Node类中增加一个标识字段isDelete,当该字段为true时,表示该节点已经删除,反正没有删除。那么我们在做比如find()等操作的时候,要先判断isDelete字段是否为true。这样删除的节点并不会改变树的结构。
public class Node { int data; //节点数据 Node leftChild; //左子节点的引用 Node rightChild; //右子节点的引用 boolean isDelete;//表示节点是否被删除 }
二叉树操作完整代码:
public class BinaryTree implements Tree { //表示根节点 private Node root; //查找节点 public Node find(int key) { Node current = root; while(current != null){ if(current.data > key){//当前值比查找值大,搜索左子树 current = current.leftChild; }else if(current.data < key){//当前值比查找值小,搜索右子树 current = current.rightChild; }else{ return current; } } return null;//遍历完整个树没找到,返回null } //插入节点 public boolean insert(int data) { Node newNode = new Node(data); if(root == null){//当前树为空树,没有任何节点 root = newNode; return true; }else{ Node current = root; Node parentNode = null; while(current != null){ parentNode = current; if(current.data > data){//当前值比插入值大,搜索左子节点 current = current.leftChild; if(current == null){//左子节点为空,直接将新值插入到该节点 parentNode.leftChild = newNode; return true; } }else{ current = current.rightChild; if(current == null){//右子节点为空,直接将新值插入到该节点 parentNode.rightChild = newNode; return true; } } } } return false; } //中序遍历 public void infixOrder(Node current){ if(current != null){ infixOrder(current.leftChild); System.out.print(current.data+" "); infixOrder(current.rightChild); } } //前序遍历 public void preOrder(Node current){ if(current != null){ System.out.print(current.data+" "); infixOrder(current.leftChild); infixOrder(current.rightChild); } } //后序遍历 public void postOrder(Node current){ if(current != null){ infixOrder(current.leftChild); infixOrder(current.rightChild); System.out.print(current.data+" "); } } //找到最大值 public Node findMax(){ Node current = root; Node maxNode = current; while(current != null){ maxNode = current; current = current.rightChild; } return maxNode; } //找到最小值 public Node findMin(){ Node current = root; Node minNode = current; while(current != null){ minNode = current; current = current.leftChild; } return minNode; } @Override public boolean delete(int key) { Node current = root; Node parent = root; boolean isLeftChild = false; //查找删除值,找不到直接返回false while(current.data != key){ parent = current; if(current.data > key){ isLeftChild = true; current = current.leftChild; }else{ isLeftChild = false; current = current.rightChild; } if(current == null){ return false; } } //如果当前节点没有子节点 if(current.leftChild == null && current.rightChild == null){ if(current == root){ root = null; }else if(isLeftChild){ parent.leftChild = null; }else{ parent.rightChild = null; } return true; //当前节点有一个子节点,右子节点 }else if(current.leftChild == null && current.rightChild != null){ if(current == root){ root = current.rightChild; }else if(isLeftChild){ parent.leftChild = current.rightChild; }else{ parent.rightChild = current.rightChild; } return true; //当前节点有一个子节点,左子节点 }else if(current.leftChild != null && current.rightChild == null){ if(current == root){ root = current.leftChild; }else if(isLeftChild){ parent.leftChild = current.leftChild; }else{ parent.rightChild = current.leftChild; } return true; }else{ //当前节点存在两个子节点 Node successor = getSuccessor(current); if(current == root){ root= successor; }else if(isLeftChild){ parent.leftChild = successor; }else{ parent.rightChild = successor; } successor.leftChild = current.leftChild; } return false; } public Node getSuccessor(Node delNode){ Node successorParent = delNode; Node successor = delNode; Node current = delNode.rightChild; while(current != null){ successorParent = successor; successor = current; current = current.leftChild; } //后继节点不是删除节点的右子节点,将后继节点替换删除节点 if(successor != delNode.rightChild){ successorParent.leftChild = successor.rightChild; successor.rightChild = delNode.rightChild; } return successor; } public static void main(String[] args) { BinaryTree bt = new BinaryTree(); bt.insert(50); bt.insert(20); bt.insert(80); bt.insert(10); bt.insert(30); bt.insert(60); bt.insert(90); bt.insert(25); bt.insert(85); bt.insert(100); bt.delete(10);//删除没有子节点的节点 bt.delete(30);//删除有一个子节点的节点 bt.delete(80);//删除有两个子节点的节点 System.out.println(bt.findMax().data); System.out.println(bt.findMin().data); System.out.println(bt.find(100)); System.out.println(bt.find(200)); } }