重温数据结构与算法之AVL树可视化

avl_tree

前言

​ 当连续并且有序的数据陆续插入一棵二叉搜索树( binary search tree )时,这时树就会退化为链表,查找、插入和删除都需要花费 O ( n ) O(n)O(n) 时间,这时二叉搜索树的优势就没有了,AVL 树的发明就是为了解决这种问题。

​ 在计算机科学中,AVL 树是一种自平衡的二叉搜索树。在 AVL 树中,任何节点的两个子树的高度最多相差一,如果在插入或删除时,它们相差大于一时,会进行自平衡以恢复此属性。在平均和最坏情况下,查找、插入和删除都需要 O ( l o g n ) O(log n)O(logn) 时间。

AVL 树是以两位前苏联发明者 Georgy Adelson-Velsky 和 Evgenii Landis 的名字命名的,他们在1962年的论文《信息组织算法》中发表了这棵树。

一、定义

​ 一棵 AVL 树的节点,首先是一个二叉搜索树的节点,这表示节点中必须有数据,左子树和右子树,并且数据必须是可比较的,因为二叉搜索树规定了,父节点的数据一定大于左子树,一定小于右子树。

​ 由于 AVL 树需要计算树的高度用于判断是否平衡,为了减少重复计算,所以节点中需要再保存一个高度(这里定义:叶子节点的高度为1)。而一般介绍 AVL 树的文章中都会有一个平衡因子( Balance Factor ),BF是左右子树的高度之差,这里定义

B F ( x ) = g e t H e i g h t ( l e f t ( x ) ) − g e t H e i g h t ( r i g h t ( x ) ) BF(x) = getHeight(left(x)) - getHeight(right(x))BF(x)=getHeight(left(x))getHeight(right(x))

x xx 为要计算平衡因子的节点,l e f t ( x ) left(x)left(x)x xx 的左子树,r i g h t ( x ) right(x)right(x)x xx 的右子树。有了高度值,很容易计算平衡因子,这里就不保存平衡因子了。

​ 为了方便理解,这里规定,AVL 树不存储重复值,这表示 AVL 树在插入时不会插入相同的数据。

​ 下面是 main.h ,定义了一些 AVL 树需要用到的数据结构和方法。

#include <stdio.h>
#include <stdlib.h>

#define MAX(A, B) (A > B ? A : B)

struct _treeNode {
    int                   data; 	// 节点存储的数据
    int                   height;	// 节点的高度
    struct _treeNode     *left;     // 节点的左子树
    struct _treeNode     *right;    // 节点的右子树
};

typedef struct _treeNode* treeNode;

struct _avlTree {
    treeNode root;	// 树的根节点
    int size;       // 树的节点个数
    int height;     // 树的高度
};
typedef struct _avlTree* avlTree;

void avlTreeInit(avlTree* tree);

void avlTreeInsert(avlTree tree, int val);
void avlTreeDelete(avlTree tree, int val);

treeNode avlTreeSearch(avlTree tree, int val);

treeNode maxNode(treeNode node);
treeNode minNode(treeNode node);

int getHeight(treeNode root);  // 获取节点的高度
void calHeight(treeNode root); // 计算节点的高度

int calBf(treeNode node); // 计算平衡因子

void printTree(avlTree tree);			//先序遍历
void printTreeByLevel(avlTree tree); 	//层次遍历

二、初始化

#include "main.h"

void avlTreeInit(avlTree* tree) {
    *tree = (avlTree) malloc(sizeof(struct _avlTree));
    (*tree)->root = NULL;
    (*tree)->size = 0;
    (*tree)->height = 0;
}

三、工具方法

int getHeight(treeNode root) {
    if (!root) return 0;
    return root->height;
}

void calHeight(treeNode root) {
    if (!root) return;
    root->height =  MAX(getHeight(root->left), getHeight(root->right)) + 1;
}

int calBf(treeNode node) {
    return getHeight(node->left) - getHeight(node->right);
}

