1. 程式人生 > >資料結構----AVL平衡樹----AVL平衡樹的基本操作

資料結構----AVL平衡樹----AVL平衡樹的基本操作

一、二叉查詢樹

我們先來講講二叉查詢樹。

大家應該聽說過二分查詢吧,二分查詢是對一個有序序列的快速查詢,時間複雜度為O(log2(n)),

但是二分查詢也有它的缺點,當序列加入一個元素時,我們就需要對這個有序序列進行維護,要麼就用sort(),要麼就用插入排序(附帶大量的資料移動),時間複雜度就會陡然提升。

於是就有了一種新的資料結構:二叉查詢樹!

二叉查詢樹的運用比較靈活,支援插入O(log2(n))、查詢O(log2(n))、刪除和前驅後繼的查詢。

插入操作:

(1)將要插入的節點與根節點比較,如果沒有根節點,則把該節點作為根節點。

(2)如果插入的節點於根節點,就把該節點插入到子樹,並把

子樹的根節點作為比較物件。

(3)如果插入的節點於根節點,就把該節點插入到子樹,並把子樹的根節點作為比較物件。

(4)遞迴進行以上操作,直到達到葉子節點。

(5)將它插入到葉子節點的下方。

查詢操作:

(1)將它與根節點比較,如果比根節點小,就向左子樹查詢;如果比根節點大,就向右子樹查詢。

(2)如果到了葉子節點下方還沒有找到返回-1。

(3)如果找到了,就返回該節點的下表或值。

刪除操作:

(1)找到該節點a。(最重要的一步)

(2)在這個節點的左子樹裡找到最大的節點b(即前驅)。

(3)刪掉節點b,因為節點b沒有右兒子,所以就把節點b的左子樹的根節點來代替節點b。

(4)用節點b來代替節點a。

查詢前驅與後繼操作:

(1)找到這個節點a。

(2)a的前驅為它的子樹裡最的兒子。

(3)a的後繼為它的子樹裡最的兒子。

但是,二叉查詢樹也有它的缺點,因為當輸入資料是有序的時候(如:1 2 3 4 5 6),經過插入操作之後,建成的樹為這個樣子:


之後,所有的操作的時間複雜度都變成了O(n)了,怎麼處理這樣的情況呢?

於是就有了二叉查詢樹的升級版:AVL平衡樹!

二、AVL平衡樹

AVL平衡樹其實就是在每一個節點上面加了一個平衡因子h,h表示的是以這個節點為根節點的樹總深度,當一個節點的左右兒子的平衡因子的差大於1時,就會對此進行平衡化旋轉操作。

平衡樹的定義方法:(注意maxn大小最好為輸入資料數量n的4倍,即4n)

struct node{
    int lc,rc,h,v;
}tree[maxn];

平衡化旋轉大致分為4類

1、zig旋轉:

當平衡樹中插入的節點在第一個不平衡的節點的左子樹左子樹中,我們就要對平衡樹進行zig旋轉

程式碼:

int zig(int r)
{
    int t=tree[r].lc;
    tree[r].lc=tree[t].rc;
    tree[t].rc=r;
    tree[r].h=max(tree[tree[r].rc].h,tree[tree[r].lc].h)+1;
    tree[t].h=max(tree[tree[t].rc].h,tree[tree[t].lc].h)+1;
    return t;
}

2、zag旋轉:

當平衡樹中插入的節點在第一個不平衡的節點的右子樹右子樹中,我們就要對平衡樹進行zag旋轉

程式碼:

int zag(int r)
{
    int t=tree[r].rc;
    tree[r].rc=tree[t].lc;
    tree[t].lc=r;
    tree[r].h=max(tree[tree[r].rc].h,tree[tree[r].lc].h)+1;
    tree[t].h=max(tree[tree[t].rc].h,tree[tree[t].lc].h)+1;
    return t;
}

3、zigzag旋轉:

當平衡樹中插入的節點在第一個不平衡的節點的左子樹右子樹中,我們就要對平衡樹進行zigzag旋轉

程式碼:

int zigzag(int r)
{
    tree[r].rc=zig(tree[r].rc);
    return zag(r);
}

4、zagzig旋轉:

