coverPiccoverPic

【算法学习笔记 有序表 1】用 Rust 实现了一个 AVL 树

🎈前言

AVL 树就是平衡二叉搜索树。它的节点左右子树高度相差不超过 1,当超过 1 时,它通过左旋和右旋操作降低树高,保持这一性质,降低平均搜索长度。对 AVL 树而言,插入、查找、删除节点的时间复杂度都能维持在 O(logn),适用于处理有序的结构中的增删改查操作。

最近发现自己在做 Leetcode 的时候有点吃力,然后就闲来没事复习一下算法和数据结构,恰好在学 rust,这里我就用 rust 来实现一个 AVL 树。感觉用 rust 实现的难度主要在于它复杂的借用和所有权规则了,不过我也不太懂,这里就不多涉及了,把 AVL 树写出来就完事了。

说起来这种有序表的题目其实在 Leetcode 也挺少见的,这里找了个少见的例子,406 根据身高重建队列,虽然不用平衡树之类的解法在 O(n2)O(n^2) 复杂度下也能通过就是了。这是我的题解

🎇类型定义

除了基础的 AVL 树外,我希望实现一个 AVL 树,可以实现排名、节点前驱后继等进一步的 O(logn)O(logn) 时间复杂度(nn 是树中节点个数)的查询。这样子就需要记录重复值个数count和子树大小size。不妨认为没有子树的单独一个节点树高为 1。我们先来定义一下类型,使用Box记录树的节点:

rust
  1. #[derive(Debug)]
  2. struct AVLTreeNode {
  3. key: i32,
  4. height: i32,
  5. size: i32,
  6. count: i32,
  7. left: OptionBoxAVLTreeNode,
  8. right: OptionBoxAVLTreeNode,
  9. }
  10. type BoxAVLTreeNode = Box<AVLTreeNode>;
  11. type OptionBoxAVLTreeNode = Option<
  12. BoxAVLTreeNode
  13. >;
  14. struct AVLTree {
  15. root: OptionBoxAVLTreeNode,
  16. }

实现new和属性获取的方法:

rust
  1. impl AVLTreeNode {
  2. fn new(key: i32) -> Box<Self> {
  3. Box::new(Self {
  4. key,
  5. height: 1,
  6. size: 1,
  7. count: 1,
  8. left: None,
  9. right: None
  10. })
  11. }
  12. }
  13. impl AVLTree {
  14. fn new() -> Self {
  15. AVLTree { root: None }
  16. }
  17. fn size(node: &OptionBoxAVLTreeNode) -> i32 {
  18. match node {
  19. Some(node) => node.size,
  20. None => 0,
  21. }
  22. }
  23. fn count(node: &OptionBoxAVLTreeNode) -> i32 {
  24. match node {
  25. Some(node) => node.count,
  26. None => 0,
  27. }
  28. }
  29. fn height(node: &OptionBoxAVLTreeNode) -> i32 {
  30. match node {
  31. Some(node) => node.height,
  32. None => 0,
  33. }
  34. }
  35. fn key(node: &OptionBoxAVLTreeNode) -> Option<i32> {
  36. match node {
  37. Some(node) => Some(node.key),
  38. None => None,
  39. }
  40. }
  41. }

🎆节点更新

当插入、删除、旋转操作发生后,节点或者其子节点发生变化时,需要递归向上更新节点的heightsizecount这些值,定义一个update函数。节点的height为子树height最大值 + 1,size为子树size之和再加上自身的数量count

rust
  1. fn update(node: &mut BoxAVLTreeNode) {
  2. let left_height = Self::height(&node.left);
  3. let right_height = Self::height(&node.right);
  4. let left_size = Self::size(&node.left);
  5. let right_size = Self::size(&node.right);
  6. let self_count = node.count;
  7. node.height = std::cmp::max(left_height, right_height) + 1;
  8. node.size = left_size + right_size + self_count;
  9. }

✨旋转操作

当插入和删除操作发生后,节点左右子树高度差大于 1,不平衡的时候,树需要进行旋转操作。操作分为两种,左旋和右旋。往哪边旋转,根节点就往哪边倒。