treeNode maxNode(treeNode node) {
     treeNode max = node;
     while (node) {
        max = node;
        node = node->right;
     }
     return max;
}

treeNode minNode(treeNode node) {
     treeNode min = node;
     while (node) {
        min = node;
        node = node->left;
     }
     return min;
}

void printNode(treeNode node) {
    if (node) {
        printNode(node->left);
        printf("%d ", node->data);
        printNode(node->right);
    }
}

void printTree(avlTree tree) {
    printf("tree size:%d\n", tree->size);
    printNode(tree->root);
    printf("\n");
    
}

void printTreeByLevel(avlTree tree) {
    if (!tree || !tree->root) return;
    treeNode * arr = (treeNode *) malloc(tree->size * sizeof(treeNode));
    int left = 0, right = 1;
    int count = 0;
    arr[count++] = tree->root;
    while (left < right) {
        for (int i = left; i < right; i++) {
            printf("[val:%d, height:%d] ", arr[i]->data, arr[i]->height);
            if (arr[i]->left) {
                arr[count++] = arr[i]->left;
            }
            if (arr[i]->right) {
                arr[count++] = arr[i]->right;
            }
            left++;
        }
        right = count;
        printf("\n");
    }

}

四、自平衡(旋转)

​ 当插入或删除节点时,需要从插入的位置沿向根的路径回溯,检查各节点平衡因子。如果出现 BF 为2或-2的情况,这时就需要自平衡。AVL 树通过旋转(AVL 树的重点) 来实现自平衡。
旋转的类型可以根据旋转次数分为分为:单旋或双旋。单旋表示1次旋转,双旋表示2次旋转
总而言之,旋转一共有四种:LL、RR、LR、RL

  1. LL

    LL:LeftLeft,也称为"左左"。插入或删除一个节点后,向上回溯到某个节点(失衡节点)的BF为2,并且插入节点还位于失衡节点的左节点的左子树

    举个栗子:向下面的 AVL 树插入5后,节点9进行自平衡,LL旋转

    在这里插入图片描述

    treeNode rotateLL(treeNode node) {
        treeNode left = node->left;
    
        node->left = left->right;
        left->right = node;
        
        calHeight(node);
        calHeight(left);
        
        return left;
    }
    
  2. RR

    RR:RightRight,也称为"右右"。插入或删除一个节点后,向上回溯到某个节点(失衡节点)的BF为-2,并且插入节点还位于失衡节点的右节点的右子树

    举个栗子:向下面的 AVL 树插入6后,节点2进行自平衡,RR旋转

    avl_RR

    treeNode rotateRR(treeNode node) {
        treeNode right = node->right;
    
        node->right = right->left;
        right->left = node;
        
        calHeight(node);
        calHeight(right);
        
        return right;
    }
    
  3. LR

    LR, LeftRight,也称为"左右"。插入或删除一个节点后,向上回溯到某个节点(失衡节点)的BF为2, 并且插入节点还位于失衡节点的左节点的右子树。

    举个栗子:向下面的 AVL 树插入5后,节点7进行自平衡LR旋转,先将左节点4 RR旋转,再将自身LL旋转

    avl_LR

    treeNode rotateLR(treeNode node) {
        node->left = rotateRR(node->left);
        return rotateLL(node);
    }
    
  4. RL

    RL, RightLeft,也称为"右左"。插入或删除一个节点后,向上回溯到某个节点(失衡节点)的BF为-2, 并且插入节点还位于失衡节点的右节点的左子树。

    举个栗子:向下面的 AVL 树插入8后,节点7进行自平衡LR旋转,先将右节点12 LL旋转,再将自身RR旋转

    AVL_RL

    treeNode rotateRL(treeNode node) {
        node->right = rotateLL(node->right);
        return rotateRR(node);
    }
    

五、插入

下图以插入[3, 2, 1, 4, 5, 6, 7, 16, 15, 14, 13, 12, 11, 10, 8, 9]为例,演示 AVL 树插入过程

AVL_插入

