1. 程式人生 > >數據結構——k-d樹

數據結構——k-d樹

不能 pla 偶數 應該 isp 替罪羊樹 它的 recycle his

一直以為k-d樹是一種高級的數據結構,和LCT可以並列;不過實際上沒那麽厲害。

k-d樹解決的是k維空間裏的點的範圍查找問題。k維空間必須滿足兩點之間的距離是歐幾裏德距離,比如二維的話,A(x1,y1)B(x2,y2)的距離就是√(x1-x2)2+(y1-y2)2

k-d樹是一顆二叉搜索樹,只不過每個節點的鍵值不唯一了,都有k個鍵值。k-d樹必須解決如何在這種情況下查找的問題。方法比較簡單:對k-d樹的每一層預先分配一下依賴哪個鍵值。比如2-d樹維護平面直角坐標系的點,我們可以讓第一層依賴於橫坐標,那麽橫坐標小於根節點的橫坐標的點都在左子樹,大於根節點的橫坐標的點都在右子樹。

如果這一層分配好了依賴的鍵值,那麽在這一層查找時暫時不管其他鍵值了。同時,分配好的層不能改,因為那樣樹的結構就必須改變,那修正這棵樹就太麻煩了。

由於有多個鍵值,k-d樹不能一直只維護一個鍵值,那樣無法對其他鍵值搜索。一種方法是循環分配,第n層依賴於第n%k個鍵值。比如上面的2-d樹,可以讓奇數層依賴於x,偶數層依賴於y。如果點的分布已經知道不很平均了,那麽可以讓方差比較大的那些鍵值多分配一些層。比如我們已經知道給我們的點的橫坐標都一樣,那麽上面那棵2-d樹完全可以改成1-d樹了。不過不知道輸入的時候還是用循環分配比較好。

k-d樹可以實現二叉搜索樹的查找功能,還可以多實現一個範圍查找功能。

以1-d樹為例:假設要求維護一些數軸上的點,支持插入和範圍查找。範圍查找表示輸入一個數軸上的區間[l,r],要求輸出區間裏的點的個數。

1-d樹維護範圍查找是這樣的:首先對根節點判斷,假如根節點在整個區間的右邊且不在區間裏,表明區間裏的所有點一定都在根節點的左邊了,這時候直接搜索左子樹即可;另一種對稱的情況一樣,搜索右子樹即可。問題是處理根節點在區間裏的情況:假設根節點的坐標為x,那麽對左子樹搜索區間[l,x],同時對右子樹搜索區間[x,r],答案加起來就可以了。查找到NULL的時候直接返回0。這個處理是比較好理解的。

情況推廣到k-d樹是一樣的,只不過查找時也要判斷當前層依賴的鍵值,只對詢問的這一維的範圍做一維查找即可。

有時候k-d樹被要求做部分查找,比如對平面直角坐標系,查找橫坐標在[l,r]區間裏的點。這時候如果當前層的依賴鍵值不是橫坐標,直接對左右子樹暴力查找[l,r],否則還是做一維查找。

k-d樹的復雜度很復雜,可能會受到重復元素的幹擾。同時,如果部分查找太多也會影響復雜度。分析它的復雜度本身就很復雜,我照抄書上的結果:對於平衡的k-d樹,復雜度為O(M+kN1-1/k)。k為維度,N為節點數,M為範圍內的節點數。簡單一點的話可以略去M(k或N較大時)。可以看到,k越大,M越多,上界越接近O(N)

,因此k-d樹的復雜度還是比較高的,並且重復元越多越影響實際效率。。

上面大家可能看到了平衡這兩個字:如何對k-d樹維護平衡?一般的平衡樹的操作不可行,因為它們都用到了旋轉,但是旋轉意味著整棵樹的節點高度都變了,那麽它們所在的層就變了,那麽就需要重新調整它們的兒子,這對效率來講明顯是災難性的。顯然我們不應該頻繁的改變樹的結構,尤其不應改變節點的高度。

替罪羊樹可以滿足這一點。替罪羊樹平時不會改變樹的結構,僅在重構時改變樹的結構。重構和本身的替罪羊不太一樣,還要將需要重構的點按照當前鍵值排序,然後再像以前一樣處理。也可以建一顆空樹,預先處理好每層的依賴鍵值,將點一個個插入,最後拼回去。由於本來範圍查找的復雜度就是O(log2n),因此這個操作還是可以維持在上界裏。不過1-d樹可以像本來那樣重構。

若用循環分配,在遞歸時記錄一下當前層數就可以O(1)處理當前層的依賴鍵值了。

這樣,k-d樹的刪除可以用懶惰刪除,即只給一個被刪除節點打上標記,還可以用它查找,但不計入答案裏,當重構時順便刪除,這樣可以最低限度維持樹的結構不改變。

k-d樹是一種比較方便的多維範圍查找的數據結構,復雜度也不是很差,所以是多維範圍查找的首選。

1-d樹代碼(不處理重復元的代碼)(之後可能補上2-d樹的):

