码迷,mamicode.com
首页 > 其他好文 > 详细

平衡树

时间:2017-05-01 22:27:45      阅读:309      评论:0      收藏:0      [点我收藏+]

标签:基本操作   img   树的高度   删除   二叉堆   limit   常用   需要   nes   

首先,我们回顾一下二叉查找树(binary search tree, BST)。

二叉查找树具有下列性质:

  1. 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  2. 若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  3. 左、右子树也分别为二叉查找树;

BST 的插入

首先执行查找算法,找出被插结点的父亲结点。

若 key 值比当前结点小,则进入左儿子。

若 key 值比当前结点大,则进入右儿子。

//在二叉查找树中插入查找关键字key
void InsertBST(t,key) {
    if(t == NULL) {
        t = newBiTree;
        t->lchild = t->rchild=NULL;
        t->data = key;
        return;
    }
    if(key < t->data)
        InsertBST(t->lchild, key);
    else
        InsertBST(t->rchild, key);
}

 

最坏情况下,当先后插入的关键字有序时,二叉查找树会退化成链,树的深度为其平均查找长度 (n+1)/2?? 和(顺序查找相同),

最好的情况是二叉查找树的形态和折半查找的判定树相同,其平均查找长度和 logn 成正比。

 

优化:平衡树

常用的平衡二叉树有红黑树、AVL、Treap、Splay、SBT 等。

平衡二叉树的定义:它是一棵空树或它的左右两个子树的高度差满足一定限制以确保树的高度为 O(logn) 量级的

(如在 AVL 树中,确保左右子树高度的绝对值不超过 1),并且左右两个子树都是一棵平衡二叉树的二叉查找树。

 

树堆:Treap

相对于其他的平衡树,Treap 的特点是实现简单,且能基本实现随机平衡的结构。

Treap 是一棵二叉查找树,它的左子树和右子树分别是一个 Treap,

和一般的二叉查找树不同的是,Treap 记录额外的值——优先级。

Treap 以关键字构成二叉查找树的同时,优先级还需要满足堆的部分性质(结点的优先级大于该结点的孩子的优先级)。

注意二叉堆一定是完全二叉树,而 Treap 未必是。

Treap 的数据结构定义如下:

const int MAX_NODE = 10000;  // 最多 10000 个结点,根据题目的数据范围自行调整
struct treap {
    int v;  // 关键字
    int w;  // 同关键字的元素个数
    int rnd;  // 随机生成的优先级
    int size;  // 当前子树内的结点总数
    int l;  // 左孩子下标
    int r;  // 右孩子下标
} tr[MAX_NODE];

 

a) 旋转

旋转是基于随机产生的优先级来维护堆的性质,进而调整了 Treap 的形态。一共有两种旋转:左旋和右旋。

void rturn(int &k) {
    int t = tr[k].l;
    tr[k].l = tr[t].r;
    tr[t].r = k;
    k = t;
}

void lturn(int &k) {
    int t = tr[k].r;
    tr[k].r = tr[t].l;
    tr[t].l = k;
    k = t;
}

 

b) 插入

给结点随机分配一个优先级,先把要插入的点插入到一个叶子上,然后跟维护堆一样,自底向上调整。

若当前结点的优先级比根大就旋转,其中,若它是父亲的左儿子就右转,若它是父亲的右儿子就左转。

最多进行 h 次(h 是树的高度)旋转,插入的复杂度是 O(logn) 的。在期望意义下,单次操作的复杂度是 O(logn)。

void insert(int &k, int x) {
    if (k == 0) {
        size++;
        k = size;
        tr[k].size = tr[k].w = 1;
        tr[k].v = x;
        tr[k].rnd = rand();
        return;
    }
    tr[k].size++;
    if (tr[k].v == x)
        tr[k].w++;  // 每个结点顺便记录下与该结点相同值的数的个数
    else if (x > tr[k].v) {
        insert(tr[k].r, x);
        if (tr[tr[k].r].rnd < tr[k].rnd)
            lturn(k);  // 维护堆性质
    } else {
        insert(tr[k].l, x);
        if (tr[tr[k].l].rnd < tr[k].rnd)
            rturn(k);  // 维护堆性质
    }
}
 

在使用 Treap 时,应该在外面通过如下的方法进行初始化和插入操作:

int root = 0;
insert(root, 1);  // 这次插入后,root 被修改为 1
insert(root, 2);  // 之后 root 都是 1
insert(root, 3);  // 不过每次 insert 时的代码都一样,用起来没有任何区别

 

c) 删除

只需要该元素自顶向下调整,与优先级较大的儿子交换,直到叶子结点,直接删除。这样,该树仍然保持树堆的性质。在

期望意义下,删除最多进行 O(logn) 次旋转,时间复杂度依然是 O(logn) 的。

 
void del(int &k, int x) {  // 删除关键字 x
    if (k == 0)
        return;
    if (tr[k].v == x) {
        if (tr[k].w > 1) {  // 若关键字 x 有多个,只删去一个
            tr[k].w--;
            tr[k].size--;
            return;
        }
        if (tr[k].l * tr[k].r == 0)  // 有一个儿子为空
            k = tr[k].l + tr[k].r;
        else if (tr[tr[k].l].rnd < tr[tr[k].r].rnd)
            rturn(k), del(k, x);
        else
            lturn(k), del(k, x);
    }
    else if(x > tr[k].v)
        tr[k].size--, del(tr[k].r , x);
    else
        tr[k].size--, del(tr[k].l, x);
}
del(root,
3); // 删除结点值为 3 的元素

 

d) 查询

和一般的二叉查找树一样,但是由于 Treap 的随机化结构,可以证明 Treap 中查找的期望复杂度是 O(logn)。

另外,基于上述基本操作,我们还可以支持查询指定关键字的排名、查询第 k 大、查询前驱后继等操作,时间复杂度均为 O(logn)。

 

技术分享

技术分享

技术分享

 

平衡树

标签:基本操作   img   树的高度   删除   二叉堆   limit   常用   需要   nes   

原文地址:http://www.cnblogs.com/wangkaipeng/p/6792742.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!