treeNode avlTreeNodeInsert(avlTree tree, treeNode root, int val) {
    if (!root) {
        treeNode v = (treeNode)calloc(1, sizeof(struct _treeNode));
        v->data = val;
        v->height = 1;
        tree->size++;
        return v;
    }

    if (root->data > val) {
          root->left = avlTreeNodeInsert(tree, root->left, val);
          if (calBf(root) == 2) {
            if (root->left->data > val) {
                root = rotateLL(root);
            } else {
                root = rotateLR(root);
            }
          }
    } else if (root->data < val) {
          root->right = avlTreeNodeInsert(tree, root->right, val);
          if (calBf(root) == -2) {
            if (root->right->data < val) {
                root = rotateRR(root);
            } else {
                root = rotateRL(root);
            }
          }
    }
    calHeight(root);
    return root;
}

void avlTreeInsert(avlTree tree, int val) {
    treeNode n = avlTreeNodeInsert(tree, tree->root, val);
    tree->root = n;
    tree->height = getHeight(tree->root);
}

六、删除

​1. 如果删除节点是叶子节点,那很简单,直接删掉即可。
2. 如果删除节点只有左节点或有节点,那也很简单,将子节点替换为删除节点
3. 如果被删除节点有左右子节点,需要根据左右子树高度判断,左子树高就将左子树最大值跟被删节点调换,然后删除,右子树高的话就将右子树中最小值调换然后删除,这样做的话树仍是平衡的

AVL_删除

treeNode avlTreeNodeDelete(avlTree tree, treeNode root, int val) {
    if (root) {
        if (root->data > val) {
            root->left = avlTreeNodeDelete(tree, root->left, val);
            if (calBf(root) == -2) {
                if (getHeight(root->right->left) > getHeight(root->right->right)) {
                    root = rotateRL(root);
                } else {
                    root = rotateRR(root);
                }
            }

        } else if (root->data < val) {
            root->right = avlTreeNodeDelete(tree, root->right, val);
            if (calBf(root) == 2) {
                if (getHeight(root->left->left) > getHeight(root->left->right)) {
                    root = rotateLL(root);
                } else {
                    root = rotateLR(root);
                }
            }
        } else {

            if (root->left && root->right) {
                if (getHeight(root->left) > getHeight(root->right)){
                    treeNode max = maxNode(root->left);
                    root->data = max->data;
                    root->left = avlTreeNodeDelete(tree, root->left, max->data);
                } else{
                    treeNode min = minNode(root->right);
                    root->data = min->data;
                    root->right = avlTreeNodeDelete(tree, root->right, min->data);
                }
            }else {
                treeNode tmp = root;
                root = root->left ? root->left : root->right;
                free(tmp);
                tree->size--;
            }
        }
        calHeight(root);
    }
    return root;
}

void avlTreeDelete(avlTree tree, int val) {
    tree->root = avlTreeNodeDelete(tree, tree->root, val);
    tree->height = getHeight(tree->root);
}

七、测试

int main() {
    avlTree tree = NULL;
    avlTreeInit(&tree);
    int nums[] = {3, 2, 1, 4, 5, 6, 7, 16, 15, 14, 13, 12, 11, 10, 8, 9};
    int len = sizeof(nums) / sizeof(int);
    for (int i = 0; i < len; i++) {
        avlTreeInsert(tree, nums[i]);
        printf("树的高度%d,树的size:%d\n", tree->height, tree->size);
        printTree(tree);
        printTreeByLevel(tree);
        printf("\n");
    }

    printf("-------开始删除---------------------------------\n\n");
    for (int i = 0; i < len; i++) {
        avlTreeDelete(tree, nums[i]);
        printf("---------删除%d----树的高度:%d,树的size:%d---\n", nums[i], tree->height, tree->size);
        printTreeByLevel(tree);
        printf("--------------------------------------------\n\n");
    }
    printf("-------删除结束---------------------------------\n\n");

    return 0;
}

八、javascript 实现

function AvlTree() {
    this.root = null;
    this.size = 0;
    this.height = 0;
}

