二叉树介绍
二叉树可以用来做什么?
答:可以搜索、排序
可是,排序有快速排序、归并排序,查找有二分法、直接遍历等,那么为什么要用二叉树呢?
二叉树确实在实际运用中比较少,因为有更高级的树,但是二叉树作为一种最基本最典型的排序树,是研究其他树的基础。
我们知道,在有序数组中,可以快速找到特定的值;但是在有序数组内插入一个新数据项,或者删除数据项,需要费时的移动所有位置改变的数据项,所以在做插入和删除操作时,不该选用有序数组。
另一方面,链表中可以快速添加和删除某个数据项,但是在链表中查找数据不容易,必须从头开始访问链表的每一个数据项,直到找到该数据为止,过程很慢。
树这种数据结构,技能像链表那样快速插入和删除,又能像有序数组那样快速查找。这里主要实现一种特殊的树--二叉(搜索)树。
二叉(搜索)树有如下特点:
1. 一个子节点的关键字值小于这个节点,右子节点的关键字值大于或者等于这个节点。
2. 插入一个节点需要根据这个规则进行插入。
3. 删除节点时,二叉搜索树最复杂的操作,但是删除节点在很多树中的应用又非常重要,所以详细研究并总结如下特点。
1)删除节点要从查找要删的节点开始入手,首先找到节点,这个要删除的节点可能有三种情况需要考虑:
a. 该节点是叶节点,没有子节点;
b. 该节点有一个子节点;
c. 该节点有2个子节点;
第一种最简单,第二种也比较简单,第三种就相当复杂了。下面分析这三种删除情况:
一、要删除叶节点,只需要改变该节点的父节点对应子字段的值即可,由指向该节点改为 null 就可以了。垃圾回收器会自动回收叶节点,不需要自己手动删掉。
二、当节点有一个子节点时,这个节点只有两个连接:连向父节点和连向它唯一的子节点。需要从这个序列中剪断这个节点,把它的子节点直接连到它的父节点上即可,这个过程要求改变父节点适当的引用(左子节点还是右子节点),指向要删除节点的子节点即可。
三、这种情况最复杂,如果要删除有两个子节点的节点,就不能只用它的一个子节点代替它,比如要删除节点25,如果用35取代它,那35的左子节点是15呢还是30?
因此需要考虑另一种方法,寻找它的中序后继来代替该节点。下图显示的就是要删除节点用它的后继代替它的情况,删除后还是有序的。(这里还有更麻烦的情况,即它的后继自己也有右子节点,下面再讨论。)
那么如何找后继节点呢?首先得找到要删除的节点的右子节点,它的关键字值一定比待删除节点的大。然后转到待删除节点右子节点的左子节点那里(如果有的话),然后到这个左子节点的左子节点,以此类推,顺着左子节点的路径一直向下找,这个路径上的最后一个左子节点就是待删除节点的后继。如果待删除节点的右子节点没有左子节点,那么这个右子节点本身就是后继。寻找后继的示意图如下:
找到了后继节点,现在开始删除了,先看第一种情况,后继节点是delNode右子节点的做后代,这种情况要执行以下四个步骤:
-
把后继父节点的leftChild字段置为后继的右子节点;
-
把后继的rightChild字段置为要删除节点的右子节点;
-
把待删除节点从它父节点的leftChild或rightChild字段删除,把这个字段置为后继;
-
把待删除的左子节点移除,将后继的leftChild字段置为待删除节点的左子节点。
如下图所示:
如果后继节点就是待删除节点的右子节点,这种情况就简单了,因为只需要把后继为跟的子树移到删除的节点的位置即可。如下图所示:
看到这里,就会发现删除时相当棘手的操作。实际上,因为它非常复杂,一些程序员都尝试着躲开它,他们在Node类中加了一个Boolean字段来标识该节点是否已经被删除,在其他操作之前会先判断这个节点是不是已经删除了,这样删除节点不会改变树的结构。当然树中还保留着这种已经删除的节点,对存储造成浪费,但是如果没有那么多删除的话,这也不失为一个好方法。
另外二叉树有三种遍历方式:前序、中序和后序。这个比较简单,直接看下代码即可。
下面手写个二叉搜索树的代码
1 public class BinaryTree { 2 private BNode root; //根节点 3 public BinaryTree() { 4 root = null; 5 } 6 7 //二叉搜索树查找的时间复杂度为O(logN) 8 public BNode find(int key) { //find node with given key 9 BNode current = root; 10 while(current.key != key) { 11 if (key < current.key) { 12 current = current.leftChild; 13 }else { 14 current = current.rightChild; 15 } 16 if(current == null) { 17 return null; 18 } 19 } 20 return current; 21 } 22 23 // 插入节点 24 public void insert(int key, double value) { 25 BNode newNode = new BNode(); 26 newNode.key = key; 27 newNode.data = value; 28 if(root == null) { //if tree is null 29 root = newNode; 30 } 31 else { 32 BNode current = root; 33 BNode parent; 34 while(true) { 35 parent = current; 36 if(key < current.data) { //turn left 37 current = current.leftChild; 38 if(current == null) { 39 parent.leftChild = newNode; 40 newNode.parent = parent; 41 return; 42 } 43 }else { //turn right 44 current = current.rightChild; 45 if(current == null) { 46 parent.rightChild = newNode; 47 newNode.parent = parent; 48 return; 49 } 50 } 51 } 52 } 53 } 54 55 //遍历二叉树 56 public void traverse(int traverseType) { 57 switch (traverseType){ 58 case 1: System.out.println("Preorder traversal:"); 59 preOrder(root);//前向遍历 60 break; 61 case 2: System.out.println("Inorder traversal:"); 62 inOrder(root);//中向遍历 63 break; 64 case 3: System.out.println("Postorder traversal:"); 65 postOrder(root);//后向遍历 66 break; 67 default: System.out.println("Inorder traversal:"); 68 inOrder(root); 69 break; 70 } 71 System.out.println(""); 72 } 73 74 75 //前向遍历 76 private void preOrder(BNode localRoot) { 77 if(localRoot != null) { 78 System.out.print(localRoot.data + " "); 79 preOrder(localRoot.leftChild); 80 preOrder(localRoot.rightChild); 81 } 82 } 83 84 //中向遍历 85 private void inOrder(BNode localRoot) { 86 if(localRoot != null) { 87 inOrder(localRoot.leftChild); 88 System.out.print(localRoot.data + " "); 89 inOrder(localRoot.rightChild); 90 } 91 } 92 93 94 //后向遍历 95 private void postOrder(BNode localRoot) { 96 if(localRoot != null) { 97 postOrder(localRoot.leftChild); 98 postOrder(localRoot.rightChild); 99 System.out.print(localRoot.data + " "); 100 } 101 } 102 103 //查找最小值 104 105 /*根据二叉搜索树的存储规则,最小值应该是左边那个没有子节点的那个节点*/ 106 107 public BNode minNumber() { 108 BNode current = root; 109 BNode parent = root; 110 while(current != null) { 111 parent = current; 112 current = current.leftChild; 113 } 114 return parent; 115 } 116 117 //查找最大值 118 119 /*根据二叉搜索树的存储规则,最大值应该是右边那个没有子节点的那个节点*/ 120 121 public BNode maxNumber() { 122 BNode current = root; 123 BNode parent = root; 124 while(current != null) { 125 parent = current; 126 current = current.rightChild; 127 } 128 return parent; 129 } 130 131 132 //删除节点 133 134 /* 135 * 删除节点在二叉树中是最复杂的,主要有三种情况: 136 * 1. 该节点没有子节点(简单) 137 * 2. 该节点有一个子节点(还行) 138 * 3. 该节点有两个子节点(复杂) 139 * 删除节点的时间复杂度为O(logN) 140 */ 141 142 public boolean delete(int key) { 143 BNode current = root; 144 // BNode parent = root; 145 boolean isLeftChild = true; 146 if(current == null) { 147 return false; 148 } 149 //寻找要删除的节点 150 while(current.key != key) { 151 // parent = current; 152 if(key < current.key) { 153 isLeftChild = true; 154 current = current.leftChild; 155 } 156 else{ 157 isLeftChild = false; 158 current = current.rightChild; 159 } 160 if(current == null) { 161 return false; 162 } 163 } 164 //找到了要删除的节点,下面开始删除 165 //1. 要删除的节点没有子节点,直接将其父节点的左子节点或者右子节点赋为null即可 166 if(current.leftChild == null && current.rightChild == null) { 167 return deleteNoChild(current, isLeftChild); 168 } 169 //3. 要删除的节点有两个子节点 170 else if(current.leftChild != null && current.rightChild != null) { 171 return deleteTwoChild(current, isLeftChild); 172 } 173 174 //2. 要删除的节点有一个子节点,直接将其砍断,将其子节点与其父节点连起来即可,要考虑特殊情况就是删除根节点,因为根节点没有父节点 175 else{ 176 return deleteOneChild(current, isLeftChild); 177 } 178 179 } 180 181 182 public boolean deleteNoChild(BNode node, boolean isLeftChild) { 183 if(node == root) { 184 root = null; 185 return true; 186 } 187 if(isLeftChild) { 188 node.parent.leftChild = null; 189 } 190 else{ 191 node.parent.rightChild = null; 192 } 193 return true; 194 } 195 196 197 public boolean deleteOneChild(BNode node, boolean isLeftChild) { 198 if(node.leftChild == null) { 199 if(node == root) { 200 root = node.rightChild; 201 node.parent = null; 202 return true; 203 } 204 if(isLeftChild) { 205 node.parent.leftChild = node.rightChild; 206 } 207 else{ 208 node.parent.rightChild = node.rightChild; 209 } 210 node.rightChild.parent = node.parent; 211 } 212 else{ 213 if(node == root) { 214 root = node.leftChild; 215 node.parent = null; 216 return true; 217 } 218 if(isLeftChild) { 219 node.parent.leftChild = node.leftChild; 220 } 221 else{ 222 node.parent.rightChild = node.leftChild; 223 } 224 node.leftChild.parent = node.parent; 225 } 226 return true; 227 } 228 229 230 public boolean deleteTwoChild(BNode node, boolean isLeftChild) { 231 BNode successor = getSuccessor(node); 232 if(node == root) { 233 successor.leftChild = root.leftChild; 234 successor.rightChild = root.rightChild; 235 successor.parent = null; 236 root = successor; 237 } 238 else if(isLeftChild) { 239 node.parent.leftChild = successor; 240 } 241 else{ 242 node.parent.rightChild = successor; 243 } 244 successor.leftChild = node.leftChild;//connect successor to node's left child 245 return true; 246 } 247 248 //获得要删除节点的后继节点(中序遍历的下一个节点) 249 public BNode getSuccessor(BNode delNode) { 250 BNode successor = delNode; 251 BNode current = delNode.rightChild; 252 while(current != null) { 253 successor = current; 254 current = current.leftChild; 255 } 256 if(successor != delNode.rightChild) { 257 successor.parent.leftChild = successor.rightChild; 258 if(successor.rightChild != null) { 259 successor.rightChild.parent = successor.parent;//删除后续节点在原来的位置 260 } 261 successor.rightChild = delNode.rightChild;//将后续节点放到正确位置,与右边连上 262 } 263 return successor; 264 } 265 } 266 267 class BNode { 268 public int key; 269 public double data; 270 public BNode parent; 271 public BNode leftChild; 272 public BNode rightChild; 273 274 public void displayNode() { 275 System.out.println("{" + key + ":" + data + "}"); 276 } 277 }