問題:設計一個演算法,將一棵二叉搜尋樹(Binary Search Tree,BST)儲存到檔案中,需要能夠從檔案中恢複原來的二叉搜尋樹。注意演算法的時空複雜度。
思路:二叉樹遍曆演算法有先序遍曆、中序遍曆、後序遍曆演算法等。但是它們中間只有一種遍曆演算法符合題目條件,用於儲存BST到檔案中並從檔案中恢複原來的BST。假定我們要儲存的BST如下:
_ 30_ / \ 20 40 / / \10 35 50
1)中序遍曆
如果我們對該BST進行中序遍曆,可以得到10 20 30 35 40 50,但是我們無法從中推斷出原始的二叉搜尋樹結構。輸出為10 20 30 35 40 50,一種可能的BST結構如下所示,這是一棵不平衡的BST,顯然這不是原來的BST。
_50 / 40 / 35 / 30 / 20 /10
2)後序遍曆既然中序遍曆不能滿足條件,那麼看看後序遍曆如何?後序遍曆在列印出父結點之前列印葉子結點。後序遍曆該BST可以得到:10 20 35 50 40 30 。讀取這些結點並構造出原來的BST是個難題,因為在構造二叉樹時是先構造父結點在插入孩子結點,而後序遍曆序列是先讀取到孩子結點然後才是父結點,所以也不符合條件。3)先序遍曆中序遍曆與後序遍曆都不滿足條件,只有先序遍曆是可以滿足條件的。BST的先序遍曆結果為:30 20 10 40 35 50。我們觀察到重要的一點就是:一個結點的父親結點總是在該結點之前輸出。有了這個觀察,我們從檔案中讀取BST結點序列後,總是可以在構造孩子結點之前構造它們的父結點。
將BST寫入到檔案的代碼跟先序遍曆一樣。
從檔案中讀取BST現在的問題是,如何從讀取的結點序列中重新構造BST?簡單的辦法就是對於每個結點,使用二叉搜尋樹insert方法執行N次插入操作,每次插入需要時間O(lgN),這樣總共需要O(NlgN)的時間。
這個方法不夠高效。
解法:回顧前面文章,判定一棵二叉樹是否是二叉排序樹 中的解法2採用了範圍判定來判斷每個節點是否符合條件。這裡採用類似的思想給出一個更為高效的解法用於從檔案中重新構建原來的二叉搜尋樹。我們從父結點傳遞一個有效範圍到孩子結點。當我們要插入結點時,判斷該插入結點是否在有效範圍,如果是則插入,否則尋找一個新的位置進行插入。整個時間複雜度為O(N)。
void readBSTHelper(int min, int max, int &insertVal, struct node *&p, ifstream &fin) { if (insertVal > min && insertVal < max) { int val = insertVal; p = newNode(val); if (fin >> insertVal) { readBSTHelper(min, val, insertVal, p->left, fin); readBSTHelper(val, max, insertVal, p->right, fin); } }} void readBST(struct node *&root, ifstream &fin) { int val; fin >> val; readBSTHelper(INT_MIN, INT_MAX, val, root, fin);}
英文原文:Saving a Binary Search Tree to a File