👉右旋

这里节点 5 的左树高度是 2,右树高 1,这时候给它插入一个 1,左树高 3,根节点 5 不平衡,进行一次右旋,根节点往右倒,根节点的左子树变成了其原左节点的右子树(LL)。

这里把旋转后的子树根节点返回出去,用来给上层的节点调整子树指针。

rust
  1. fn right_rotate(mut node: BoxAVLTreeNode) -> BoxAVLTreeNode {
  2. let mut child = node.left.unwrap();
  3. node.left = child.right;
  4. Self::update(&mut node);
  5. child.right = Some(node);
  6. Self::update(&mut child);
  7. child
  8. }
  9. fn right_rotate_option(node: OptionBoxAVLTreeNode) -> OptionBoxAVLTreeNode {
  10. match node {
  11. Some(node) => {
  12. Some(Self::right_rotate(node))
  13. },
  14. None => node
  15. }
  16. }

👈左旋

左旋也类似,根节点往左倒,根节点的右子树是原右节点的左子树(RR):

rust
  1. fn left_rotate(mut node: BoxAVLTreeNode) -> BoxAVLTreeNode {
  2. let mut child: BoxAVLTreeNode = node.right.unwrap();
  3. node.right = child.left;
  4. Self::update(&mut node);
  5. child.left = Some(node);
  6. Self::update(&mut child);
  7. child
  8. }
  9. fn left_rotate_option(node: OptionBoxAVLTreeNode) -> OptionBoxAVLTreeNode {
  10. match node {
  11. Some(node) => {
  12. Some(Self::left_rotate(node))
  13. },
  14. None => node
  15. }
  16. }

💢四种旋转情况

AVL 树不平衡有四种情况,分别是 LL、LR、RR、RL。右旋和左旋的图示分别是 LL、RR 的情况。LL 就是指左节点的左子树高大于等于左节点的右子树高,只需要一次右旋。LR 则是左节点左子树高小于它右子树高,这需要左节点先进行一次左旋,变成 LL 的样子,根节点再进行一次右旋。

RR 和 RL 则和 LL 和 LR 是对称的,这里就不详述了。来看代码:

rust
  1. fn balance_factor_option(node: &OptionBoxAVLTreeNode) -> i32 {
  2. match node {
  3. None => 0,
  4. Some(node) => {
  5. Self::balance_factor(node)
  6. }
  7. }
  8. }
  9. fn balance_factor(node: &BoxAVLTreeNode) -> i32 {
  10. Self::height(&node.left) - Self::height(&node.right)
  11. }
  12. fn rotate(node: BoxAVLTreeNode) -> BoxAVLTreeNode {
  13. let balance_factor = Self::balance_factor(&node);
  14. if balance_factor > 1 {
  15. let mut node = node;
  16. if Self::balance_factor_option(&node.left) >= 0 {
  17. Self::right_rotate(node)
  18. } else {
  19. let left = node.left;
  20. node.left = Self::left_rotate_option(left);
  21. Self::right_rotate(node)
  22. }
  23. }
  24. else if balance_factor < -1 {
  25. let mut node = node;
  26. if Self::balance_factor_option(&node.right) <= 0 {
  27. Self::left_rotate(node)
  28. } else {
  29. let right = node.right;
  30. node.right = Self::right_rotate_option(right);
  31. Self::left_rotate(node)
  32. }
  33. } else {
  34. node
  35. }
  36. }

🔍搜索

简单地遍历二叉搜索树就可以了。如果查找的key小于当前的就向左查找,大于向右查找。

rust
  1. fn search(&self, key: i32) -> &OptionBoxAVLTreeNode {
  2. let mut cur = &self.root;
  3. while let Some(current) = cur {
  4. match current.key.cmp(&key) {
  5. Ordering::Less => {
  6. cur = &current.right;
  7. }
  8. Ordering::Greater => {
  9. cur = &current.left;
  10. }
  11. Ordering::Equal => {
  12. break;
  13. }
  14. }
  15. }
  16. cur
  17. }

➕插入

