当前位置:   article > 正文

详解平衡二叉搜索树( AVL树 二叉树) 操作(附图解)Java实现_java实现平衡二叉树查找

java实现平衡二叉树查找

二叉树

(个人纯手写,花了几天时间调试,有问题欢迎留言一起讨论  )

目录

1、二叉树的概念:

(1)、简介

(2)、为什么需要二叉树呢?

2、二叉树的插入操作

(1)、静态内部类属性

(2)、插入:

3、遍历查找等简单操作

(1)、遍历

(2)、查找:

(3)、查找父节点:

4、二叉树的删除:

(1)、被删除的节点,是一颗叶子节点

(2)、被删除的节点有一颗右子节点

(3)、要删除的节点有一颗左子节点

(4)、要删除的节点有两个子节点:

5、树的旋转:

(1)、单旋转之左左情况

(2)、单旋转之右右情况

(3)、双旋转 :

6、Java代码实现:


 

 

1、二叉树的概念:

(1)简介

树是一种非线性的数据结构,在现实生活中,我们一般的树长这个样子的:

但是在编程的世界中,我们一般把树倒过来看,这样容易我们分析;二叉树每个结点最多有两个子树的树结构,所以叫二叉树,不是三四五叉树,它是一种单向关系的特殊链表结构;这也是二叉树名字的由来,如下图1.1:

 

(2)、为什么需要二叉树呢?

假如我有一个插入和查找都比较频繁需求,

你会发现,用有序链表,插入操作成本小,查找成本大O(N);

用有序数组,查找成本小。但插入操作成本很大O(N)。

二叉树,本质上,是对链表和数组的一个折中。所以,我们折中使用排序二叉树(二叉树仅仅作为排序二叉树的基础),查找、插入操作成本也挺小O(logN)。具体的应用就是由排序二叉树(由于普通排序二叉树可能会有不平衡的情况)引申出来的红黑树(linux中ext3文件系统管理),AVL树“windows对进程地址空间的管理”。

(3)、平衡二叉搜索树(Self-balancing binary search tree):又被称为AVL树(Adelson-Velskii and Landis发明者命名)(有别于AVL算法),且具有以下性质:它是一棵空树或,它的任意一颗节点的左右两个子树高度差的绝对值<=1,并且左右两个子树都是一棵平衡二叉树;

 

2、二叉树的插入操作

(1)静态内部类属性

先来看一给出的下二叉树中的节点静态内部类属性:

  1. /**
  2. * 内部类Node树对象,存值E value,左子节点,右子节点 Node<E>表示泛型,
  3. *可以支持对传入类型的排序存储
  4. */
  5. private static class Node<E> {
  6. E value;
  7. Node<E> left;
  8. Node<E> right;
  9. // 值0表示根节点,值1表示左节点,值2表示右节点
  10. // 如图中的节点5、7、10、22、23、24、30都属于左节点类型,
  11. // isLeftRight属性值为1;
  12. // 如图中的15、17、30、36、38都属于右节点类型,
  13. // isLeftRight属性值为2;
  14. Integer isLeftRight;
  15. public Node(E value, Node<E> leftNode, Node<E> rightNode,
  16. Integer isLeftRight) {
  17. this.value = value;
  18. this.left = leftNode;
  19. this.right = rightNode;
  20. this.isLeftRight = isLeftRight;
  21. }
  22. }

(2)插入:

先插入第一个节点是根节点,按照递归方式,小的节点插入到根节点到左边,大的节点插入到根节点的右边;

例如第一个插入的节点是20,那么20作为根节点,下面插入10的时候,判断10小于20,把10放到根节点20的左边,30大于20,就把30放到根节点20的右边;

这时候,20叫做10和30的父节点,10是20的左子节点,30是20的右子节点;

依次类推,再插入15节点和7节点,因为15和7都小于根节点20,所以都插入到20的左边;因为是二叉树,20只能有10和30两个子节点;所以再把10作为左边子树的父节点,15大于10,15插入到10的右边,7小于10,7插入到10的左边;同理,10叫做7和15的父节点,7叫做10的左子节点,15叫做10的右子节点;如下图1.2