function Node(element) {
    this.element = element;
    this.height = 1;
    this.left = null;
    this.right = null;  
}

function getHeight(node) {
    if (node === null) {
        return 0;
    }
    return node.height;
}

function calHeight(node) {
    if (node === null) {
        return;
    }
    node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
}

function calBf(node) {
    return getHeight(node.left) - getHeight(node.right);
}


AvlTree.prototype._rotateLL = function(node) {
    var l = node.left;
    var lr = l.right;

    l.right = node;
    node.left = lr;

    calHeight(node);
    calHeight(l);

    return l;
};

AvlTree.prototype._rotateRR = function(node) {
    var r = node.right;
    var rl = r.left;

    r.left = node;
    node.right = rl;

    calHeight(node);
    calHeight(r);
   
    return r;
};

AvlTree.prototype._rotateLR = function(node) {
    node.left = this._rotateRR(node.left);
    return this._rotateLL(node);
};

AvlTree.prototype._rotateRL = function(node) {
    node.right = this._rotateLL(node.right);
    return this._rotateRR(node);
};

AvlTree.prototype.insert = function(element) {
    this.root = this._insert(element, this.root);
    this.height = getHeight(this.root);
};

AvlTree.prototype._insert = function(element, node) {
    if (node === null) {
        this.size++;
        return new Node(element, parent);
    }

    if (node.element > element) {
        node.left = this._insert(element, node.left);
        if (calBf(node) == 2) {
            if (node.left.element > element) {
                node = this._rotateLL(node);
            } else {
                node = this._rotateLR(node);
            }
        }
    } else if (node.element < element) {
        node.right = this._insert(element, node.right);
        if (calBf(node) == -2) {
            if (node.right.element < element) {
                node = this._rotateRR(node);
            } else {
                node = this._rotateRL(node);
            }
        }
    }
    calHeight(node);
    return node;

};

AvlTree.prototype.delete = function(element) {
    if (this.root !== null) {
        this.root = this._delete(element, this.root);
    }
};

AvlTree.prototype._delete = function(element, node) {
    if (node != null) {
        if (node.element > element) {
            node.left = this._delete(element, node.left);
            if (calBf(node) == -2) {
                if (height(node.right.left) > height(node.right.right)) {
                    node = this._rotateRL(node);
                } else {
                    node = this._rotateRR(node);
                }
            }

        } else if (node.element < element) {
            node.right = this._delete(element, node.right);
            if (calBf(node) == 2) {
                if (getHeight(node.left.left) > getHeight(node.left.right)) {
                    node = this._rotateLL(node);
                } else {
                    node = this._rotateLR(node);
                }
            }
        } else {

            if (node.left != null && node.right != null) {
                if (getHeight(node.left) > getHeight(node.right)) {
                    max = this.maxNode(node.left);
                    node.element = max.element;
                    node.left = this._delete(max.element, node.left);
                } else {
                    min = this.minNode(node.right);
                    node.element = min.element;
                    node.right = this._delete(min.element, node.right);
                }
            } else {
                node = node.left != null ? node.left : node.right;
                this.size--;
            }

        }
    }
    return node;
};

AvlTree.prototype.minNode = function(node) {
    if (node.left === null) {
        return node;
    }
    return this.minNode(node.left);
};

AvlTree.prototype.maxNode = function(node) {
    if (node.right === null) {
        return node;
    }
    return this.maxNode(node.right);
};

AvlTree.prototype.forEach = function(func) {
    this._forEach(this.root, func);
};

AvlTree.prototype._forEach = function(node, func) {

    if (node !== null) {
        this._forEach(node.left, func);
        func(node);
        this._forEach(node.right, func);
    }
};

参考

  1. AVL树(一)之 图文解析 和 C语言的实现
  2. https://vilyapilya.github.io/AVLTreeVisualization/
  3. https://en.wikipedia.org/wiki/AVL_tree
  4. http://www.rmboot.com/AVLtree.html

版权声明:本文为qq_23091073原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。