1. 程式人生 > >不需要旋轉,卻能力壓群雄的資料結構——非旋Treap 看完還不會你打我

不需要旋轉,卻能力壓群雄的資料結構——非旋Treap 看完還不會你打我

非旋Treap講解

Treap,一種平衡樹。作為一棵平衡樹,一定是遵從著某種原則,使得這棵樹儘量的接近完全二叉樹。除了二叉搜尋樹都具備的性質——左子樹 ≤ 根 ≤ 右子樹,顧名思義,Treap = tree+heap。這時他的特殊性質就飄出水面了——heap有一個需要慢慢理解的東西,我覺得是本文最重要的——二叉搜尋樹中順次排列所參考的引數是靈活的,誰說非要按數值??有的題你就得用下標。

一、每個node的元素組成

對於一個node,有fix(隨機分配的數,用rand即可),size(這個node所引領的樹的大小,包括自己),剩下的引數視情況而定。這棵樹所包含點的fix值,需要滿足堆的性質,一般情況下我們會使用最小堆(約定俗成)。以bzoj3224為例:
struct Treap {
    Treap *l, *r;
    int fix, key, size; //fix表示隨機值,key表示數值,size表示以它為根的這棵子樹的大小
    Treap(int key_):fix(randad()), key(key_), l(NULL), r(NULL), size(1) {}
    inline void updata() {
        //更新樹的大小
        size = 1 + (l?l->size:0) + (r?r->size:0);
    }
}*root;
typedef pair<Treap*, Treap*> Droot;
inline int Size(Treap *x) {return x?x->size:0;}

一定要注意!在改了某節點後,一定要update!

二、基礎的操作

有人會說,既然都不旋轉了,那肯定操作起來特別難。好吧我告訴你們,真的挺難。不過只要認真看完我的文章(老臉一紅)並多多練習,我相信你一定能使用的得心應手。

①Split

先下個定義:Split(x, k)表示,把x引導的這一棵樹的前k個節點(順序是按照左子樹 ≤ 根 ≤ 右子樹來劃定的)從樹中分離出來。函式的返回結果是一個節點對,記作(x1, x2),此時x1所引導的樹就是前k個節點,x2引導的樹就是剩下的節點。注意:Split之後,我們已經真真切切的把它們切開了,現在我們的Treap已經不是一個整體了,一定要養成好習慣,Split之後不要忘了再合併起來
(合併是我們要講的下一個基礎操作)。那具體怎麼做呢?
Droot Split(Treap *x, int k) {
    if(!x) return Droot(NULL, NULL);
    Droot y;
    if(Size(x->l) >= k) { //如果左子樹的節點數能達到k個,則前k個一定都在左子樹中
        y = Split(x->l, k);
        x->l = y.second; //x的左子樹只留下來了不在前k個位置的節點
        x->updata();
        y.second = x; //此時x這棵樹可以整個拖上去,作為以前不在前k個位置的節點集合
    }else {
        y = Split(x->r, k - Size(x->l) - 1); //要在右子樹搜尋前幾個,連同根節點與左子樹,作為前k個
        x->r = y.first; //x的右子樹變為以前的右子樹的前幾個(此時x的size應恰為k)
        x->updata();
        y.first = x; //整個拖上去,作為前k個位置的點的集合
    }
    return y;
}

②Merge

繼續下定義:Merge(x,y)表示,把x這棵樹與y這棵樹合併起來(必須滿足的條件:在劃分順序時,x中所有元素都應排在y中的元素的前面。所以我認為Merge最重要的性質在於有序性)。函式的返回結果是一個節點,表示合併後這棵樹的樹根。
Treap *Merge(Treap *A, Treap *B) {
    if(!A) return B;
    if(!B) return A;
    if(A->fix < B->fix) { //如果A的隨機數比較小,那他應該居上
        A->r = Merge(A->r, B); //而B本就應該排A後面,於是把A的右子樹和B合併,一併作為A的右子樹(注意順序)
        A->updata();
        return A;
    }else { //B居上
        B->l = Merge(A, B->l); //把A和B的左子樹合併,一併作為B的左子樹
        B->updata();
        return B;
    }
}

