比如有这么一个数组:{1,2,3,4,5,6},对应的二叉排序树存在一个问题。如下图
image.png

  1. 左子树全部为空,从形式上看,更像是一个单链表
  2. 插入速度没有影响
  3. 查询速度明显降低(因为需要依次比较),不能发挥BST的优势,因为每次还需要比较左子树,其查询速度比单链表还慢
  4. 解决方案就是平衡二叉树(AVL)

    简介

    平衡二叉树也叫平衡二叉排序搜索树,又被称为AVL树,可以保证查询效率较高。
    AVL树具有的特点:它是一颗空树或者它的左右两个子树的高度差值的绝对值不能超过1,并且左右两个子树都是一颗平衡二叉树。平衡二叉树的常用实现方法有红黑树,AVL,替罪羊树,Treap,伸展树等。

如下三棵树
image.png

插入节点失去平衡

平衡二叉树(AVL) - 图3
上图中节点 66 的左子树高度为 1,右子树高度为 3,此时平衡因子为 -2,树失去平衡。
以节点 66 为父节点的那颗树就称为 最小失衡子树

最小失衡子树:在新插入的结点向上查找,以第一个平衡因子的绝对值超过 1 的结点为根的子树称为最小不平衡子树。也就是说,一棵失衡的树,是有可能有多棵子树同时失衡的。而这个时候,我们只要调整最小的不平衡子树,就能够将不平衡的树调整为平衡的树。
平衡二叉树的失衡调整主要是通过旋转最小失衡子树来实现的。根据旋转的方向有两种处理方式,左旋右旋
旋转的目的就是减少高度,通过降低整棵树的高度来平衡。哪边的树高,就把那边的树向上旋转。

平衡二叉树旋转

在插入完节点后,发现不再是一棵AVL树,这时候就要进行旋转

平衡二叉树左旋转

思路

  1. 当右子树的高度-左子树的高度>1时就要进行左旋转
  2. 创建一个新的节点,这个节点的值等于当前节点
  3. 把新节点的左子树设置为当前节点的左子树
  4. 把新节点的右子树设置为当前节点的右子树的左子树
  5. 把当前节点的的值替换为右子节点的值
  6. 把当前节点的右子树设置成右子树的右子树
  7. 把当前节点的左子树设置为新的节点

    图解

平衡二叉树(AVL) - 图4

平衡二叉树右旋转

思路

  1. 当左子树的高度-右子树的高度>1时,就要进行右旋转
  2. 创建一个新的节点,以当前节点的值
  3. 把新节点的右子树设置为当前节点的右子树
  4. 把新节点的左子树设置为当前节点的左子树的右子树
  5. 把当前节点的值换为左子节点的值
  6. 把当前节点的左子树设置为左子树的左子树
  7. 把当前节点的右子树设置为新的节点

    图解

平衡二叉树(AVL) - 图5

平衡二叉树双旋转

如图
图1右旋转后得到图2,还是不平衡。这种情况就要双旋转。
image.png
image.png

思路

  1. 当符合右旋转的条件时
  2. 如果它的左子树的右子树高度大于它的左子树的左子树高度
  3. 先对当前这个节点的左子节点进行左旋转
  4. 再对当前节点进行右旋转的操作
  5. 当符合左旋转的条件时,和上面的2,3,4步骤相反,思路顺序是一样的

image.png
image.png

代码