为了方便后续删除等操作,同时在插入节点的时候,我们会为这颗节点标记为左节点(值为1)还是右节点类型(值为2);

上述代码中静态内部类Node<E>中的字段:Integer isLeftRight 用来存储左右节点类型的字段;

如上图1.2:节点710都叫做左子节点类型,isLeftRight = 1

如上图1.2:节点1530都叫做右子节点类型,isLeftRight = 2

特殊情况,根节点20的类型值 isLeftRight = 0 ;(一棵树只有一个根节点)

 

3、遍历查找等简单操作

(1)遍历

我们先把节点都插入,形成一棵树了,开始遍历操作,一共有3种方式:

前序遍历:先访问中间根节点,然后访问左子节点、再访问右子节点

中序遍历:左、中、右;

后续遍历:左、右、中;

都是采用递归的方式遍历,下面的代码中,我只实现了前序遍历方式,其他两种方式类似,写了一种其他两种很好实现;

(2)查找:

查找的节点是否存在这颗树种,递归遍历;

(3)查找父节点:

辅助方法;查找的节点的父节点,为了方便实现删除操作;

 

4、二叉树的删除:

删除稍微复杂一些,删除共有几种情况:

(1)、被删除的节点,是一颗叶子节点

被删除的节点,是一颗叶子节点,没有左子树,也没有右子树,这种情况比较简单,如下图2,删除节点6或者22;这里需要做一个判断:

(a)、6是一颗右子节点(在他父节点的右边),找到他的父节点5,将5的right置空,将6的节点置对象Node赋值为null,有助于帮助垃圾回收;

(b)、22是一颗左子节点(在他父节点的左边),找到他的父节点23,将23的left置空,将22的节点置对象Node赋值为null,有助于帮助垃圾回收;

(2)、被删除的节点有一颗右子节点

要删除的节点有一颗右子节点,例如需要删除31,他有一颗右子节点32;同样需要判断31节点是一个左子节点,同时找到他的父节点33,将父节点33的左引用left指向到31的右子节点32,同时31的Node节点置空,如图3.1:

(其实这里还有一种情况没有列举,要删除的节点有一颗右子节点,但是他是一颗右子节点,例如删除节点16,操作类似)

(3)、要删除的节点有一颗左子节点

被删除的节点有一颗左子节点,例如需要删除23,他有一颗左子节点22;同样需要判断23节点是一个左子节点,找到他的父节点24,将父节点24的左引用left指向到23的左子节点22,同时原来的23Node节点置空,如图3.2:

(还有一种情况,要删除的节点有一颗左子节点,但是他是一颗右子节点,例如需要删除节点17,操作类似)

(4)、要删除的节点有两个子节点:

