一、問題描述
輸入一棵二叉搜尋樹,現在要將該二叉搜尋樹轉換成乙個排序的雙向鍊錶。而且在轉換的過程中,不能建立任何新的結點,只能調整樹中的結點指標的指向來實現。
二、實現思路
在二叉搜尋樹中,每個結點都有兩個分別指向其左、右子樹的指標,左子樹結點的值總是小於父結點的值,右子樹結點的值總是大於父結點的值。而在雙向鍊錶中,每個結點也有兩個指標,它們分別指向前乙個結點和後乙個結點。所以這兩種資料結構的結點是一致,二叉搜尋樹之所以為二叉搜尋樹,雙向鍊錶之所以為雙向鍊錶,只是因為兩個指標的指向不同而已,通過改變其指標的指向來實現是完全可能的。
例如如下的二叉搜尋樹,
若採用中序遍歷,其遍歷順序為1-2-3-4-5-6-7,通過適當的指標變換操作,可變成的雙向有序鍊錶如下:
從上圖,我們可以看出,為了減少指標的變換次數,並讓操作更加簡單,在轉換成排序雙向鍊錶時,原先指向左子結點的指標調整為鍊錶中指向前乙個結點的指標,原先指向右子結點的指標調整為鍊錶中指向下乙個結點的指標。例如對於上面的值為2的指點,調整後,它的前乙個結點為1,後乙個結點為3,而結點2的左子結點本來就為1,右子結點本來就為3.
對於樹的操作,通常是在遍歷樹的各個結點的過程中,通過對結點實施某些操作來完成的,這個演算法也不例外。由於要求轉換後的雙向鍊錶也是有序的,而我們從上面也可以看到,當我們以中序遍歷二叉搜尋樹時,其遍歷的結點就是有序的,所以在這裡我位採用的遍歷順序應該是中序。
那麼我們應該如何調整指標,讓二叉搜尋樹變成乙個雙向有序鍊錶呢?當遍歷到根結點時,我們可以把樹看成三個部分:根結點,根的左子樹和根的右子樹。如上圖的二叉排序樹,就分成了根結點4、以結點2為根的左子對和以結點6為根的右子樹。從變換的鍊錶中我們可以看到,應當把結點4的left指標指向結點3,把結點3的right指標指向結點4,而由於我們採用的是中序遍歷,所以當我們遍歷到結點4時,結點4的左子樹已經轉化為乙個有序的雙向鍊錶,而結點3是這個已經轉化的雙向鍊錶的尾結點,所以我們應該用乙個變數last_node來儲存最後乙個結點的指標,以便在與根結點連續時使用。然後把這個變數last_node的值更新為指向根結點4。對於結點4的右子樹,採取相似的操作。至於具體的實現,我們只需要對所有的子樹遞迴地執行上述操作即可。其操作過程如下:
三、實現**
#include #include #include using std::cout;
using std::cin;
using std::endl;
struct bsnode
;//定義各種用到資料型別
typedef bsnode* bstree;
typedef bsnode* dlist;
typedef bsnode dlnode;
//往二叉搜尋樹tree中插入值為data的結點
bstree insertnode(bstree tree, int data);
//把二叉搜尋樹tree轉化成雙向鍊錶,返回頭結點
dlist bstreetolist(bstree tree);
//遍歷二叉搜尋樹tree的各個結點,並進行指標調整
void convertnode(bstree tree, bsnode **last_node);
//查詢二叉搜尋樹tree的最左結點
bsnode* findleftmostnode(bstree tree);
//以中序輸出二叉搜尋樹tree
void printbitree(bstree tree);
//輸出鍊錶
void printlist(dlist list);
bstree insertnode(bstree tree, int data)
//插入在其右子樹中
else if(tree->data < data)
tree->right = insertnode(tree->right, data);
//插入在其左子樹中
else if(tree->data > data)
tree->left = insertnode(tree->left, data);
return tree;
}dlist bstreetolist(bstree tree)
bsnode* findleftmostnode(bstree tree)
void convertnode(bstree tree, bsnode **last_node)
void printbstree(bstree tree)
void printlist(dlist list)
}int main()
cout << "\nthe bstree is: " << endl;
printbstree(tree);
//進行轉換
tree = bstreetolist(tree);
cout << "\nbitree to list: "<< endl;
printlist(tree);
return 0;
}
執行結果如下:
四、**分析
由於二叉排序樹中不允許有相同的元素,在隨機產生的10個數中,有兩個是相同的3和73,所以實際插入到二叉排序樹中的結點只有8個,然後我們以中序方式遍歷輸出二叉排序樹中的資料,然後輸出轉換後的鍊錶的資料,發現其順序是一致的,從而證明演算法的正確性。
該演算法的實現的核心函式為bstreetolist,convertnode和findleftmostnode.
我們可以看到在函式bstreetolist中,我們有乙個變數last_node用來記錄轉換了的鍊錶末結點,由於在慣例中,我們會返回鍊錶的第1個結點(從1開始計數)的指標,而last_node指向的卻是末結點,我們可以通過該指標來從尾走到頭來獲取第乙個結點的指標,但是在這裡我卻沒有這樣做,因為它需要對每個結點都遍歷一次,時間複雜度為o(n)。而是在變換前,找到二叉排序樹的最左結點的指標。因為排序二叉樹是有序的,最左的結點即為最小的結點,而我們的演算法也不會刪除或新增結點,也就是說結點的位址是不會改變的,所以最左的結點就是轉換後的鍊錶的第1個結點,其時間複雜度為o(logn)。
五、時間複雜度與空間複雜度
該演算法首先從根要點一直向左走,找到最左邊的結點,其時間複雜度為o(logn),然後對二叉排序樹中的每個結點遍歷一次,進行指標變換,其時間複雜度為o(n),所以總的時間複雜度為o(n)。
至於空間複雜度,由於convertnode函式進行遞迴呼叫,其函式有兩個開參,而函式棧中的函式呼叫層數不會超過樹高,所以其空間複雜度為o(logn)。
二叉搜尋樹轉換為有序雙向鍊錶
一 問題描述 輸入一棵二叉搜尋樹,現在要將該二叉搜尋樹轉換成乙個排序的雙向鍊錶。而且在轉換的過程中,不能建立任何新的結點,只能調整樹中的結點指標的指向來實現。二 實現思路 在二叉搜尋樹中,每個結點都有兩個分別指向其左 右子樹的指標,左子樹結點的值總是小於父結點的值,右子樹結點的值總是大於父結點的值。...
將二叉搜尋樹轉換為雙向鍊錶
題目描述 輸入一棵二叉搜尋樹,將該二叉搜尋樹轉換成乙個排序的雙向鍊錶。要求不能建立任何新的結點,只能調整樹中結點指標的指向。好悲傷,面試微軟的時候被遇到這個題,知道大體思路,但是最終 沒寫好,由此可見平常學東西不紮實。總是淺嘗輒止。思路 使用中序遍歷,儲存已經建好的雙向鍊錶的最後乙個節點。那麼處理當...
將二叉搜尋樹轉換為雙向鍊錶
二叉搜尋樹的定義 若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值 若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值 它的左 右子樹也分別為二叉排序樹。題目描述 輸入一棵二叉搜尋樹,將其轉換為排序的雙向鍊錶,要求是不能建立任何新的節點,只能調整樹中節點指標的指向。public...