技術分享圖片
#include<cstdio>
#define nil 0
#define alpha 0.8f
#define MXN 100000+1
class IO{
    public:
        IO operator >> (int &a){scanf("%d",&a);return *this;}
        IO operator << (const char &a){printf("%c",a);return *this;}
        IO operator << (const char *a){printf(a);return *this;}
        IO operator << (const int &a){printf("%d",a);return *this;}
}cin,cout;
class Node{
    public:
        int x,size,fa,left,right,del;
};
class QueryData{
    public:
        int xl,xr;
        void Set(int l,int r){xl=l,xr=r;return;}
        int Check(Node N){
            if(xl<=N.x&&N.x<=xr) return 0;
            else if(xr<N.x) return 1;
            else if(N.x<xl) return 2;
        }
};
class Node node[MXN];
int recycle[MXN],lst[MXN],ntop,rtop,ltop,root,node_sum,del_sum;
void Init(){
    ntop=ltop=root=0;
    rtop=-1;
    return;
}
int NewNode(int k){
    int nw;
    if(rtop==-1) nw=++ntop;
    else nw=recycle[rtop--];
    node[nw].x=k;
    node[nw].del=0;
    node[nw].size=1;
    node[nw].fa=node[nw].left=node[nw].right=0;
    return nw;
}
void Update(int now){
    node[now].size=node[node[now].left].size+node[node[now].right].size+1;
    return;
}
bool CheckBalan(int now){
    double T=(double)node[now].size;
    double L=(double)node[node[now].left].size;
    double R=(double)node[node[now].right].size;
    return L>T*alpha || R>T*alpha;
}
void Travel(int now){
    if(now==nil) return;
    Travel(node[now].left);
    if(node[now].del==0) lst[ltop++]=now;
    else{
        recycle[++rtop]=now;
        del_sum--;
    }
    Travel(node[now].right);
    return;
}
int Build(int l,int r){
    if(l>=r) return nil;
    int nw=lst[(l+r)/2];
    node[nw].left=Build(l,(l+r)/2);
    node[nw].right=Build((l+r)/2+1,r);
    node[node[nw].left].fa=nw;
    node[node[nw].right].fa=nw;
    Update(nw);
    return nw;
}
int Search(int now,int k){
    if(now==nil) return nil;
    if(node[now].x==k){
        if(node[now].del==0) return now;
        else return nil;
    }
    if(k<node[now].x) return Search(node[now].left,k);
    if(k>node[now].x) return Search(node[now].right,k);
}
void Insert(int now,int ins){
    if(root==nil) root=ins;
    else{
        node[now].size++;
        if(node[ins].x<node[now].x){
            if(node[now].left==nil){
                node[now].left=ins;
                node[ins].fa=now;
            }
            else Insert(node[now].left,ins);
        }
        else{
            if(node[now].right==nil){
                node[now].right=ins;
                node[ins].fa=now;
            }
            else Insert(node[now].right,ins);
        }
    }
    return;
}
void Insert(int k){
    if(Search(root,k)!=nil) return;
    node_sum++;
    int ins=NewNode(k);
    Insert(root,ins);
    int t=nil,f=nil,flag=0,a=nil;
    for(int i=ins;i!=nil;i=node[i].fa){
        if(CheckBalan(i)) t=i;
    }
    if(t!=nil){
        f=node[t].fa;
        if(f==nil||node[f].left==t) flag=0;
        else flag=1;
        ltop=0;
        Travel(t);
        a=Build(0,ltop);
        if(f==nil) root=a;
        else if(flag==0) node[f].left=a;
        else if(flag==1) node[f].right=a;
        node[a].fa=f;
        Update(f);
    }
    return;
}
void Remove(int now,int k){
    if(now==nil) return;
    if(k==node[now].x){
        node[now].del=1;
    }
    else{
        if(k<node[now].x) Remove(node[now].left,k);
        else Remove(node[now].right,k);
    }
    return;
}
void GrandOrder(int now){
    if(now==nil) return;
    GrandOrder(node[now].left);
    if(node[now].del==0) cout<<node[now].x<< ;
    GrandOrder(node[now].right);
    return;
}
int Query(int now,QueryData D){
    if(now==nil) return 0;
    int ans=0,temp=D.Check(node[now]);
    QueryData T;
    if(temp==0){
        if(node[now].del==0) ans=1;
        T.Set(D.xl,node[now].x);
        ans+=Query(node[now].left,T);
        T.Set(node[now].x,D.xr);
        ans+=Query(node[now].right,T);
    }
    else if(temp==1){
        ans+=Query(node[now].left,D);
    }
    else if(temp==2){
        ans+=Query(node[now].right,D);
    }
    return ans;
}
int main(){
    freopen("test.txt","r",stdin);
    freopen("testans.txt","w",stdout);
    Init();
    int p,x,y;
    while(1){
        if(del_sum*2>=node_sum){
            ltop=0;
            int a;
            Travel(root);
            a=Build(0,ltop);
            root=a;
            node[a].fa=nil;
            node_sum=node[a].size;
            del_sum=0;
        }
        cin>>p;
        if(p==0) break;
        if(p==1){
            cin>>x;
            Insert(x);
        }
        if(p==2){
            cin>>x;
            if(Search(root,x)!=nil){
                Remove(root,x);
                del_sum++;
            }
        }
        if(p==3){
            cin>>x>>y;
            QueryData D;
            D.Set(x,y);
            cout<<Query(root,D)<<\n;
        }
        if(p==4){
            GrandOrder(root);
            cout<<\n;
        }
    }
    return 0;
}
1-d樹

數據結構——k-d樹