1.数组、链表和树存储方式分析
1.1数组的存储方式
(1)优点:通过下表当时访问元素,速度快。对于有序数组,还可以使用二分查找提高检索速度
(2)缺点:如果检索具体某个值,或者插入值(按照一定顺序)会整体移动,效率较低
1.2 链式存储方式的分析
(1)优点:在一定程度上对数组存储方式有优化(例如:插入一个数值节点,只需要将插入节点,连接到链表中即可,删除效率也很好)
(2)缺点:在进行检索时,效率仍然比较低,比如(检索某个值,需要从头节点开始遍历)
1.3 树存储方式的分析
能够提高数据存储,读取的效率,比如利用二叉排序树(binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入,删除,修改的速度。
2.二叉树的概念和常用术语
2.1 二叉树的概念
(1)每个节点最多只能有两个子节点的一种形式
(2)二叉树的子节点分为左节点和右节点
(3)如果该二叉树的所有叶子节点都在最后一层,并且节点总数=2^n-1,n为层数时,称为满二叉树
(4)如果该二叉树的所有叶子节点都在最后一层或倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,称为完全二叉树
2.2 二叉树的遍历
(1)前序遍历:先输出父节点,再遍历左子树和右子树;
(2)中序遍历:先遍历左子树,再输出父节点,再遍历右子树;
(3)后序遍历:先遍历左子树,再遍历右子树,最后输出父节点;
(4)分析二叉树的前序,中序,后序的遍历步骤
1)创建一颗二叉树
2) 前序遍历
2.1)先输出当前节点;(初始的时候事root节点)
2.2)如果左子节点不为空,则递归继续前序遍历;
2.3)如果右子节点不为空,则递归继续前序遍历
3)中序遍历
3.1)如果当前节点的左子节点不为空,则递归中序遍历;
3.2)输出当前节点;
3.3)如果当前节点的右子节点不为空,则递归中序遍历;
4)后序遍历
4.1)如果当前节点的左子节点不为空,则递归后序遍历;
4.2)如果当前节点的右子节点不为空,则递归后序遍历;
4.3)输出当前节点;
2.3 二叉树的前序,中序,后序的查找步骤
(1)前序查找
1)先判断当前节点的no是否等于要查找的;
2)如果相等,返回当前节点;
3)如果不相等,则判断当前节点的左子节点是否为空,如果不为空,则递归前序查找;
4)如果左递归前序查找,找到节点则返回,否则继续判断,当前节点的右子节点是否为空,如果不为空,则继续向右递归前序查找;
(2)中序查找
1)判断当前节点的左子节点是否为空,如果不为空,则递归中序查找;
2)如果找到就返回,如果没有找到就和当前节点比较,如果是则返回当前节点,否则继续进行右递归的中序查找;
3)如果右递归中序查找,找到就返回,否则就返回null;
(3)后序查找
1)判断当前节点的左子节点是否为空,如果不为空就继续向左后序查找;
2)如果找到就返回,没有找到就判断当前节点的右子节点是否为空,如果不为空就右递归进行后序查找,如果找到就返回;
3)就和当前节点进行比较,如果是则返回,否则返回null;
2.4 二叉树删除节点
(1)要求
1)如果删除的节点是叶子节点,则删除该节点;
2)如果删除的节点是非叶子节点,则删除该子树;
3)测试,删除掉5号叶子节点和3号子树;
(2)删除思路分析
1)因为二叉树是单向的,因此判断的是当前节点的子节点是否需要删除,而不是判断当前节点;
2)如果当前节点的左子节点不为空,并且左子节点就是要删除的节点,将this.left置空,并且返回,结束递归;
3)如果当前节点的右子节点不为空,并且右子节点就是要删除的节点,将this.right置空,并且返回,结束递归;
4)如果第2,3步都没有删除节点,那么向左子树递归删除;
5)如果第4步也没有删除节点,则应当向右子树递归删除;
6)考虑如果树是空树root,如果只有一个root节点,则等价于将二叉树置空。(第一步)
2.5 源代码
1 package cn.atguigu.Tree; 2 3 public class BinaryTreeDemo { 4 5 public static void main(String[] args) { 6 // TODO Auto-generated method stub 7 //先创建一个二叉树 8 BinaryTree binaryTree=new BinaryTree(); 9 //创建需要的节点 10 HeroNode root=new HeroNode(1, "宋江"); 11 HeroNode hero2=new HeroNode(2, "吴用"); 12 HeroNode hero3=new HeroNode(3, "卢俊义"); 13 HeroNode hero4=new HeroNode(4, "林冲"); 14 HeroNode hero5=new HeroNode(5, "关胜"); 15 16 //先手动创建二叉树 17 root.setLeft(hero2); 18 root.setRight(hero3); 19 hero3.setRight(hero4); 20 hero3.setLeft(hero5); 21 binaryTree.setRoot(root); 22 23 //遍历测试 24 /* 25 * System.out.println("前序遍历"); binaryTree.preOrder(); 26 * System.out.println("中序遍历"); binaryTree.infixOrder(); 27 * System.out.println("后序遍历"); binaryTree.postOrder(); 28 */ 29 30 //查找测试 31 /* 32 * System.out.println("前序查找~~~~");//查找了4次 HeroNode 33 * res=binaryTree.preOrderSearch(5); if(res!=null) { 34 * System.out.printf("找到了,信息为no=%d,name=%s",res.getNo(),res.getName()); }else { 35 * System.out.printf("没有找到no=%d 的英雄",5); } System.out.println(); 36 * System.out.println("中序查找~~~~");//查找了3次 res=binaryTree.infixOrderSearch(5); 37 * if(res!=null) { 38 * System.out.printf("找到了,信息为no=%d,name=%s",res.getNo(),res.getName()); }else { 39 * System.out.printf("没有找到no=%d 的英雄",5); } System.out.println(); 40 * System.out.println("后序查找~~~~");//查找了2次 res=binaryTree.postOrderSearch(5); 41 * if(res!=null) { 42 * System.out.printf("找到了,信息为no=%d,name=%s",res.getNo(),res.getName()); }else { 43 * System.out.printf("没有找到no=%d 的英雄",5); } 44 */ 45 46 //删除测试 47 System.out.println("删除前前序遍历~~~~"); 48 binaryTree.preOrder();//1,2,3,5,4 49 // binaryTree.delNode(5); 50 binaryTree.delNode(3); 51 System.out.println("删除后前序遍历~~~~"); 52 binaryTree.preOrder();//1,2,3,4 53 } 54 55 } 56 //定义BinaryTree 二叉树 57 class BinaryTree{ 58 private HeroNode root; 59 60 public void setRoot(HeroNode root) { 61 this.root = root; 62 } 63 //删除节点 64 public void delNode(int no) { 65 if(root!=null) { 66 //单独对root节点进行判断 67 if(root.getNo()==no) { 68 root=null; 69 }else { 70 root.delNode(no); 71 } 72 }else { 73 System.out.println("空树,不能删除"); 74 } 75 } 76 77 //前序遍历 78 public void preOrder() { 79 if(this.root!=null) { 80 this.root.preOrder(); 81 }else { 82 System.out.println("当前二叉树为空,无法遍历"); 83 } 84 } 85 86 //中序遍历 87 public void infixOrder() { 88 if(this.root!=null) { 89 this.root.infixOrder(); 90 }else { 91 System.out.println("当前二叉树为空,无法遍历"); 92 } 93 } 94 //后序遍历 95 public void postOrder() { 96 if(this.root!=null) { 97 this.root.postOrder(); 98 }else { 99 System.out.println("当前二叉树为空,无法遍历"); 100 } 101 } 102 103 //前序查找 104 public HeroNode preOrderSearch(int no) { 105 if(root!=null) { 106 return root.preOrderSearch(no); 107 }else { 108 return null; 109 } 110 } 111 112 //中序查找 113 public HeroNode infixOrderSearch(int no) { 114 if(root!=null) { 115 return root.infixOrderSearch(no); 116 }else { 117 return null; 118 } 119 } 120 //后序查找 121 public HeroNode postOrderSearch(int no) { 122 if(root!=null) { 123 return root.postOrderSearch(no); 124 }else { 125 return null; 126 } 127 } 128 } 129 130 131 //先创建HeroNode节点 132 class HeroNode{ 133 private int no; 134 private String name; 135 private HeroNode left;//默认null 136 private HeroNode right;//默认null 137 public HeroNode(int no, String name) { 138 this.no = no; 139 this.name = name; 140 } 141 public int getNo() { 142 return no; 143 } 144 public void setNo(int no) { 145 this.no = no; 146 } 147 public String getName() { 148 return name; 149 } 150 public void setName(String name) { 151 this.name = name; 152 } 153 public HeroNode getLeft() { 154 return left; 155 } 156 public void setLeft(HeroNode left) { 157 this.left = left; 158 } 159 public HeroNode getRight() { 160 return right; 161 } 162 public void setRight(HeroNode right) { 163 this.right = right; 164 } 165 @Override 166 public String toString() { 167 return "HeroNode [no=" + no + ", name=" + name + "]"; 168 } 169 //递归删除节点 170 public void delNode(int no) { 171 if(this.left!=null&&this.left.no==no) {//如果当前节点的左子节点不为空,并且左子节点就是要删除的节点,将this.left置空,并且返回,结束递归; 172 this.left=null; 173 return; 174 } 175 if(this.right!=null&&this.right.no==no) {//如果当前节点的右子节点不为空,并且右子节点就是要删除的节点,将this.right置空,并且返回,结束递归; 176 this.right=null; 177 return; 178 } 179 if(this.left!=null) {//向左子树递归删除 180 this.left.delNode(no); 181 } 182 if(this.right!=null) {//向右子树递归删除 183 this.right.delNode(no); 184 } 185 } 186 187 188 //编写前序遍历的方法 189 public void preOrder() { 190 System.out.println(this);//先输出父节点 191 //递归向左子树遍历 192 if(this.left!=null) { 193 this.left.preOrder(); 194 } 195 //递归向右子树遍历 196 if(this.right!=null) { 197 this.right.preOrder(); 198 } 199 } 200 //中序遍历 201 public void infixOrder() { 202 //向左子树遍历 203 if(this.left!=null) { 204 this.left.infixOrder(); 205 } 206 //输出当前节点 207 System.out.println(this); 208 //向右子树遍历 209 if(this.right!=null) { 210 this.right.infixOrder(); 211 } 212 } 213 //后序遍历 214 public void postOrder() { 215 //向左子树遍历 216 if(this.left!=null) { 217 this.left.postOrder(); 218 } 219 //向右子树遍历 220 if(this.right!=null) { 221 this.right.postOrder(); 222 } 223 //输出当前节点 224 System.out.println(this); 225 } 226 227 //前序查找 228 /** 229 * 230 * @param no 查找no 231 * @return 如果找到就返回该Node,否则返回null 232 */ 233 public HeroNode preOrderSearch(int no) { 234 System.out.println("进入前序查找"); 235 //比较当前节点是否是 236 if(this.no==no) { 237 return this; 238 } 239 //向左判断 240 HeroNode resNode=null; 241 if(this.left!=null) { 242 resNode=this.left.preOrderSearch(no); 243 } 244 if(resNode!=null) { 245 return resNode; 246 } 247 //向右判断 248 if(this.right!=null) { 249 resNode=this.right.preOrderSearch(no); 250 } 251 return resNode; 252 } 253 254 //中序遍历查找 255 public HeroNode infixOrderSearch(int no) { 256 //向左判断 257 HeroNode resNode=null; 258 if(this.left!=null) { 259 resNode=this.left.infixOrderSearch(no); 260 } 261 if(resNode!=null) { 262 return resNode; 263 } 264 System.out.println("进入中序查找"); 265 //判断当前节点 266 if(this.no==no) { 267 return this; 268 } 269 //向右判断 270 if(this.right!=null) { 271 resNode=this.right.infixOrderSearch(no); 272 } 273 return resNode; 274 } 275 276 //后序遍历查找 277 public HeroNode postOrderSearch(int no) { 278 //向左判断 279 HeroNode resNode=null; 280 if(this.left!=null) { 281 resNode=this.left.postOrderSearch(no); 282 } 283 if(resNode!=null) { 284 return resNode; 285 } 286 //向右判断 287 if(this.right!=null) { 288 resNode=this.right.postOrderSearch(no); 289 } 290 if(resNode!=null) { 291 return resNode; 292 } 293 //判断当前节点 294 System.out.println("进入后序查找"); 295 if(this.no==no) { 296 return this; 297 }else { 298 return null; 299 } 300 } 301 }
3.顺序存储二叉树
3.1顺序存储二叉树的特点
(1)顺序二叉树通常只考虑完全二叉树
(2)第n个元素的左节点为2*n+1
(3)第n个元素的右节点为2*n+2
(4)第n个元素的父节点为(n-1)/2
(5)n表示二叉树中第几个元素(从0开始编号)
3.2 需求:数组{1,2,3,4,5,6,7},要求以二叉树前序遍历的方式进行遍历,结果应当为{1,2,4,5,3,6,7}
1 package cn.atguigu.Tree; 2 3 public class ArrBinaryTreeDemo { 4 5 public static void main(String[] args) { 6 // TODO Auto-generated method stub 7 int[] arr= {1,2,3,4,5,6,7}; 8 ArrBinaryTree arrbinaryTree=new ArrBinaryTree(arr); 9 arrbinaryTree.preOrder(); 10 } 11 12 } 13 //编写ArrBinaryTree,实现顺序存储二叉树遍历 14 class ArrBinaryTree{ 15 private int[] arr;//存储数据节点的数组 16 17 public ArrBinaryTree(int[] arr) { 18 this.arr = arr; 19 } 20 //重载 21 public void preOrder() { 22 this.preOrder(0); 23 } 24 25 //编写一个方法,完成顺序存储二叉树的前序遍历 26 /** 27 * 28 * @param index 表示数组的下标 29 */ 30 public void preOrder(int index) { 31 //如果数组为空,或者arr.length=0 32 if(arr==null||arr.length==0) { 33 System.out.println("数组为空,不能按照二叉树的前序遍历"); 34 } 35 //输出当前这个元素 36 System.out.println(arr[index]); 37 //向左 38 if((index*2+1)<arr.length) { 39 preOrder(2*index+1); 40 } 41 //向右 42 if((index*2+2)<arr.length) { 43 preOrder(2*index+2); 44 } 45 } 46 }
3.3 顺序二叉树的实际应用场景是堆排序
4. 线索二叉树
4.1 基本介绍
(1)n个节点的二叉链表中含有n+1个空指针域,利用二叉链表中的空指针域,存放指向该节点在某种遍历次序下的前驱和后继节点的指针
(2)这种添加了线索的二叉链表称为线索链表,相应的二叉树为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树,中序线索二叉树,后序线索二叉树三种;
(3)一个节点的前一个节点,称为前驱节点;
(4)一个节点的后一个节点,称为后继节点;
4.2 线索二叉树应用案例
(1)当线索化二叉树后,Node节点的属性left和right,有以下两种情况:left指向的是左子树,也可能指向的是前驱节点;right指向的是右子树,也可能指向后继节点;
中序遍历的结果:{B,F,D,A,C,G,E,H}
(2)源代码
1 package cn.atguigu.Tree; 2 3 public class ThreadedBinaryTreeDemo { 4 5 public static void main(String[] args) { 6 // TODO Auto-generated method stub 7 //中序线索二叉树测试 8 ThreadedHeroNode root=new ThreadedHeroNode(1,"tom"); 9 ThreadedHeroNode node2=new ThreadedHeroNode(3,"jack"); 10 ThreadedHeroNode node3=new ThreadedHeroNode(6,"smith"); 11 ThreadedHeroNode node4=new ThreadedHeroNode(8,"mary"); 12 ThreadedHeroNode node5=new ThreadedHeroNode(10,"king"); 13 ThreadedHeroNode node6=new ThreadedHeroNode(14,"dim"); 14 15 //手动创建二叉树 16 root.setLeft(node2); 17 root.setRight(node3); 18 node2.setLeft(node4); 19 node2.setRight(node5); 20 node3.setLeft(node6); 21 22 //测试线索化 23 ThreadedBinaryTree tree=new ThreadedBinaryTree(); 24 tree.setRoot(root); 25 tree.threadedNodes();//线索化 26 27 //测试,以10节点测试 28 ThreadedHeroNode left=node5.getLeft(); 29 System.out.println("10号节点的前驱节点是:"+left); 30 31 //使用线索化遍历的方式 32 System.out.println("使用线索化的方式遍历,线索化二叉树"); 33 tree.threadedList(); 34 } 35 36 } 37 //定义ThreadedBinaryTree 二叉树 实现了线索化功能的二叉树 38 class ThreadedBinaryTree{ 39 private ThreadedHeroNode root; 40 //为了实现线索化,需要创建要给指向当前节点的新节点的指针 41 private ThreadedHeroNode pre=null; 42 //在递归进行线索化时,pre总是保留前一个节点 43 44 public void setRoot(ThreadedHeroNode root) { 45 this.root = root; 46 } 47 48 //重载 49 public void threadedNodes() { 50 this.threadedNodes(root); 51 } 52 //遍历线索化二叉树的方法 53 public void threadedList() { 54 //定义一个遍历,存储当前遍历的节点,从root开始 55 ThreadedHeroNode node=root; 56 while(node!=null) { 57 //循环的找到leftType=1的节点 58 //后面随着遍历而变化,因为当leftType=1时,说明该节点是按照线索化 59 //处理后的有效节点 60 while(node.getLeftType()==0) { 61 node=node.getLeft(); 62 } 63 //输出当前节点 64 System.out.println(node); 65 //如果当前节点的右指针指向的是后继节点,就一直输出 66 while(node.getRightType()==1) { 67 node=node.getRight(); 68 System.out.println(node); 69 } 70 //替换遍历节点 71 node=node.getRight(); 72 } 73 } 74 75 //编写对二叉树进行中序线索化的方法 76 /** 77 * 78 * @param node 当前需要线索化的节点 79 */ 80 public void threadedNodes(ThreadedHeroNode node) { 81 //如果node==null,不能线索化 82 if(node==null) { 83 return; 84 } 85 //先线索化左子树 86 threadedNodes(node.getLeft()); 87 //线索化当前节点 88 //处理当前节点的前驱节点 89 if(node.getLeft()==null) { 90 //让当前节点的做指针指向当前节点 91 node.setLeft(pre); 92 node.setLeftType(1);//修改当前节点的指针类型,当前节点指向前驱节点 93 } 94 //处理后继节点 95 if(pre!=null&&pre.getRight()==null) { 96 pre.setRight(node);//让前驱节点的右指针指向当前指针 97 pre.setRightType(1);//修改前驱节点的指针类型 98 } 99 //每处理一个节点后,让当前节点是下一个节点的前驱节点 100 pre=node; 101 102 //后线索化右子树 103 threadedNodes(node.getRight()); 104 } 105 } 106 107 108 109 //先创建ThreadedHeroNode节点 110 class ThreadedHeroNode{ 111 private int no; 112 private String name; 113 private ThreadedHeroNode left;//默认null 114 private ThreadedHeroNode right;//默认null 115 //说明 116 //1.如果leftType==0表明指向左子树,1表示指向前驱节点 117 //2如果righttype==0表明指向右子树,1表示指向后继节点 118 private int leftType; 119 private int rightType; 120 121 public int getLeftType() { 122 return leftType; 123 } 124 public void setLeftType(int leftType) { 125 this.leftType = leftType; 126 } 127 public int getRightType() { 128 return rightType; 129 } 130 public void setRightType(int rightType) { 131 this.rightType = rightType; 132 } 133 public ThreadedHeroNode(int no, String name) { 134 this.no = no; 135 this.name = name; 136 } 137 public int getNo() { 138 return no; 139 } 140 public void setNo(int no) { 141 this.no = no; 142 } 143 public String getName() { 144 return name; 145 } 146 public void setName(String name) { 147 this.name = name; 148 } 149 public ThreadedHeroNode getLeft() { 150 return left; 151 } 152 public void setLeft(ThreadedHeroNode left) { 153 this.left = left; 154 } 155 public ThreadedHeroNode getRight() { 156 return right; 157 } 158 public void setRight(ThreadedHeroNode right) { 159 this.right = right; 160 } 161 @Override 162 public String toString() { 163 return "ThreadedHeroNode [no=" + no + ", name=" + name + "]"; 164 } 165 }