當平衡樹中插入的節點在第一個不平衡的節點的右子樹左子樹中,我們就要對平衡樹進行zagzig旋轉

程式碼:

int zagzig(int r)
{
    tree[r].lc=zag(tree[r].lc);
    return zig(r);
}

插入操作跟二叉查詢樹差不多,只不過要注意插入後的調整:
int insert(int x,int r)
{
    if(r==0){
        tree[++cnt].v=x;
        tree[cnt].h=1;
        return cnt;
    }
    if(x<tree[r].v){
        tree[r].lc=insert(x,tree[r].lc);
        if(tree[tree[r].lc].h==tree[tree[r].rc].h+2){
            if(x<tree[tree[r].lc].v) r=zig(r);
            else if(x>tree[tree[r].lc].v) r=zagzig(r);
        }
    }
    else if(x>tree[r].v){
        tree[r].rc=insert(x,tree[r].rc);
        if(tree[tree[r].rc].h==tree[tree[r].lc].h+2){
            if(x>tree[tree[r].rc].v) r=zag(r);
            else if(x<tree[tree[r].rc].v) r=zigzag(r);
        }
    }
    tree[r].h=max(tree[tree[r].lc].h,tree[tree[r].rc].h)+1;
    return r;
}

刪除操作的思路跟二叉查詢樹的思路是一樣的,但是調整整棵樹就比較麻煩了(因為一次旋轉不能達到平衡),所以說就要寫一個maintain()函式,在每次呼叫dele()的最後都要呼叫一下maintain()。

void maintain(int &r)
{
    if(tree[tree[r].lc].h==tree[tree[r].rc].h+2){
        int t=tree[r].lc;
        if(tree[tree[t].lc].h==tree[tree[r].rc].h+1)
           r=zig(r);
        else if(tree[tree[t].rc].h==tree[tree[r].rc].h+1){
            tree[r].lc=zag(tree[r].lc);
            r=zig(r);
        }
    }
    else if(tree[tree[r].lc].h+2==tree[tree[r].rc].h){
        int t=tree[r].rc;
        if(tree[tree[t].rc].h==tree[tree[r].lc].h+1)
            r=zag(r);
        else if(tree[tree[t].lc].h==tree[tree[r].lc].h+1){
            tree[r].rc=zig(tree[r].rc);
            r=zag(r);
        }
    }
    tree[r].h=max(tree[tree[r].lc].h,tree[tree[r].rc].h)+1;
}
int dele(int &r,int x)
{
    int tx;
    if(x==tree[r].v||(x<tree[r].v&&tree[r].lc==0)||(x>tree[r].v&&tree[r].rc==0)){
        if(tree[r].lc==0||tree[r].rc==0){
            tx=tree[r].v;
            r=tree[r].lc+tree[r].rc;
            return tx;
        }
        else
            tree[r].v=dele(tree[r].lc,x);
    }
    else{
        if(x<tree[r].v)
            tx=dele(tree[r].lc,x);
        else tx=dele(tree[r].rc,x);
    }
    maintain(r);
    return tx;
}

AVL仍然可以採用惰性刪除,對於要刪除的節點,只需要給它進行一個標記。平衡樹的高度仍然是log(N),這樣並不會降低效率,而刪除操作卻要快速的多。

如果遇到那些刪除過後還要恢復的節點,則惰性刪除更優,不需要額外佔用空間,將原來的節點恢復即可。

如果遇到那種允許節點重複的AVL,惰性刪除更可取,將刪除標記設為整型變數,表示該節點出現的數量即可。

查詢操作和二叉查詢樹也大致相同。以下程式碼是查詢一個數在序列中與其他數的最小的差值(即最接近的數與它的差)。

int find(int root,int x)
{
    if(root==0) return 1<<30;
    if(x==tree[root].v) return 0;
    if(x>tree[root].v)
        return min(x-tree[root].v,find(tree[root].rc,x));
    else
        return min(tree[root].v-x,find(tree[root].lc,x));
}

三、例題

營業額統計

題目描述