插入就是二叉搜索树的插入,再加上平衡树的检查,就不多说了。如果插入的key小于当前的就向左查找,大于向右查找,遍历到空节点就创建一个新节点。如果要插入的key等于当前节点则使当前节点count += 1

rust
  1. fn insert(&mut self, key: i32) {
  2. self.root = Self::insert_helper(self.root.take(), key);
  3. }
  4. fn insert_helper(node: OptionBoxAVLTreeNode, key: i32) -> OptionBoxAVLTreeNode {
  5. match node {
  6. Some(mut node) => {
  7. match {
  8. let node_val = node.key;
  9. node_val
  10. }
  11. .cmp(&key)
  12. {
  13. Ordering::Greater => {
  14. node.left = Self::insert_helper(node.left, key);
  15. }
  16. Ordering::Less => {
  17. node.right = Self::insert_helper(node.right, key);
  18. }
  19. Ordering::Equal => {
  20. node.count += 1;
  21. }
  22. }
  23. Self::update(&mut node);
  24. Some(Self::rotate(node))
  25. },
  26. None => {
  27. Some(AVLTreeNode::new(key))
  28. }
  29. }
  30. }

❌删除

删除也一样遍历二叉搜索树,找到待删除节点时有 4 种情况:

  1. 待删除节点count > 1,则count -= 1
  2. 否则如果待删除节点左右有一个为空,则用非空节点代替待删除节点;
  3. 如果左右节点都空,直接删除当前待删除节点;
  4. 如果左右都不空,则找待删除节点右子树最左节点代替待删除节点。

这里我返回一个布尔值表示待删除节点是否存在。使用了查找节点后继的操作来寻找情况 4 的右子树最左节点。

rust
  1. fn remove(&mut self, val: i32) -> bool {
  2. let ans = Self::remove_helper(self.root.take(), val, false);
  3. self.root = ans.0;
  4. ans.1
  5. }
  6. fn remove_helper(node: OptionBoxAVLTreeNode, key: i32, reomve_node: bool) -> (OptionBoxAVLTreeNode, bool) {
  7. match node {
  8. Some(mut node) => {
  9. let mut match_key = false;
  10. if key < node.key {
  11. let ans = Self::remove_helper(node.left, key, reomve_node);
  12. node.left = ans.0;
  13. match_key = ans.1
  14. } else if key > node.key {
  15. let ans = Self::remove_helper(node.right, key, reomve_node);
  16. node.right = ans.0;
  17. match_key = ans.1;
  18. } else {
  19. match_key = true;
  20. if !reomve_node && node.count > 1 {
  21. node.count -= 1;
  22. } else if node.left.is_none() || node.right.is_none() {
  23. let child = if node.left.is_some() {
  24. node.left
  25. } else {
  26. node.right
  27. };
  28. match child {
  29. None => {
  30. return (None, match_key);
  31. }
  32. Some(child) => node = child,
  33. }
  34. } else {
  35. let right = node.right.take().unwrap();
  36. let (next_key, next_count) = Self::next_helper(&right, node.key);
  37. node.right = Some(right);
  38. let ans = Self::remove_helper(node.right, next_key, true).0;
  39. node.right = ans;
  40. node.key = next_key;
  41. node.count = next_count;
  42. }
  43. }
  44. Self::update(&mut node);
  45. node = Self::rotate(node);
  46. (Some(node), match_key)
  47. }
  48. None => (None, false),
  49. }
  50. }
  51. fn next_helper_option(node: &OptionBoxAVLTreeNode, key: i32) -> (i32, i32) {
  52. match node {
  53. Some(cur) => {
  54. Self::next_helper(cur, key)
  55. },
  56. None => (i32::MAX, 1)
  57. }
  58. }
  59. fn next_helper(node: &BoxAVLTreeNode, key: i32) -> (i32, i32) {
  60. let cur_key = node.key;
  61. if cur_key <= key {
  62. Self::next_helper_option(&node.right, key)
  63. } else {
  64. let ans = Self::next_helper_option(&node.left, key);
  65. if cur_key <= ans.0 {
  66. (cur_key, node.count)
  67. } else {
  68. ans
  69. }
  70. }
  71. }

