前言數組
最近作題目,已經不止一次用到笛卡爾樹了。這種數據結構極爲優秀,可是構造的細節很容易出錯。所以寫一篇文章作一個總結。數據結構
笛卡爾樹 Cartesian Tree函數
引入問題ui
有N條的長條狀的矩形,寬度都爲1,第i條高度爲Hi,相鄰的豎立在x軸上,求最大的子矩形面積。spa
約定指針
1 ≤ N ≤ 105code
1 ≤ Hi ≤ 109blog
分析索引
咱們只須要求出每條矩形最多能夠向兩邊拓展的寬度,就能夠算出以這個矩形高度爲高的最大子矩形面積。最後咱們求一個最大值便可。ast
下面咱們仍是回到以前的笛卡爾樹。
概念
笛卡爾樹的樹根是這一子樹中鍵值最小(或最大)的元素;且對於某個序列的笛卡爾樹,其任一子樹的中序遍歷剛好是對應了原序列中的一段連續區間。
性質
咱們會發現笛卡爾樹同時知足二叉樹搜索和堆的性質:
構造
咱們能夠利用單調棧在線性時間內對給定的數組序列構造出其笛卡爾樹。
首先,因爲笛卡爾樹的中序遍歷爲原數組序列,那麼咱們設
Ti爲當前序列中[1..i]區間的笛卡爾樹,
那麼,必定有:
第(i+1)個節點屬於Ti最右邊的那一條路徑。
那麼對於有已經構造好的Ti和第(i+1)個節點,咱們只須要沿着Ti最右邊的路徑從下往上找,直到發現當前節點能夠放的位置。
咱們能夠用一個單調棧來維護最右邊的這條路徑,當前根節點壓在棧底,對於當前節點,咱們將它與棧頂比較,若棧頂不能作它的父節點,則將棧頂彈出。
因爲每一個點進棧和出棧至多一次,所以這個構造的複雜度爲O(n)的。
參考代碼
節點用結構體定義
(這裏咱們用指針會方便許多)
struct Node { int index, val; // index表示原數組的索引,val爲當前節點的鍵值 Node *parent, *lefts, *rights; // 這三個指針分別指向父節點,左子節點,右子節點 Node(int id = 0, int v = 0, Node *l = NULL, Node *r = NULL) { index = id; val = v; lefts = l; rights = r; } };
樹構造函數
(這裏要注意的細節較多)
Node * build(int arr[], int size) { // 這裏構建一個根節點爲最小值的笛卡爾樹 std::stack<Node * > S; // 存儲最右邊路徑的棧 Node *now, *next, *last; for (int i = 0; i < size; i++) { next = new Node(i, arr[i]); last = NULL; // last用來指向最後被彈出棧的元素(如有彈出),它的做用後面會寫到 while (!S.empty()) { if (S.top()->val < next->val) { // 若棧頂節點的鍵值比當前節點鍵值小了,那麼當前節點就作棧頂節點的右子節點 now = S.top(); if (now->rights) { // 而棧頂節點的原右子節點要變成當前節點的左子節點(因爲前面必定與當前節點比較過了,棧頂節點右子樹的鍵值必定都比當前節點大) now->rights->parent = next; next->lefts = now->rights; } now->rights = next; next->parent = now; break; } last = S.top(); S.pop(); } if (S.empty() && last) { // 這裏爲了特判一種可能出現的狀況,就是當前節點把棧所有彈空了,就要把原先的根節點做爲當前節點的左子節點 next->lefts = last; last->parent = next; } S.push(next); } while (!S.empty()) now = S.top(), S.pop(); return now; }