Tiger最近被公司升任為營業部經理,他上任後接受公司交給的第一項任務便是統計並分析公司成立以來的營業情況。 Tiger拿出了公司的賬本,賬本上記錄了公司成立以來每天的營業額。分析營業情況是一項相當複雜的工作。由於節假日,大減價或者是其他情況的時候,營業額會出現一定的波動,當然一定的波動是能夠接受的,但是在某些時候營業額突變得很高或是很低,這就證明公司此時的經營狀況出現了問題。經濟管理學上定義了一種最小波動值來衡量這種情況: 該天的最小波動值 = min {該天以前某一天的營業額 - 該天營業額} 當最小波動值越大時,就說明營業情況越不穩定。 而分析整個公司的從成立到現在營業情況是否穩定,只需要把每一天的最小波動值加起來就可以了。你的任務就是編寫一個程式幫助Tiger來計算這一個值。 第一天的最小波動值為第一天的營業額。 天數小於100000.

輸入

第一行為正整數 ,表示該公司從成立一直到現在的天數

接下來的n行每行有一個整數(一定有資料小於〇) ,表示第i天公司的營業額。

輸出

輸出檔案僅有一個正整數,即每一天的最小波動值之和。答案保證在int範圍內。

樣例輸入

6
5
1
2
5
4
6

樣例輸出

12

提示

結果說明:5+|1-5|+|2-1|+|5-5|+|4-5|+|6-5|=5+4+1+0+1+1=12


四、分析

一道典型的平衡樹題目。

思路:

(1)輸入一個數a。如果a為第一個,則sum+=a,進行步驟(3)。

(2)查詢最接近a的數b與a之差,即abs(a-b)。sum+=abs(a-b)。

(3)插入a。

程式碼:

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
struct node{
    int lc,rc,h,v;
}tree[100005];
int cnt;
int zig(int r)
{
    int t=tree[r].lc;
    tree[r].lc=tree[t].rc;
    tree[t].rc=r;
    tree[r].h=max(tree[tree[r].rc].h,tree[tree[r].lc].h)+1;
    tree[t].h=max(tree[tree[t].rc].h,tree[tree[t].lc].h)+1;
    return t;
}
int zag(int r)
{
    int t=tree[r].rc;
    tree[r].rc=tree[t].lc;
    tree[t].lc=r;
    tree[r].h=max(tree[tree[r].rc].h,tree[tree[r].lc].h)+1;
    tree[t].h=max(tree[tree[t].rc].h,tree[tree[t].lc].h)+1;
    return t;
}
int zagzig(int r)
{
    tree[r].lc=zag(tree[r].lc);
    return zig(r);
}
int zigzag(int r)
{
    tree[r].rc=zig(tree[r].rc);
    return zag(r);
}
int find(int root,int x)
{
    if(root==0) return 1<<30;
    if(x==tree[root].v) return 0;
    if(x>tree[root].v)
        return min(x-tree[root].v,find(tree[root].rc,x));
    else
        return min(tree[root].v-x,find(tree[root].lc,x));
}
int insert(int x,int r)
{
    if(r==0){
        tree[++cnt].v=x;
        tree[cnt].h=1;
        return cnt;
    }
    if(x<tree[r].v){
        tree[r].lc=insert(x,tree[r].lc);
        if(tree[tree[r].lc].h==tree[tree[r].rc].h+2){
            if(x<tree[tree[r].lc].v) r=zig(r);
            else if(x>tree[tree[r].lc].v) r=zagzig(r);
        }
    }
    else if(x>tree[r].v){
        tree[r].rc=insert(x,tree[r].rc);
        if(tree[tree[r].rc].h==tree[tree[r].lc].h+2){
            if(x>tree[tree[r].rc].v) r=zag(r);
            else if(x<tree[tree[r].rc].v) r=zigzag(r);
        }
    }
    tree[r].h=max(tree[tree[r].lc].h,tree[tree[r].rc].h)+1;
    return r;
}
int main()
{
    int n,i,x,sum=0,root=0,s;
    scanf("%d",&n);
    for(i=1;i<=n;i++){
        scanf("%d",&x);
        if(i==1){sum+=x;root=insert(x,root);continue;}
        s=find(root,x);
        if(s!=0){
            root=insert(x,root);
            sum+=abs(s);
        }
    }
    printf("%d",sum);
}