平衡树的入门

平衡二叉搜索树的入门


前言

更新:忘了说这篇文章正施工中了。

更新:今天下午才知道我介绍的是HAVL,不是正统AVL……(不过HAVL更优秀啦

若发现本文有错误或不当之处, 希望能指出, 谢谢。

当年, 我还没有把二叉搜索树学好, 就会了平衡树……

本文的目的不是研究各种原理。

本文适合会使用二叉搜索树且想初步了解一下平衡树的OIer

本文以AVL树的维持平衡方式的讲解为主要内容。

平衡树是一类在恶意数据下, 树高仍然保持优秀的二叉搜索树。
对于平衡树,我觉得可以称它们为小脑发达的二叉搜索树, 而不必觉得它们很

平衡树什么时候需要“平衡”(rebalance)?当搜索树的结构改变的时候。
搜索树的结构什么时候会改变呢?当对它进行插入、删除操作的时候。

由此,为简化文本复杂度,本文只讨论插入时的平衡维护。


正文

树高

AVL用这个数据判断自己的平衡程度。

我的计算公式:
height of Node is max(height of Node's left child, height of Node's right child) + 1

就当给知识不牢固的人复习一下。


平衡因子(Balance Factor)

左右子树的高度差。

一颗AVL树的任一节点的平衡因子的绝对值都不会超过1。

定义一种程序中普适的平衡因子的计算方法(如右子树高减左子树高),
不但可以判断树是否失衡, 还能判断哪个子树偏高。


旋转(rotate)操作

基础旋转操作包括:左旋、 右旋。

它们用于解决简单的失衡情况。

如果情况复杂,就要进行组合旋转操作。


一些简单却重要的事实

1.一个节点的插入过程中, 会经过一些节点, 且这个节点的插入所导致的
树高变化只可能会波及到它们(注意是可能)。

2.由于递归的性质,检查一个子树的平衡性时, 它的两个子树都是平衡的。


举例实现

1.节点的定义

struct Node{
    int val, cnt, height;//val 代表节点值, cnt代表节点的创建次数
    Node *lc, *rc;//即 left child, right child
    Node(int val) {
        this->val = val;
        cnt = height = 1;
        lc = rc = NULL;
    }//构造函数
}*root;

2.插入函数的编写

展示插入函数之前, 先来简单了解一下它的逻辑, 不要管实现细节, 后文会说。

为了保持二叉搜索树的性质, 寻址、插入操作自然和二叉搜索树一样。

插入之后?

插入之后, 树的结构就改变了, 就要进行自平衡了。

这是插入函数

//插入一个权为x节点
Node* insert(Node* u, int x) {
    if(u == NULL) return new Node(x);
    
    if(u->val == x) u->cnt++;
    else {
        if(u->val < x) u->rc = insert(u->rc, x);
        else u->lc = insert(u->lc, x);
    }
    
    u->height = max(get_h(u->lc), get_h(u->rc)) + 1;//标记
    
    rebalance(u);//标记
    
    return u;
}

在主函数里是这样调用的root = insert(root, val)

3.插入函数中涉及到的函数

在插入函数中标好了, 分别是get_hrebalance

get_h
用于快速判断树高,用此函数的原因是指向NULL的指针不方便进行操作。
然而用数组就没有这么麻烦。

//返回节点u的树高
inline int get_h(Node* u) {
    if(u == NULL) return 0;
    return u->height;
}

rebalance
通过旋转使树达到一种严格平衡的状态。

旋转的细节后续会补上, 网上都有吧?

函数体

// > 1 为左偏 
Node* rebalance(Node* u) {
    int f = getfactor(u);
    // case 1 
    if(f > 1 && getfactor(u->lc) > 0) {
        return rotateRight(u);
    }
    // case 2
    if(f < -1 && getfactor(u->rc) < 0) {
        return rotateLeft(u);
    }
    // case 3
    if(f > 1 && getfactor(u->lc) < 0) {
        u->lc = rotateLeft(u->lc);
        return rotateRight(u);
    }
    // case 4
    if(f < -1 && getfactor(u->rc) > 0) {
        u->rc = rotateRight(u->rc);
        return rotateLeft(u);
    }
    
    return u;
}

//简化版:
Node* ReBalance(Node* u) {
    int f = Get_Factor(u);
    // > 1  Left Greater

    if(f > 1) {
        if(Get_Factor(u->lc) < 0) u->lc = LeftRotate(u->lc);
        return RightRotate(u);
    }

    if(f < -1) {
        if(Get_Factor(u->rc) > 0) u->rc = RightRotate(u->rc);
        return LeftRotate(u);
    }

    return u;
}

4.rebalance涉及到的函数

rotateRight 右旋

Node* rotateRight(Node* x) {
    Node* y = x->lc;
    x->lc = y->rc;
    y->rc = x;
    
    x->height = max(get_h(x->lc), get_h(x->rc)) + 1;
    y->height = max(get_h(y->lc), get_h(y->rc)) + 1;
    
    return y;
}

rotateLeft 左旋

Node* rotateLeft(Node* x) {
    Node* y = x->rc;
    x->rc = y->lc;
    y->lc = x;
    
    x->height = max(get_h(x->lc), get_h(x->rc)) + 1;
    y->height = max(get_h(y->lc), get_h(y->rc)) + 1;
    
    return y;
}

getfactor 获取一个节点的平衡因子,本题中,平衡因子以左树高减右树高计算。

inline int getfactor(Node* u) {
    return get_h(u->lc) - get_h(u->rc);
}

效果测试

前言
树高 = 效果


源码

#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;

struct Node{
    int val, cnt, height;
    Node *lc, *rc;
    Node(int val) {
        this->val = val;
        cnt = height = 1;
        lc = rc = NULL;
    }
}*root;

inline int get_h(Node* u) {
    if(u == NULL) return 0;
    return u->height;
}

inline int getfactor(Node* u) {
    return get_h(u->lc) - get_h(u->rc);
}

Node* rotateRight(Node* x) {
    Node* y = x->lc;
    x->lc = y->rc;
    y->rc = x;
    
    x->height = max(get_h(x->lc), get_h(x->rc)) + 1;
    y->height = max(get_h(y->lc), get_h(y->rc)) + 1;
    
    return y;
}

Node* rotateLeft(Node* x) {
    Node* y = x->rc;
    x->rc = y->lc;
    y->lc = x;
    
    x->height = max(get_h(x->lc), get_h(x->rc)) + 1;
    y->height = max(get_h(y->lc), get_h(y->rc)) + 1;
    
    return y;
}
// > 1 为左偏 

Node* rebalance(Node* u) {
    int f = getfactor(u);
    // case 1 
    if(f > 1 && getfactor(u->lc) > 0) {
        return rotateRight(u);
    }
    // case 2
    if(f < -1 && getfactor(u->rc) < 0) {
        return rotateLeft(u);
    }
    // case 3
    if(f > 1 && getfactor(u->lc) < 0) {
        u->lc = rotateLeft(u->lc);
        return rotateRight(u);
    }
    // case 4
    if(f < -1 && getfactor(u->rc) > 0) {
        u->rc = rotateRight(u->rc);
        return rotateLeft(u);
    }
    
    return u;
}

Node* insert(Node* u, int x) {
    if(u == NULL) return new Node(x);
    
    if(u->val == x) u->cnt++;
    else {
        if(u->val < x) u->rc = insert(u->rc, x);
        else u->lc = insert(u->lc, x);
    }
    
    u->height = max(get_h(u->lc), get_h(u->rc)) + 1;
    
    u = rebalance(u);
    
    return u;
}

int main() {
    
    int n, val;
    
    freopen("test.txt", "r", stdin);
    
    scanf("%d", &n);
    
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &val);
        root = insert(root, val);
    }
    
    cout << root->height;
    
    fclose(stdin);
    
    return 0;
    
}

恶意数据

数据生成器:

#include<bits/stdc++.h>
using namespace std;
int main() {
    freopen("test.txt", "w", stdout);
    srand((unsigned)time(0));
    cout<<1000000<<'\n';
    for(int i = 1; i <= 1000000; ++i) cout << i << '\n';
    fclose(stdout);
    return 0;
}

输出结果为整棵树的高度。
输出结果: 20


随机数据

数据生成器:

#include<bits/stdc++.h>
using namespace std;
int main() {
    freopen("test.txt", "w", stdout);
    srand((unsigned)time(0));
    cout<<1000000<<'\n';
    for(int i = 1; i <= 1000000; ++i) cout << rand()%1000000 + 1 << '\n';
    fclose(stdout);
    return 0;
}

输出结果:
第一次:18
第二次:18
第三次:18
第四次:18
第五次:18
第六次:18
第七次:18
第八次:18
第九次:18
第十次:18

保证数据随机。

转载于:https://www.cnblogs.com/tztqwq/p/10912965.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值