於是,到現在,基本的操作你就都會了。我們來應用一下。看一下bzoj3224,要我們支援的操作有6個,我們一一來分析。①先說查詢一個數的排名。就和普通二叉搜尋樹沒什麼區別的,直接上程式碼。
inline int Getkth(Treap *x, int v) {
    //詢問一個數是第幾小 
    if(!x) return 0;
    int ans = 0, temp = (int)2e9+1;
    while(x) {
        if(v == x->key) temp = min(temp, ans + Size(x->l) + 1);
        if(v > x->key) ans+= Size(x->l) + 1, x = x->r;
        else x = x->l;
    }
    return temp==(int)2e9+1?ans:temp;
}
②順便一塊把查詢第幾名是誰說了吧。異曲同工。
int Findkth(int k) {
    //尋找第k小
    Treap *p; p = root;
    while(true) {
        if(Size(p->l) == k - 1) return p->key;
        if(Size(p->l) > k - 1) p = p->l;
        else k-= (Size(p->l) + 1), p = p->r;
    }
}
③Insert:只加入一個數,於是我們先查詢,在平衡樹中有幾個比x小的(用Getkth,這就是為什麼我先講查排名),記為k,然後把樹分開,把節點放中間,合併。
inline void Insert(int v) {
    int k = Getkth(root, v);
    Droot x = Split(root, k);
    Treap *n = new Treap(v);
    root = Merge(Merge(x.first, n), x.second);
    return ;
}
④Delete:刪除。與insert一樣,分成三段——前k-1個,第k個,後面剩下的。直接合並第1、3段即可。(如果必要,最好隨刪隨清記憶體,這就是指標的好處。但指標的壞處就是極其不可控,容易re)刪記憶體程式碼:
void del(Treap *p) {
	if(!p) return ;
	if(p->l) del(p->l);
	if(p->r) del(p->r);
	delete p;
}
⑤⑥前驅與後繼,這個很簡單,就是利用查排名函式。
inline int pre(Treap *k, int x) {
    int ans = (int)-2e9-1;
    while(k) {
        if(k->key < x) ans = max(ans, k->key), k = k->r;
        else k = k->l;
    }
    return ans;
}

inline neg(Treap *k, int x) {
    int ans = (int)2e9+1;
    while(k) {
        if(k->key > x) ans = min(ans, k->key), k = k->l;
        else k = k->r;
    }
    return ans;
}

再看一個應用,笛卡爾樹。我們知道,加點的複雜度是log級的,批量加點複雜度顯然有一點大(其實我只是想去掉一點常數,log級並沒有那麼大,但是oj也許會卡)。批量加數時,先存進陣列a中,a[0]表示一共有多少數要進樹。
Treap *Build(int *a) {
	static Treap *x, *last;
	int p = 0;
	for(int i = 1; i <= a[0]; ++i) {
		x = new Treap(a[i]);
		last = NULL;
		while(p && sta[p]->fix > x->fix) {
			sta[p]->updata();
			last = sta[p];
			sta[p--] = NULL;
		}
		if(p) sta[p]->r = x;
		x->l = last;
		sta[++p] = x;
	}
	while(p) sta[p--]->updata();
	return sta[1];
}

至此,Treap的基本知識已經講完了。如果大家有興趣看一下平衡樹的操作boss題,我推薦bzoj1500維修數列。附上我的維修數列Treap版程式碼:http://paste.ubuntu.com/26194333/

相關推薦

需要旋轉能力群雄資料結構——Treap

非旋Treap講解Treap,一種平衡樹。作為一棵平衡樹,一定是遵從著某種原則,使得這棵樹儘量的接近完全二叉樹。除了二叉搜尋樹都具備的性質——左子樹 ≤ 根 ≤ 右子樹,顧名思義,Treap = tree+heap。這時他的特殊性質就飄出水面了——heap。有一個需要慢慢理解

webpack-dev-server使用方法的來找~