(a)、要删除节点30,在30的右子树中找到最左的节点,就是最小的值,即31,用31的值替代30,判断最小左节点31是否还有右节点,如果它有右节点(说明最小节点31肯定是一个左子节点,因为我这里说找到左子树最小的值,左边不可能再有子节点了,所以只需要判断是否有右节点),让他的父节点33的左子节点指向31的右子节点32(Node33.left=Node31.right,同时赋值30节点的值为31:Node30.value= Node31.value;

(b)、要删除节点10,在30的右子树中找到最左的节点,最小的值,即16,用16的值替代10,判断最小左节点16是否还有右节点,这里没有右子节点,则需要判断16是左子节点(让父亲节点左指向为空: findParentNode (Node16).left=null);

(c)、这里假如删除的节点是7的时候,7的右树最小节点为8,8是一个右子节点,则让8的父节点7,右指向指向为空,同时赋值7为8节点的值findParentNode (Node8).right=null,Node7.value=Node8.value;

5、树的旋转:

在一些极端的情况下,插入的二叉树可能会影响它的效率,使它无法发挥二叉树的特性,这样就使我们创建二叉树失去了他的初衷意义;

例如,有一组依次插入的数据:1,2,3,4,5 ,这颗二叉树的性能,就跟普通的链表一样了,查找性能将会非常慢,如下图5.1:

那么我们怎么作才能是一颗二叉树保持平衡呢?那就是对二叉树做旋转操作;

在每插入(删除)一个节点时候,都需要做一次平衡判断,如果不平衡(平衡条件: 它的任意一颗节点的左右子树高度差的绝对值<=1)则需要旋转操作:

(1)、单旋转之左左情况

单旋转之左左情况,向右旋转:例如有以下两种情况

我们先来看情况一的旋转:

a、当左边最小的子节点为7的时候,左边插入了一个节点5,作为20节点来说,5的高度值比30多了2,所以插入5,导致了二叉树不平衡;

那么把20作为当前节点,进行旋转操作,共分为以下a~f六个步骤:

(a)、创建一个新的节点,值等于当前节点的值

(b)、把新节点的右子树设置为当前节点的右子树

(c)、把新节点左子树设置为当前节点的左子树的右子树(假如当前节点的左子树的右子树不为空,此处为11)

(d)、当前节点的值变成它的左子树的值(Node20.value=Node10.value)

(e)、当前的左子节点的左子节点变为当前节点的左子节点(Node20.left = Node20.left.left,7和5都向上升一级)

(f)、当前节点的右节点指向新节点(Node20.right = newNode)

旋转完成以后,与旋转前对比如下,这样就没有高度值差大于1的情况了:

b、情况二的旋转,就简单多了,依然可以使用上述通用流程

 

(2)、单旋转之右右情况

单旋转之右右情况,把上述(1)的描述的左变右,右变左,即左旋转:

(a)、创建一个新的节点,值等于当前节点的值

(b)、把新节点的左子树设置为当前节点的左子树

(c)、把新节点右子树设置为当前节点的右子树的左子树

(d)、当前节点的值变成它的右子树的值

(e)、当前的右子节点的右子节点变为当前节点的右子节点

(f)、当前节点的左节点指向新节点

右右情况左旋转完成后,与旋转前对比图

(3)双旋转 :

a、在上述步骤(1)的情况一当中,如果不是往节点7的左边插入节点5,而是往节点11的左边插入15,节点15和30对于20来说导致不平衡,假如直接进行左左情况右旋转操作以后:

还是造成了节点7和15的高度不一样,右旋转以后不平衡,那怎么解决呢?

 

b、在进行右旋转的时候,如果该节点左边的子树的左高度比右高度小(例如10作为根节点的左子树中,节点7就比15的高度小),那么需要先把该节点左边的子树进行一次左旋转,然后对该节点再进行右旋转如下:

需要旋转的节点20,那么他的左子树11的左高度比右高度高了,此时再按照讲述过的步骤(1)进行左左情况的右旋转,就可以解决平衡问题了;

6、Java代码实现:

二叉平衡树,上述整体逻辑描述完了,完整代码如下:

  1. /**
  2. * @author Ethan
  3. *
  4. * 20190625
  5. *
  6. * 创建一颗平衡二叉树,支持泛型对象E,支持E排序
  7. *
  8. * 支持增、删、查、旋转等操作
  9. */
  10. public class AVLTree<E extends Comparable<E>> {
  11. /**
  12. * 单独指定根节点
  13. */
  14. public Node<E> root;
  15. /**
  16. * 构造方法,初始化根节点为null
  17. */
  18. public AVLTree() {
  19. this.root = null;
  20. }
  21. /**
  22. * 内部类Node树对象,存值E value,左子节点,
  23. * 右子节点 Node<E>表示泛型,可以支持对传入类型的排序存储
  24. */
  25. private static class Node<E> {
  26. E value;
  27. Node<E> left;
  28. Node<E> right;
  29. // 值0表示根节点,值1表示左节点,值2表示右节点
  30. // 如图中的节点5、7、10、22、23、24、30都属于左节点类型,
  31. // isLeftRight属性值为1;
  32. // 如图中的15、17、30、36、38都属于右节点类型,
  33. // isLeftRight属性值为2;
  34. Integer isLeftRight;
  35. public Node(E value, Node<E> leftNode,
  36. Node<E> rightNode, Integer isLeftRight) {
  37. this.value = value;
  38. this.left = leftNode;
  39. this.right = rightNode;
  40. this.isLeftRight = isLeftRight;
  41. }
  42. }
  43. /**
  44. * 1.1、为树添加节点,提供对外的方法
  45. *
  46. * @param node
  47. */
  48. public Node<E> addNode(E e) {
  49. return insert(e, root);
  50. }
  51. /**
  52. * 1.2、为树添加节点,内部实现
  53. */
  54. private Node<E> insert(E e, Node<E> node) {
  55. // 添加的第一个元素为根节点
  56. if (null == root) {
  57. root = new Node<E>(e, null, null, 0);
  58. System.out.println("root:" + root.value);
  59. return root;
  60. }
  61. // 比较插入的元素,与节点的大小,如果大于,则往节点右边插入
  62. // 如果小于该节点,反之左边(第一次与root节点比较)
  63. int compareInt = e.compareTo(node.value);
  64. if (compareInt > 0) {
  65. if (node.right != null) {
  66. // 右边有子节点,继续递归调用
  67. insert(e, node.right);
  68. } else {
  69. Node<E> rightNode = new Node<E>(e, null,
  70. null, 2);
  71. // 创建单向的父子节点关系,父节点right指向子节点;
  72. node.right = rightNode;
  73. System.out.println("父节点为" + node.value
  74. + "===作为右子节点:" + rightNode.value);
  75. return rightNode;
  76. }
  77. } else {
  78. if (node.left != null) {
  79. // 左边有子节点,继续递归调用
  80. insert(e, node.left);
  81. } else {
  82. Node<E> leftNode = new Node<E>(e, null,
  83. null, 1);
  84. // 创建单向的父子节点关系,父节点left指向子节点;
  85. node.left = leftNode;
  86. // 插入一次节点,做一次平衡操作
  87. System.out.println("父节点为" + node.value
  88. + "===作为左子节点:" + leftNode.value);
  89. return leftNode;
  90. }
  91. }
  92. return node;
  93. }
  94. /**
  95. * 2.1、外部方法,查找节点
  96. *
  97. * @param node
  98. */
  99. public Node<E> findNode(E e) {
  100. if (root == null) {
  101. return null;
  102. }
  103. if (e.compareTo(root.value) == 0) {
  104. return root;
  105. }
  106. return findNode(e, root);
  107. }
  108. /**
  109. * 2.2、内部方法,查找节点
  110. */
  111. public Node<E> findNode(E e, Node<E> node) {
  112. if (e.compareTo(node.value) == 0) {
  113. return node;
  114. }
  115. // 大于从右边继续找
  116. if (e.compareTo(node.value) > 0) {
  117. if (node.right != null) {
  118. return findNode(e, node.right);
  119. } else
  120. return null;
  121. }
  122. // 小于从左边继续找
  123. else if (e.compareTo(node.value) < 0) {
  124. if (node.left != null) {
  125. return findNode(e, node.left);
  126. } else
  127. return null;
  128. } else
  129. return null;
  130. }
  131. /**
  132. * 3.1、对外遍历节点方法
  133. *
  134. * 前序遍历:先遍中节点,然后左子节点、右子节点
  135. *
  136. * 中序遍历:左、中、右
  137. *
  138. * 后续遍历:左、右、中
  139. *
  140. * @param node
  141. */
  142. public void getAllNode() {
  143. if (root == null) {
  144. return;
  145. }
  146. getAllNode(root);
  147. }
  148. /**
  149. * 3.2、内部方法,前序遍历节点
  150. */
  151. private void getAllNode(Node<E> e) {
  152. if (e.left != null) {
  153. System.out.println("父节点:" + e.value);
  154. System.out.println("左子节点:" + e.left.value);
  155. getAllNode(e.left);
  156. }
  157. if (e.right != null) {
  158. System.out.println("父节点:" + e.value);
  159. System.out.println("右子节点:" + e.right.value);
  160. getAllNode(e.right);
  161. }
  162. }
  163. /**
  164. * 3.3、对外提供方法,遍历节点,顺便打印出结构
  165. */
  166. public void getAllNodeWithParent() {
  167. System.out.println("根节点" + root.value);
  168. getAllNodeWithParent(root);
  169. }
  170. /**
  171. * 3.4、内部方法,遍历节点,顺便打印出结构
  172. */
  173. private void getAllNodeWithParent(Node<E> node) {
  174. if (node.left == null && node.right == null) {
  175. System.out.println("叶子节点值:" + node.value);
  176. } else {
  177. System.out.println("父节点值:" + node.value);
  178. }
  179. if (node.left != null) {
  180. System.out.println(node.value + "的左子节点值:"
  181. + node.left.value);
  182. getAllNodeWithParent(node.left);
  183. }
  184. if (node.right != null) {
  185. System.out.println(node.value + "的右子节点值:"
  186. + node.right.value);
  187. getAllNodeWithParent(node.right);
  188. }
  189. }
  190. /**
  191. * 4.1、找到父节点
  192. */
  193. public Node<E> findParentNode(E e) {
  194. return findParentNode(root, e);
  195. }
  196. /**
  197. * 4.2、内部方法,找到父节点
  198. */
  199. private Node<E> findParentNode(Node<E> node, E e) {
  200. // 父节点为空,或者该节点为root节点,那么root的父节点就不存在了
  201. if (root == null || root.value.compareTo(e) == 0) {
  202. return null;
  203. } else {// 当前节点的左子节点或右子节点值等于e,返回当前节点
  204. if (node.left != null
  205. && node.left.value.compareTo(e) == 0
  206. || node.right != null
  207. && node.right.value.compareTo(e) == 0) {
  208. return node;
  209. }
  210. // 递归继续从左边查找
  211. else if (node != null
  212. && node.value.compareTo(e) > 0) {
  213. return findParentNode(node.left, e);
  214. } // 递归继续从右边查找
  215. else if (node != null
  216. && node.value.compareTo(e) < 0) {
  217. return findParentNode(node.right, e);
  218. } else
  219. return null;
  220. }
  221. }
  222. /**
  223. * 5.1、删除节点
  224. *
  225. * @return
  226. */
  227. public boolean deleteNode(E e) {
  228. if (root == null) {
  229. return false;
  230. }
  231. Node<E> node = findNode(e);
  232. // a、先查找此节点是否存在
  233. if (node == null) {
  234. return false;
  235. }
  236. // b、节点存在,则需要找到它的父节点
  237. else {
  238. Node<E> parentNode = findParentNode(root, e);
  239. System.out.println(e + "的父亲节点是:"
  240. + parentNode.value + "(在删除方法中)");
  241. if (parentNode != null) {
  242. // c、如果需要删除的节点恰好是叶子节点(没有左右子节点了)
  243. if (node.left == null
  244. && node.right == null) {
  245. // d、如果要删除的节点是他父亲节点的左边子节点
  246. if (parentNode.left != null
  247. && parentNode.left.value.
  248. compareTo(node.value) == 0) {
  249. parentNode.left = null;
  250. return true;
  251. }
  252. // e、如果要删除的节点是他父亲节点的右边子节点
  253. else
  254. parentNode.right = null;
  255. // 删除一次节点,做一次平衡操作
  256. return true;
  257. }
  258. // f、如果需要删除的节点有一个左子节点
  259. if (node.left != null
  260. && node.right == null) {
  261. // 如果需要删除的节点是一个左节点
  262. // 则让父亲节点左边指向他的左子节点
  263. if (parentNode.isLeftRight == 1) {
  264. parentNode.left = node.left;
  265. // 删除一次节点,做一次平衡操作
  266. return true;
  267. }
  268. // 如果需要删除的节点是一个右节点
  269. // 则让父亲节点右边指向他的左子节点
  270. else if (parentNode.isLeftRight == 2) {
  271. parentNode.right = node.left;
  272. // 删除一次节点,做一次平衡操作
  273. return true;
  274. }
  275. }
  276. // g、如果需要删除的节点有一个右子节点
  277. if (node.right != null
  278. && node.left == null) {
  279. // 如果需要删除的节点是一个左节点
  280. // 则让父亲节点左边指向他的右子节点
  281. if (parentNode.isLeftRight == 1) {
  282. parentNode.left = node.right;
  283. // 删除一次节点,做一次平衡操作
  284. return true;
  285. }
  286. // 如果需要删除的节点是一个右节点
  287. // 则让父亲节点右边指向他的右子节点
  288. else if (parentNode.isLeftRight == 2) {
  289. parentNode.right = node.right;
  290. // 删除一次节点,做一次平衡操作
  291. return true;
  292. }
  293. }
  294. // h、如果需要删除的节点有两个子节点
  295. // (删除根节点时也适用)
  296. if (node.left != null
  297. && node.right != null) {
  298. // 删除右子树中最小节点,获取到最小节点的值;
  299. // 把最小节点的值,赋值给要删除节点的值;
  300. // (实际上并不是删除该节点,只是改变赋值);
  301. // 一般右边最小节点值,位于右子节点
  302. // 下面的最左的子节点;
  303. Node<E> minNode = deleteGetMin(
  304. node.right);
  305. // 找到该最小节点的父节点
  306. Node<E> minNodeParent =
  307. findParentNode(minNode.value);
  308. // 找到了最小的左节点,不可能再有左子节点
  309. if (minNode.right != null) {
  310. // 然后把找到的最小值minNode.value
  311. // 赋值给要删除的节点
  312. // 最小节点肯定是左子节点
  313. node.value = minNode.value;
  314. // 如果左最小节点minNode是一个右节点
  315. // (右边全是右节点)
  316. if (minNode.isLeftRight == 2) {
  317. minNodeParent.right =
  318. minNode.right;
  319. // 删除一次节点,做一次平衡操作
  320. return true;
  321. }
  322. // 如果左最小节点minNode是一个左节点
  323. else {
  324. // 最小节点的父节点,的左子节点
  325. //则指向最小节点的右子节点
  326. minNodeParent.left =
  327. minNode.right;
  328. // 删除一次节点,做一次平衡操作
  329. return true;
  330. }
  331. }
  332. // 得到的右子树最小节点,已经是一个叶子
  333. // 节点了,直接赋值
  334. else {
  335. // 如果左最小节点minNode是一个右节点
  336. // (右边全是右节点)
  337. if (minNode.isLeftRight == 2) {
  338. node.value = minNode.value;
  339. // 最小节点的父节点,的右子节点
  340. // 则指向空
  341. minNodeParent.right = null;
  342. // 删除一次节点,做一次平衡操作
  343. return true;
  344. } else {
  345. // 然后把找到的最小值minNode.value
  346. //赋值给要删除的节点
  347. // 最小节点肯定是左子节点
  348. node.value = minNode.value;
  349. // 最小节点的父节点,的左子节点则指向空
  350. minNodeParent.left = null;
  351. // 删除一次节点,做一次平衡操作
  352. return true;
  353. }
  354. }
  355. }
  356. }
  357. }
  358. return false;
  359. }
  360. /**
  361. * 5.2、获取右边最小节点值,位于右子节点下面的最左的子节点;
  362. *
  363. * @param right
  364. * @return
  365. */
  366. private Node<E> deleteGetMin(Node<E> node) {
  367. // 如果左边一直有节点,那么一直找到最小的
  368. // 非递归写法
  369. while (node.left != null) {
  370. node = node.left;
  371. }
  372. return node;
  373. //
  374. // //递归写法
  375. // if (node.left != null) {
  376. // return deleteGetMin(node.left);
  377. // }
  378. // return node;
  379. }
  380. /**
  381. * 6.1、判断该节点是属于左节点(返回1)还是右节点(返回2)
  382. *
  383. * 也可用Node内部类isLeftRight判断
  384. */
  385. public int isLeftRight(E e) {
  386. // 根节点返回0
  387. if (root.value.compareTo(e) == 0) {
  388. return 0;
  389. }
  390. return isLeftRight(root, e);
  391. }
  392. /**
  393. * 6.2、判断该节点是属于左节点(返回1)还是右节点(返回2)
  394. *
  395. * 也可用Node内部类isLeftRight判断
  396. */
  397. private int isLeftRight(Node<E> node, E e) {
  398. // 是node的左子节点
  399. if (node.left != null
  400. && node.left.value.compareTo(e) == 0) {
  401. return 1;
  402. } // 是node的右子节点
  403. if (node.right != null
  404. && node.right.value.compareTo(e) == 0) {
  405. return 2;
  406. }
  407. // 子节点nodeSon比node节点小,往左边递归
  408. if (node.left != null
  409. && e.compareTo(node.value) < 0) {
  410. return isLeftRight(node.left, e);
  411. }
  412. // 子节点nodeSon比node节点大,往右边递归
  413. if (node.right != null
  414. && e.compareTo(node.value) > 0) {
  415. return isLeftRight(node.right, e);
  416. }
  417. return -1;
  418. }
  419. /**
  420. * 7.1求树子节点的高度
  421. *
  422. * 在Node内部类里面有height方法可用,求高度
  423. */
  424. public int heigth(E e) {
  425. if (e == null) {
  426. return 0;
  427. }
  428. // 先找到节点
  429. Node<E> node = findNode(e);
  430. // 递归它的子树的高度,加上自己的高度
  431. return heigth(node) + 1;
  432. }
  433. /**
  434. * 7.2求树子节点的高度
  435. *
  436. * 在Node内部类里面有height方法可用,求高度
  437. */
  438. private int heigth(Node<E> node) {
  439. if (node == null) {
  440. return -1;
  441. }
  442. return Math.max(
  443. node.left == null ? 0
  444. : heigth(node.left) + 1,
  445. node.right == null ? 0
  446. : heigth(node.right) + 1);
  447. }
  448. /**
  449. * 左左情况一之向右旋转
  450. *
  451. * 8.1树右旋转
  452. */
  453. private void rightRotate(Node<E> node) {
  454. // (a)、创建一个新的节点,值等于当前节点的值
  455. Node<E> newNode = new Node<E>(node.value, null,
  456. null, 0);
  457. // (b)、把新节点的右子树设置为当前节点的右子树
  458. if (node.right != null) {
  459. newNode.right = node.right;
  460. }
  461. // (c)、把新节点左子树设置为当前节点的左子树的右子树
  462. if (node.left.right != null) {
  463. newNode.left = node.left.right;
  464. }
  465. // (d)、当前节点的值变成它的左子树的值
  466. node.value = node.left.value;
  467. // (e)、当前的左子节点变为当前节点的左子节点的左子节点
  468. node.left = node.left.left;
  469. // (f)、当前节点的右节点指向新节点
  470. node.right = newNode;
  471. }
  472. /**
  473. * 8.2树左旋转
  474. */
  475. private void leftRotate(Node<E> node) {
  476. // (a)、创建一个新的节点,值等于当前节点的值
  477. Node<E> newNode = new Node<E>(node.value, null,
  478. null, 0);
  479. // (b)、把新节点的左子树设置为当前节点的左子树
  480. if (node.left != null) {
  481. newNode.left = node.left;
  482. }
  483. // (c)、把新节点右子树设置为当前节点的右子树的左子树
  484. if (node.right.left != null) {
  485. newNode.right = node.right.left;
  486. }
  487. // (d)、当前节点的值变成它的右子树的值
  488. node.value = node.right.value;
  489. // (e)、当前的右子节点变为当前节点的右子节点的右子节点
  490. node.right = node.right.right;
  491. // (f)、当前节点的左节点指向新节点
  492. node.left = newNode;
  493. }
  494. /**
  495. * 9.1求节点的高度
  496. *
  497. * @param left
  498. * @return
  499. */
  500. public int height(Node<E> left) {
  501. if (left == null) {
  502. return 0;
  503. }
  504. return Math.max(
  505. left.left == null ? 0 : height(left.left),
  506. left.right == null ? 0 : height(left.right))
  507. + 1;
  508. }
  509. /**
  510. * 9.2求左子树的高度
  511. *
  512. * @主要用于求与右树差值,做树的旋转操作
  513. * @param node
  514. * @return
  515. */
  516. public int leftHeight(Node<E> node) {
  517. if (node == null) {
  518. return 0;
  519. }
  520. return height(node.left);
  521. }
  522. /**
  523. * 9.3求右子树的高度
  524. *
  525. * @主要用于求与左树差值,做树的旋转操作
  526. * @param node
  527. * @return
  528. */
  529. public int rightHeight(Node<E> node) {
  530. if (node == null) {
  531. return 0;
  532. }
  533. return height(node.right);
  534. }
  535. /**
  536. * 10.1平衡操作,旋转
  537. *
  538. * 左左向右单旋转
  539. *
  540. * 左左向左先旋转,再向右的双旋转
  541. *
  542. * 右右向左单旋转
  543. *
  544. * 右右向右先旋转,再向左的双旋转
  545. *
  546. * @param node
  547. */
  548. public void reBalance(Node<E> node) {
  549. System.out.println("旋转值节点:" + node.value);
  550. System.out.println("旋转值节点左高:" +
  551. leftHeight(node));
  552. System.out.println("旋转值节点右高:" +
  553. rightHeight(node));
  554. // 左左情况,该进行右旋转
  555. if (leftHeight(node) - rightHeight(node) > 1) {
  556. // 在进行右旋转的时候,如果该节点左边的子树
  557. // 的左高度比右高度小,那么需要先把该节点左边
  558. // 的子树进行一次左旋转,然后对该节点再进行右旋转
  559. // 这里就是双旋转了,先左旋转,再右旋转;
  560. if (node.left != null && leftHeight(
  561. node.left) < rightHeight(node.left)) {
  562. System.out.println(
  563. "双旋转开始,左旋:" + node.left.value);
  564. System.out.println("双旋转开始,右旋:"
  565. + node.value);
  566. leftRotate(node.left);
  567. rightRotate(node);
  568. } else {
  569. // 如果只执行这一步操作,就是单旋转,右旋转
  570. System.out.println("单旋转开始,右旋:"
  571. + node.value);
  572. rightRotate(node);
  573. }
  574. }
  575. // 同样,相反方向的右右情况,该进行左旋转
  576. // 右右情况,该进行左旋转
  577. if (rightHeight(node) - leftHeight(node) > 1) {
  578. // 在进行左旋转的时候,如果该节点右边的子树
  579. // 的右高度比左高度小,那么需要先把该节点右边
  580. // 的子树进行一次右旋转,然后对该节点再进行左旋转
  581. // 这里就是双旋转了,先右旋转,再左旋转;
  582. if (node.right != null && rightHeight(
  583. node.right) < leftHeight(node.right)) {
  584. rightRotate(node.right);
  585. leftRotate(node);
  586. } else {
  587. // 如果只执行这一步操作,就是单旋转,左旋转
  588. leftRotate(node);
  589. }
  590. }
  591. }
  592. public static void main(String[] args) {
  593. // 这组元素用来新增、删除、查询测试用
  594. // Integer integerArray[] = { 20, 10, 30, 7,
  595. // 5, 8, 6, 15, 17, 16, 24, 25, 36,
  596. // 23, 22, 33, 38, 31, 32, 37 };
  597. // 这组不平衡树,用来做旋转测试用
  598. Integer integerArray[] = { 20, 10, 30, 7, 5, 11 };
  599. // Integer integerArray[] = { 5, 4, 3, 1 };
  600. AVLTree<Integer> tree = new AVLTree<Integer>();
  601. // 1、插入节点
  602. for (int i = 0; i < integerArray.length; i++) {
  603. tree.addNode(integerArray[i]);
  604. }
  605. // 旋转根节点(疑问,到底在哪个时候旋转比较好)
  606. tree.reBalance(tree.root);
  607. // 可以递归旋转其他节点
  608. // 2、查找节点
  609. // AVLTree.Node<Integer> node =
  610. // tree.findNode(30);
  611. // System.out.println(node.value);
  612. // 3、遍历节点
  613. tree.getAllNodeWithParent();
  614. 4、查找父节点
  615. // int findParentValue = 10;
  616. // AVLTree.Node<Integer> parentNode =
  617. // tree.findParentNode(findParentValue);
  618. // if (parentNode != null) {
  619. // System.out.println(findParentValue +
  620. // " 's parentNode is:" +
  621. // parentNode.value);
  622. // }
  623. // // 5、删除节点
  624. // int deleteValue = 36;
  625. // boolean isDel=tree.deleteNode(deleteValue);
  626. // System.out.println(isDel );
  627. }
  628. }

 

下一篇:

红黑树详解

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/528392
推荐阅读
相关标签
  

闽ICP备14008679号