🔗查询前驱后继

查找节点前一个值的节点和后一个值的节点,也就是找到小于其的最大值和大于其的最小值,这是一个常见的查询操作。这里的后继查询next_helper,返回了keycount,主要是方便上面的删除操作。在 AVL 树上只需 O(logn)O(logn) 的时间复杂度。

rust
  1. fn pre(&self, key: i32) -> i32 {
  2. Self::pre_helper(&self.root, key)
  3. }
  4. fn pre_helper(node: &OptionBoxAVLTreeNode, key: i32) -> i32 {
  5. match node {
  6. Some(cur) => {
  7. let cur_key = cur.key;
  8. if cur_key >= key {
  9. Self::pre_helper(&cur.left, key)
  10. } else {
  11. cur_key.max(Self::pre_helper(&cur.right, key))
  12. }
  13. },
  14. None => i32::MIN
  15. }
  16. }
  17. fn next(&self, key: i32) -> i32 {
  18. Self::next_helper_option(&self.root, key).0
  19. }
  20. fn next_helper_option(node: &OptionBoxAVLTreeNode, key: i32) -> (i32, i32) {
  21. match node {
  22. Some(cur) => {
  23. Self::next_helper(cur, key)
  24. },
  25. None => (i32::MAX, 1)
  26. }
  27. }
  28. fn next_helper(node: &BoxAVLTreeNode, key: i32) -> (i32, i32) {
  29. let cur_key = node.key;
  30. if cur_key <= key {
  31. Self::next_helper_option(&node.right, key)
  32. } else {
  33. let ans = Self::next_helper_option(&node.left, key);
  34. if cur_key <= ans.0 {
  35. (cur_key, node.count)
  36. } else {
  37. ans
  38. }
  39. }
  40. }

🏅获取排序和根据排序查询

通过rank函数可以查询到节点排序(从 1 开始)。根据排序查询对应的节点,这也是经典的第 k 大的算法题了,在二叉搜索树上已经排序了,直接找到对应排序的节点即可。在 AVL 树上也只需 O(logn)O(logn) 的时间复杂度。

rust
  1. fn rank(&self, key: i32) -> i32 {
  2. Self::rank_helper(&self.root, key) + 1
  3. }
  4. fn rank_helper(node: &OptionBoxAVLTreeNode, key: i32) -> i32 {
  5. match node {
  6. Some(cur) => {
  7. if cur.key >= key {
  8. Self::rank_helper(&cur.left, key)
  9. } else {
  10. Self::size(&cur.left) + cur.count + Self::rank_helper(&cur.right, key)
  11. }
  12. },
  13. None => 0
  14. }
  15. }
  16. fn index(&self, idx: i32) -> i32 {
  17. Self::index_helper(&self.root, idx).unwrap()
  18. }
  19. fn index_helper(node: &OptionBoxAVLTreeNode, idx: i32) -> Option<i32> {
  20. match node {
  21. Some(cur) => {
  22. let left = &cur.left;
  23. let left_size = Self::size(&left);
  24. let cur_count = cur.count;
  25. if left_size >= idx {
  26. let ans = Self::index_helper(&left, idx);
  27. if !ans.is_none() { ans } else { Some(i32::MIN) }
  28. } else if left_size + cur_count < idx {
  29. let right = &cur.right;
  30. let ans = Self::index_helper(&right, idx - left_size - cur_count);
  31. if !ans.is_none() { ans } else { Some(i32::MAX) }
  32. } else {
  33. Some(cur.key)
  34. }
  35. },
  36. None => None
  37. }
  38. }

🌈结语

这篇博客用 rust 实现了一个 AVL 树,并且实现了对排序、前驱后继节点查询的操作。

参考:
【算法讲解148【扩展】有序表专题1-AVL树】 https://www.bilibili.com/video/BV1h5yXYJE6t/?share_source=copy_web&vd_source=a06db201a5f7fab5fe54f12bff164f84
【Hello 算法】 https://www.hello-algo.com/chapter_tree/avl_tree/#754-avl

0 条评论未登录用户
Ctrl or + Enter 评论
🌸 Run