記錄下webpack-dev-server的用法. 首先,我們來看看基本的webpack.config.js的寫法 module.exports = { entry: './src/js/index.js', output: {

SQL語句裡面煩人的引號懂罵!!

現在在做一個人事工資管理系統,採用C#+SQL Server2008實現,在看到很多SQL語句後很不解,查到資料如下:      string str =//建立查詢字串                 "select ID as '編號',employeeID as '員

手把手教用java實現syslog訊息的收發嘍!

>大家好,我是道哥,專注於後端java開發,喜歡寫作和分享。如果覺得文章對你有用,那就點個讚唄!如果能轉發那是對道哥最大的支援! ## syslog的定義 >見文知義,syslog,從英文名字上可以看出是指系統日誌。 >以下內容摘自百度百科: Syslog常被稱為系統日誌或系統記錄,是一種

關於 JOIN 耐心總結係列

現在隨著各種資料庫框架的盛行,在提高效率的同時也讓我們忽略了很多底層的連線過程,這篇文章是對 SQL 連線過程梳理,並涉及到了現在常用的 SQL 標準。 > 其實標準就是在不同的時間,制定的一些寫法或規範。 ## 從 SQL 標準說起 在編寫 SQL 語句前,需要先了解在不同版本的規範,因為隨著版本的變

java執行緒池原理(入門版)——直播吃香

網上關於java執行緒池的部落格,大多是直接分析ThreadPoolExecutor類的實現,但是他們就像是做中文翻譯一樣,但是很少有講到本質的東西。 這篇部落格從根本出發,看完可以自己實現一個簡單執行緒池。下面正式開始。 一、我們知道,用java建立一條新執行

!!!超簡單 springboot2.0中 單機 quartz yml檔案配置 持久化到資料庫

建立表 可到官網下載原始碼 解壓之後。在docs\dbTables檔案下選擇自己所需要的slq檔案。下載地址 新增引用 <!--quartz--> <dependency> &l

delphi安裝pngimage控件需要安裝只需引用就行

-- ons div 菜單 -c home 文件夾 class alt delphi7的pngimage控件如何安裝 20 解壓後的安裝包如圖所示,求高人指點如何把它安到delphi7上,感激不盡 在路徑裏面引用你這個文件夾菜單--tools---library然

企業需要“好人”而是需要稱職的人!

幹什麽 而是 原則 價值 綁架 進行 中國 交流 自己的 企業到底需要不需要好人?企業應該從什麽角度評價員工?相信這是很多企業、大多數老板都會遇到的困擾!這幾天和老板朋友交流對幹部員工的看法,不僅老板口頭上少不了對幹部是“好人”的評價,連我也會有“他的確是一個好人”的回應。

光良好好唱歌“轉行”跑去當程式設計師?

"我自己是一名從事了8年的j a v a全棧開發工程師,辭職目前在做線上教育講師,來了就是我學生,有不懂的可以問我今年4月我花了一個月整理了一套比較系統適合2018年學習的 j a v a資料,從基礎的面向物件到執行緒,j d b c都有整理,送給每一位小夥伴,這裡是學習者聚集地,如果你有興

在jsp頁面中需要建立直接使用的物件

內建物件     * 一共有9個:             變數名                    真實型別                        作用         * pageContext           PageContext          

程式設計師面試7輪辛苦拿到offer薪資感覺被坑了!

一小夥工作快3年了,拿到了阿里雲Java開發崗位P6的offer,算HR面一起,加起來有7輪面試了,將近3個月的時間,什麼jvm、多執行緒程式設計、Linux、網路等方面的面試題,直接面試到自己懷疑人生。而自己跟HR談論薪資的時候也是沒有任何底氣,說是22k,結果被HR壓倒了19k,現在拿的都是15

Iterator使用迭代器這個訪問方法可以讓開發人員需要了解訪問的容器的底層結構就可以對容器遍歷 迭代器是輕量級的容器

package com.java.Interview; import java.util.Iterator; import java.util.LinkedList; import java.util.List; public class TestIterato

喜歡程式碼為何堅持做程式設計師?

簡介一轉眼,距離自己成為一名碼農,已近一年了。回想當初,剛成為猿類時的那種對程式碼的激情,已經消失的差不多了。這個可能也跟我當初的想法有關,本身是數學科班出身,又接受了培訓,結果剛找工作時,誤打誤撞進入了售前的行業,寫程式碼就成了自己的未竟事件。我原本的職業規劃是:前端程式設

SpringBoot+jpa配置自動建立表報錯建立表

原因 找了網上很多答案,均不對,包括以下幾種: 包導的不對 配置檔案不對 註解寫的不對 … 最後發現原因: Sprint的入口檔案在子目錄裡了,應該比其他諸如server、dao、domain高一級。 例如:service檔案所在為com.wds.met

hexo next主題深度優化(十)博文加密需要外掛極簡模式相對安全融合pjax。

如果想自定義功能樣式的往下面看看也許會有點收穫,為了避免讀者不耐煩的看我的廢話,所以移到了下面。 本人部落格:mmmmmm.me 效果: 程式碼: /blog/themes/next/layout/_layout.swig,找到main標籤在吐下程式碼處新增自定義的sw

Container內需要OS為何需要OS的基礎映象?

轉載:http://dockerone.com/question/6 首先我來回答一下問題一,Container內需不需要OS?Container不是一個VM技術,所以和OS沒有關係。如果我沒有理解錯,這個Container應該指的是Docker Run出的執行環境,因為

pl/sql developer需要選中執行游標所在行

問題:        執行某一行語句時,需要選中改行語句,按F8才能執行; 解決辦法:       工具——》首選項——》SQL視窗——》自動選擇該語句。這樣游標放在此行,按F8就可以執行,需要注意的是sql語句必須以分號結束。

高德地圖的Marker需要setMap建立時預設就可以顯示在地圖上

很驚奇,之前用百度地圖API都是需要在建立Marker物件後,再執行setMap函式,才能顯示到地圖上,高德完全可以省略這一步,程式碼如下: $(document).ready(function(){ // 為防止CSRF(Cross-s

csdn快速的轉載別人部落格裡的文章需要複製簡單一點

  對於喜歡逛CSDN的人來說,看別人的部落格確實能夠對自己有不小的提高,有時候看到特別好的部落格想轉載下載,但是不能一個字一個字的敲