平衡二叉树就是二叉排序树,升级版

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. namespace ConsoleApp1
  5. {
  6. public class AVLTree
  7. {
  8. public Node root;
  9. //添加
  10. public void add (Node node)
  11. {
  12. if (root == null)
  13. root = node;
  14. else
  15. root.add(node);
  16. }
  17. //中序遍历
  18. public void infixOrder()
  19. {
  20. if (root == null)
  21. {
  22. Console.WriteLine("二叉树为空");
  23. return;
  24. }
  25. root.infixOrder();
  26. }
  27. //查找要删除的节点
  28. public Node search(int val)
  29. {
  30. if (root == null)
  31. return null;
  32. else
  33. return root.search(val);
  34. }
  35. //查找要删除节点的父节点
  36. public Node searchParent(int val)
  37. {
  38. if (root == null)
  39. return null;
  40. else
  41. return root.searchParent(val);
  42. }
  43. //删除右边节点的最小的一个节点
  44. //返回以node为根节点的二叉排序树的最小节点的值,并且删除这个节点
  45. public int delRightTreeMin(Node node)
  46. {
  47. Node target = node;
  48. while (target.left != null)
  49. {
  50. target = target.left;
  51. }
  52. //这是target就指向了最小的节点
  53. //删除这个最小节点,并且返回这个节点的值
  54. delNode(target.id);
  55. return target.id;
  56. }
  57. //删除节点
  58. public void delNode(int val)
  59. {
  60. if (root == null)
  61. return;
  62. else
  63. {
  64. //找要删除的节点target和它的父节点parent
  65. Node target = search(val);
  66. if (target == null)//都没有找到要删除的节点,下面的代码也不用执行了
  67. return;
  68. //如果这个二叉树只有一个节点
  69. if (root.left == null && root.right == null)
  70. {
  71. root = null;
  72. return;
  73. }
  74. Node parent = searchParent(val);
  75. //要删除的节点是叶子节点
  76. if (target.left == null && target.right == null)
  77. {
  78. if (parent.left == target)
  79. parent.left = null;
  80. if (parent.right == target)
  81. parent.right = null;
  82. }
  83. //如果要删除的节点有两棵子树
  84. else if (target.left != null && target.right != null)
  85. {
  86. int minval = delRightTreeMin(target.right);
  87. target.id = minval;
  88. }
  89. //要删除的节点只有一棵子树
  90. else
  91. {
  92. if(target.left!=null)//要删除的节点有左子树
  93. {
  94. if (parent != null)
  95. {
  96. if (parent.left == target)
  97. parent.left = target.left;
  98. if (parent.right == target)
  99. parent.right = target.left;
  100. }
  101. else
  102. root = target.left;
  103. }
  104. else//要删除的节点有右子树
  105. {
  106. if (parent != null)
  107. {
  108. if (parent.left == target)
  109. parent.left = target.right;
  110. if (parent.right == target)
  111. parent.right = target.right;
  112. }
  113. else
  114. root = target.right;
  115. }
  116. }
  117. }
  118. }
  119. }
  120. public class Node
  121. {
  122. public int id;
  123. public Node left;
  124. public Node right;
  125. public Node(int id)
  126. {
  127. this.id = id;
  128. }
  129. //返回以当前节点为根节点的树的高度
  130. public int height()
  131. {
  132. return Math.Max(left == null ? 0 : left.height(), right == null ? 0 : right.height())+1;//+1本身节点也要算一层
  133. }
  134. //返回左子树的高度
  135. public int leftHeight()
  136. {
  137. if (left == null)
  138. return 0;
  139. return left.height();
  140. }
  141. //返回右子树的高度
  142. public int rightHeight()
  143. {
  144. if (right == null)
  145. return 0;
  146. return right.height();
  147. }
  148. //左旋转
  149. public void leftRotate()
  150. {
  151. //创建新的节点,以当前节点的值
  152. Node newnode = new Node(this.id);
  153. //把新的节点的左子树设置成当前节点的左子树
  154. newnode.left = this.left;
  155. //把新的节点的右子树设置成当前节点的右子树的左子树
  156. newnode.right = this.right.left;
  157. //把当前节点的值替换成右子节点的值
  158. this.id = this.right.id;
  159. //把当前节点的右子树设置成右子树的右子树
  160. this.right = this.right.right;
  161. //把当前节点的左子树设置成新的节点
  162. this.left = newnode;
  163. }
  164. //右旋转
  165. public void rightRotate()
  166. {
  167. //创建一个新的节点,以当前节点的值
  168. Node newnode = new Node(this.id);
  169. //新节点的右节点设置为当前节点的右节点
  170. newnode.right = this.right;
  171. //新节点的左节点设置为当前节点的左节点的右节点
  172. newnode.left = this.left.right;
  173. //把当前节点的值改成左子节点的值
  174. this.id = this.left.id;
  175. //当前节点的左子树设置为左子树的左子树
  176. this.left = this.left.left;
  177. //将当前节点的右子节点设置成新的节点
  178. this.right = newnode;
  179. }
  180. //二叉排序树添加
  181. public void add(Node node)
  182. {
  183. if (node == null)
  184. return;
  185. //判断传入的节点的值,和当前子树的根节点的关系
  186. if (node.id < this.id)
  187. {
  188. //如果当前的左子节点为null
  189. if (this.left == null)
  190. this.left = node;
  191. else
  192. this.left.add(node);//递归向左子节点添加
  193. }
  194. else
  195. {
  196. if (this.right == null)
  197. this.right = node;
  198. else
  199. this.right.add(node);//递归向右节点添加
  200. }
  201. //当添加完成一个节点以后,如果:(右子树的高度-左子树的高度)>1,左旋转
  202. if (rightHeight() - leftHeight() > 1)
  203. {
  204. //如果它的右子树的左子树高度大于它的右子树的右子树高度
  205. if (right != null && right.leftHeight() > right.rightHeight())
  206. {
  207. //先对当前节点的右节点进行右旋转
  208. right.rightRotate();
  209. //再对当前节点进行左旋转
  210. leftRotate();
  211. }
  212. else
  213. {
  214. //直接进行左旋转
  215. leftRotate();
  216. }
  217. //因为是加一个数处理一个数,这里已经处理完了,树已经平衡了,就返回
  218. //不用执行下面的了没有意义
  219. return;
  220. }
  221. //当添加完一个节点后,如果:(左子树的高度-右子树的高度)>1,右旋转
  222. if (leftHeight() - rightHeight() > 1)
  223. {
  224. //如果它的左子树的右子树高度大于它的左子树的左子树高度
  225. if (left != null && left.rightHeight() > left.leftHeight())
  226. {
  227. //先对当前节点的左节点进行左旋转
  228. left.leftRotate();
  229. //再对当前节点进行右旋转
  230. rightRotate();
  231. }
  232. else
  233. {
  234. //直接进行右旋转
  235. rightRotate();
  236. }
  237. }
  238. }
  239. //中序遍历
  240. public void infixOrder()
  241. {
  242. //1.如果当前节点的左子节点不为空,则递归继续中序遍历
  243. if (this.left != null)
  244. this.left.infixOrder();
  245. //2.输出当前节点
  246. Console.WriteLine(this.id);
  247. //3.如果当前节点的右子节点不为空,则递归继续中序遍历
  248. if (this.right != null)
  249. this.right.infixOrder();
  250. }
  251. //查找节点
  252. public Node search(int val)
  253. {
  254. if (val == this.id)
  255. return this;
  256. else if (val < this.id)
  257. {
  258. if (this.left == null)
  259. return null;
  260. return this.left.search(val);
  261. }
  262. else
  263. {
  264. if (this.right == null)
  265. return null;
  266. return this.right.search(val);
  267. }
  268. }
  269. //查找要删除节点的父节点
  270. public Node searchParent(int val)
  271. {
  272. //如果当前节点就是要删除的节点的父节点,就返回
  273. if (this.left != null && this.left.id == val || this.right != null && this.right.id == val)
  274. return this;
  275. else
  276. {
  277. if (val < this.id && this.left != null)//左递归
  278. return this.left.searchParent(val);
  279. else if (val >= this.id && this.right != null)//右递归
  280. return this.right.searchParent(val);
  281. else//没有父节点
  282. return null;
  283. }
  284. }
  285. }
  286. }