赞
踩
- 实现堆排序
/** * 二叉树 * * @author yaomingfeng * @version C10 2017年12月18日 * @since SDP V300R003C10 */ public class ArrayTree { public static void main(String[] args) { int array[] = {0, 9, 3, 5, 7, 26, 6, 9, 1, 4}; int size = array.length; while (size > 2) { // 最大堆 // 循环所有的父节点(从后向前循环) for (int i = (size - 1) / 2; i >= 1; i--) { // 先找到最大的子节点 int maxIndex = i * 2;// 假设最大的子节点是左节点 // 右节点存在且比左节点大 if ((maxIndex + 1) < size && array[maxIndex + 1] > array[maxIndex]) { maxIndex++; } // 拿最大的子节点与父节点比较 if (array[maxIndex] > array[i]) { int temp = array[maxIndex]; array[maxIndex] = array[i]; array[i] = temp; } } // 将根节点与最后一个节点交换 int data = array[1]; array[1] = array[size - 1]; array[size - 1] = data; size--; } for (int i = 1; i < array.length; i++) { System.out.println(array[i]); } } }
如果你要排序8个数,就定义9个长度的数组,因为第一个数是不处理的
- 实现中序排序
/** * TODO 添加类的描述 * * @author yaomingfeng * @version C10 2017年12月18日 * @since SDP V300R003C10 */ @Data public class Node { int value; Node left; Node right; }
/** * TODO 添加类的描述 * * @author yaomingfeng * @version C10 2017年12月18日 * @since SDP V300R003C10 */ public class LinkedTree { static Node root; public void add(int value) { Node node = new Node(value); if (root == null) { root = node; } else { Node temp = root;// temp为当前节点 while (true) { if (value < temp.getValue()) { if (temp.getLeft() == null) { temp.setLeft(node); break; } else { temp = temp.getLeft(); } } else { if (temp.getRight() == null) { temp.setRight(node); break; } else { temp = temp.getRight(); } } } } } //打印 private static void showNode(Node pnode) { // System.out.println(pnode.getValue());前序遍历 if (pnode.getLeft() != null) { showNode(pnode.getLeft()); } System.out.println(pnode.getValue());// 中序遍历 if (pnode.getRight() != null) { showNode(pnode.getRight()); } // System.out.println(pnode.getValue());// 后序遍历 } }
红黑树是一种含有红黑结点并能自平衡的二叉查找树。它必须满足下面性质:
性质1:每个节点要么是黑色,要么是红色。
性质2:根节点是黑色。
性质3:每个叶子节点(NIL)是黑色。
性质4:每个红色结点的两个子结点一定都是黑色。
性质5:任意一结点到每个叶子结点的路径都包含数量相同的黑结点。(如果一个结点存在黑子结点,那么该结点肯定有两个子结点)(在Java中,叶子结点是为null的结点)
红黑树并不是一个完美平衡二叉查找树,即任意一个结点到到每个叶子结点的路径都包含数量相同的黑结点。所这种平衡为黑色完美平衡。
左旋:以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变。
右旋:以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变。
变色:结点的颜色由红变黑或由黑变红。
因为红黑树是一颗二叉平衡树,并且查找不会破坏树的平衡,所以查找跟二叉平衡树的查找无异:
1.从根结点开始查找,把根结点设置为当前结点;
2.若当前结点为空,返回null;
3.若当前结点不为空,用当前结点的key跟查找key作比较;
4.若当前结点key等于查找key,那么该key就是查找目标,返回当前结点;
5.若当前结点key大于查找key,把当前结点的左子结点设置为当前结点,重复步骤2;
6.若当前结点key小于查找key,把当前结点的右子结点设置为当前结点,重复步骤2;
正由于红黑树总保持黑色完美平衡,所以它的查找最坏时间复杂度为O(2lgN),也即整颗树刚好红黑相隔的时候。能有这么好的查找效率得益于红黑树自平衡的特性,而这背后的付出,红黑树的插入操作功不可没~
插入操作包括两部分工作:一查找插入的位置;二插入后自平衡。查找插入的父结点很简单,跟查找操作区别不大:
从根结点开始查找;
若根结点为空,那么插入结点作为根结点,结束。
若根结点不为空,那么把根结点作为当前结点;
若当前结点为null,返回当前结点的父结点,结束。
若当前结点key等于查找key,那么该key所在结点就是插入结点,更新结点的值,结束。
若当前结点key大于查找key,把当前结点的左子结点设置为当前结点,重复步骤4;
若当前结点key小于查找key,把当前结点的右子结点设置为当前结点,重复步骤4;
插入位置已经找到,把插入结点放到正确的位置就可以啦,但插入结点是应该是什么颜色呢?答案是红色。理由很简单,红色在父结点(如果存在)为黑色结点时,红黑树的黑色平衡没被破坏,不需要做自平衡操作。但如果插入结点是黑色,那么插入位置所在的子树黑色结点总是多1,必须做自平衡。
最简单的一种情景,直接把插入结点作为根结点就行,但注意,根据红黑树性质2:根节点是黑色。还需要把插入结点设为黑色。
处理:把插入结点作为根结点,并把结点设置为黑色。
插入结点的Key已存在,既然红黑树总保持平衡,在插入前红黑树已经是平衡的,那么把插入结点设置为将要替代结点的颜色,再把结点的值更新就完成插入。
处理:把I设为当前结点的颜色,更新当前结点的值为插入结点的值
由于插入的结点是红色的,当插入结点的黑色时,并不会影响红黑树的平衡,直接插入即可,无需做自平衡。
处理:直接插入。
插入情景4:插入结点的父结点为红结点
再次回想下红黑树的性质2:根结点是黑色。如果插入的父结点为红结点,那么该父结点不可能为根结点,所以插入结点总是存在祖父结点。这点很重要,因为后续的旋转操作肯定需要祖父结点的参与。
情景4又分为很多子情景,下面将进入重点部分,各位看官请留神了。
插入情景4.1:叔叔结点存在并且为红结点
从红黑树性质4可以,祖父结点肯定为黑结点,因为不可以同时存在两个相连的红结点。那么此时该插入子树的红黑层数的情况是:黑红红。显然最简单的处理方式是把其改为:红黑红。如图9和图10所示。
处理:
将P和S设置为黑色
将PP设置为红色
把PP设置为当前插入结点
可以看到,我们把PP结点设为红色了,如果PP的父结点是黑色,那么无需再做任何处理;但如果PP的父结点是红色,根据性质4,此时红黑树已不平衡了,所以还需要把PP当作新的插入结点,继续做插入操作自平衡处理,直到平衡为止。
试想下PP刚好为根结点时,那么根据性质2,我们必须把PP重新设为黑色,那么树的红黑结构变为:黑黑红。换句话说,从根结点到叶子结点的路径中,黑色结点增加了。这也是唯一一种会增加红黑树黑色结点层数的插入情景。
我们还可以总结出另外一个经验:红黑树的生长是自底向上的。这点不同于普通的二叉查找树,普通的二叉查找树的生长是自顶向下的。
插入情景4.2:叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的左子结点
单纯从插入前来看,也即不算情景4.1自底向上处理时的情况,叔叔结点非红即为叶子结点(Nil)。因为如果叔叔结点为黑结点,而父结点为红结点,那么叔叔结点所在的子树的黑色结点就比父结点所在子树的多了,这不满足红黑树的性质5。后续情景同样如此,不再多做说明了。
前文说了,需要旋转操作时,肯定一边子树的结点多了或少了,需要租或借给另一边。插入显然是多的情况,那么把多的结点租给另一边子树就可以了。
插入情景4.2.1:插入结点是其父结点的左子结点
处理:
将P设为黑色
将PP设为红色
对PP进行右旋
黑树插入已经够复杂了,但删除更复杂,也是红黑树最复杂的操作了。但稳住,胜利的曙光就在前面了!
红黑树的删除操作也包括两部分工作:一查找目标结点;而删除后自平衡。查找目标结点显然可以复用查找操作,当不存在目标结点时,忽略本次操作;当存在目标结点时,删除后就得做自平衡处理了。删除了结点后我们还需要找结点来替代删除结点的位置,不然子树跟父辈结点断开了,除非删除结点刚好没子结点,那么就不需要替代。
二叉树删除结点找替代结点有3种情情景:
情景1:若删除结点无子结点,直接删除
情景2:若删除结点只有一个子结点,用子结点替换删除结点
情景3:若删除结点有两个子结点,用后继结点(大于删除结点的最小结点)替换删除结点
补充说明下,情景3的后继结点是大于删除结点的最小结点,也是删除结点的右子树种最右结点。那么可以拿前继结点(删除结点的左子树最左结点)替代吗?可以的。但习惯上大多都是拿后继结点来替代,后文的讲解也是用后继结点来替代。另外告诉大家一种找前继和后继结点的直观的方法(不知为何没人提过,大家都知道?):把二叉树所有结点投射在X轴上,所有结点都是从左到右排好序的,所有目标结点的前后结点就是对应前继和后继结点
接下来,讲一个重要的思路:删除结点被替代后,在不考虑结点的键值的情况下,对于树来说,可以认为删除的是替代结点!
基于此,上面所说的3种二叉树的删除情景可以相互转换并且最终都是转换为情景1!
情景2:删除结点用其唯一的子结点替换,子结点替换为删除结点后,可以认为删除的是子结点,若子结点又有两个子结点,那么相当于转换为情景3,一直自顶向下转换,总是能转换为情景1。(对于红黑树来说,根据性质5.1,只存在一个子结点的结点肯定在树末了)
情景3:删除结点用后继结点(肯定不存在左结点),如果后继结点有右子结点,那么相当于转换为情景2,否则转为为情景1。
二叉树删除结点情景关系图如图18所示。
图18 二叉树删除情景转换
综上所述,删除操作删除的结点可以看作删除替代结点,而替代结点最后总是在树末。有了这结论,我们讨论的删除红黑树的情景就少了很多,因为我们只考虑删除树末结点的情景了。
同样的,我们也是先来总体看下删除操作的所有情景,如图19所示。
图19 红黑树删除情景
哈哈,是的,即使简化了还是有9种情景!但跟插入操作一样,存在左右对称的情景,只是方向变了,没有本质区别。同样的,我们还是来约定下,如图20所示。
图20 删除操作结点的叫法约定
图20的字母并不代表结点Key的大小。R表示替代结点,P表示替代结点的父结点,S表示替代结点的兄弟结点,SL表示兄弟结点的左子结点,SR表示兄弟结点的右子结点。灰色结点表示它可以是红色也可以是黑色。
值得特别提醒的是,R是即将被替换到删除结点的位置的替代结点,在删除前,它还在原来所在位置参与树的子平衡,平衡后再替换到删除结点的位置,才算删除完成。
万事具备,我们进入最后的也是最难的讲解。
删除情景1:替换结点是红色结点
我们把替换结点换到了删除结点的位置时,由于替换结点时红色,删除也了不会影响红黑树的平衡,只要把替换结点的颜色设为删除的结点的颜色即可重新平衡。
处理:颜色变为删除结点的颜色
删除情景2:替换结点是黑结点
当替换结点是黑色时,我们就不得不进行自平衡处理了。我们必须还得考虑替换结点是其父结点的左子结点还是右子结点,来做不同的旋转操作,使树重新平衡。
删除情景2.1:替换结点是其父结点的左子结点
删除情景2.1.1:替换结点的兄弟结点是红结点
若兄弟结点是红结点,那么根据性质4,兄弟结点的父结点和子结点肯定为黑色,不会有其他子情景,我们按图21处理,得到删除情景2.1.2.3(后续讲解,这里先记住,此时R仍然是替代结点,它的新的兄弟结点SL和兄弟结点的子结点都是黑色)。
处理:
将S设为黑色
将P设为红色
对P进行左旋,得到情景2.1.2.3
进行情景2.1.2.3的处理
图21 删除情景2.1.1
删除情景2.1.2:替换结点的兄弟结点是黑结点
当兄弟结点为黑时,其父结点和子结点的具体颜色也无法确定(如果也不考虑自底向上的情况,子结点非红即为叶子结点Nil,Nil结点为黑结点),此时又得考虑多种子情景。
删除情景2.1.2.1:替换结点的兄弟结点的右子结点是红结点,左子结点任意颜色
即将删除的左子树的一个黑色结点,显然左子树的黑色结点少1了,然而右子树又又红色结点,那么我们直接向右子树“借”个红结点来补充黑结点就好啦,此时肯定需要用旋转处理了。如图22所示。
处理:
将S的颜色设为P的颜色
将P设为黑色
将SR设为黑色
对P进行左旋
图22 删除情景2.1.2.1
平衡后的图怎么不满足红黑树的性质?前文提醒过,R是即将替换的,它还参与树的自平衡,平衡后再替换到删除结点的位置,所以R最终可以看作是删除的。另外图2.1.2.1是考虑到第一次替换和自底向上处理的情况,如果只考虑第一次替换的情况,根据红黑树性质,SL肯定是红色或为Nil,所以最终结果树是平衡的。如果是自底向上处理的情况,同样,每棵子树都保持平衡状态,最终整棵树肯定是平衡的。后续的情景同理,不做过多说明了。
删除情景2.1.2.2:替换结点的兄弟结点的右子结点为黑结点,左子结点为红结点
兄弟结点所在的子树有红结点,我们总是可以向兄弟子树借个红结点过来,显然该情景可以转换为情景2.1.2.1。图如23所示。
处理:
将S设为红色
将SL设为黑色
对S进行右旋,得到情景2.1.2.1
进行情景2.1.2.1的处理
图23 删除情景2.1.2.2
删除情景2.1.2.3:替换结点的兄弟结点的子结点都为黑结点
好了,此次兄弟子树都没红结点“借”了,兄弟帮忙不了,找父母呗,这种情景我们把兄弟结点设为红色,再把父结点当作替代结点,自底向上处理,去找父结点的兄弟结点去“借”。但为什么需要把兄弟结点设为红色呢?显然是为了在P所在的子树中保证平衡(R即将删除,少了一个黑色结点,子树也需要少一个),后续的平衡工作交给父辈们考虑了,还是那句,当每棵子树都保持平衡时,最终整棵总是平衡的。
处理:
将S设为红色
把P作为新的替换结点
重新进行删除结点情景处理
图24 情景2.1.2.3
删除情景2.2:替换结点是其父结点的右子结点
好啦,右边的操作也是方向相反,不做过多说明了,相信理解了删除情景2.1后,肯定可以理解2.2。
删除情景2.2.1:替换结点的兄弟结点是红结点
处理:
将S设为黑色
将P设为红色
对P进行右旋,得到情景2.2.2.3
进行情景2.2.2.3的处理
图25 删除情景2.2.1
删除情景2.2.2:替换结点的兄弟结点是黑结点
删除情景2.2.2.1:替换结点的兄弟结点的左子结点是红结点,右子结点任意颜色
处理:
将S的颜色设为P的颜色
将P设为黑色
将SL设为黑色
对P进行右旋
图26 删除情景2.2.2.1
删除情景2.2.2.2:替换结点的兄弟结点的左子结点为黑结点,右子结点为红结点
处理:
将S设为红色
将SR设为黑色
对S进行左旋,得到情景2.2.2.1
进行情景2.2.2.1的处理
图27 删除情景2.2.2.2
删除情景2.2.2.3:替换结点的兄弟结点的子结点都为黑结点
处理:
将S设为红色
把P作为新的替换结点
重新进行删除结点情景处理
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。