《演算法導論》12.3節習題
- 12.3-1 二叉搜尋樹insert操作的遞迴版本
void insert1(Node* pRoot, Node* pAdd) { bool bLeft = pAdd->key < pRoot->key; Node* pNextRoot = bLeft ? pRoot->left : pRoot->right; if(pNextRoot) insert1(pNextRoot, pAdd); else { pAdd->parent = pRoot; if(bLeft) pRoot->left = pAdd; else pRoot->right = pAdd; } }
12.3-2 insert過程途經n個節點後遇到了空節點,便將待插入的元素安放上去了,接下來的search過程先路過同樣的n個節點,最後與第n+1個節點比較發現相同,於是search完畢。
12.3-3 由12.3-2可得,insert過程和search過程一樣,是O(h)-time的,h代表樹的高度。
設一共有n個節點,構建一棵樹需要呼叫n次insert,這個過程耗時O(nh),後續的中序遍歷耗時是O(n)。所以排序過程的時間複雜度是由構建過程決定的。
最壞的情況集合按照正序或者倒序排列,則h = n,總時間是O(n^2)。
最好情況是集合按照層次遍歷順序排列,最後構建成一棵完全二叉樹,h = lg(n) ,總時間為O(n*lg(n))。
12.3-4 從同一棵二叉搜尋樹上先刪除x再刪除y,和先刪除y再刪除x,最終得到的樹一定是一樣的嗎?
不一定,舉個反例:
//現在有一棵樹,上面有1,2,3,4四個節點: 2 / \ 1 4 / 3 //如果 先刪除1 再刪除2,結果是: 2 2 4 / \ \ / 1 4 --> 4 --> 3 / / 3 3 //如果 先刪除2 再刪除1,結果是: 2 3 3 / \ / \ \ 1 4 --> 1 4 --> 4 / 3 //得到的結果是不一樣的。
下面說說,我是怎樣想到這個反例的。
要刪除x,根據刪除的規則,如果x沒有孩子,直接刪除;如果x只有一個孩子,就用唯一的孩子頂替x的位置;如果x有兩個孩子,就將x的後繼節點s頂替x的位置。
從這個規則中可以發現,x有幾個孩子會影響到x的接班人人選。如果刪除的順序可以影響到刪除x時候x的孩子個數,就會影響到最終樹的形狀。
那麼,y在x的什麼位置上,刪除y會對x孩子個數造成影響呢?y本身就是x的一個孩子,而且y沒有孩子。下面分左孩子和右孩子討論。因為x只有一個孩子y的情況太簡單,也不能成為反例,不做討論,下面對x有兩個孩子的情況進行分析。稱以x為根的樹為X樹。
如果y是x的左孩子,先刪y,刪除之後,x的孩子個數變成1,此時刪除x,X樹被其右子樹取代,X樹的根變為x的右孩子。反過來,先刪除x,此時x有兩個孩子,X樹的根變為x的後繼,只要其後繼不是它的右孩子本身,那麼結果就是不一樣的。這也就是上面給出的反例。
如果y是x的右子樹,先刪y,再刪x,X樹被x左子樹取代,先刪x,y頂替x的位置,再刪y,X樹仍然被x的左子樹取代,結果是一樣的,不能作為反例。
還有一種可能,刪除x,x的接班人是自己的後繼s,原以s為根的樹S會發生改變,從S樹上節點能否找出一個y作為反例,我暫時沒有想清楚。
12.3-5 二叉搜尋樹的每個節點儲存“後繼”,“左孩子”,“右孩子”三個屬性,在O(h)時間內實現insert delete search。(這道題在《演算法導論》第三版的中文版翻譯有誤)
為什麼要把“父親”屬性替換成“後繼”屬性呢?相比儲存“父親”,儲存“後繼”屬性的優勢在於查詢後繼節點時間O(1),排序雖然都是O(n)但是常數項較小。執行這兩種操作時,效能相當於單向連結串列。而執行插入、刪除、查詢操作時,效能相當於二叉樹。
我們先來總結一下這些操作需要讀寫哪些屬性。
insert操作需要修改的有:父親節點的孩子屬性,前驅節點和新插入節點的後繼屬性
delete操作需要修改的有:父親節點的孩子屬性,前驅節點的後繼屬性
search操作需要讀取的有:節點的孩子屬性
根據上面的總結可以發現,這道題的關鍵點是在O(h)時間內找到父親節點和前驅節點。
查詢父親節點的做法是從Root向下逐級查詢;如果當前節點沒有左孩子,那麼查詢前驅節點的做法也是從Root向下查詢,可以和父親節點的查詢工作合併起來。如果當前節點有左孩子,那麼前驅節點是左孩子的最大節點。
#include <iostream>
#include <cassert>
using namespace std;
struct Node
{
int key;
Node* succ;
Node* left;
Node* right;
Node(int k):key(k),succ(nullptr),left(nullptr),right(nullptr){}
};
Node* minimum(Node* pRoot);
Node* parent_pred(Node* pRoot, int key, Node*& pPred);//為新插入節點,找父親的同時從父親中找前驅
void insert(Node* pRoot, int key)
{
Node* pNew = new Node(key);
Node* pPred;
Node* pParent = parent_pred(pRoot, key, pPred);
Node* pHead = minimum(pRoot);
//upate parent's child
if(key < pParent->key)
pParent->left = pNew;
else
pParent->right = pNew;
//update pPred's succ and pNew's succ
if(pPred)
{
pNew->succ = pPred->succ;
pPred->succ = pNew;
}
else
{
pNew->succ = pHead;//注意:這個頭結點一定要在插入之前獲取
}
}
Node* pred(Node* pNode); //從左子樹上找前驅
Node* parent(Node* pRoot, Node* pNode); //找父親
Node* parent_pred(Node* pRoot, Node* pNode, Node*& pPred); //為已有節點,找父親的同時從父親中找前驅
void Delete(Node*& pRoot, Node* pDelete)
{
Node* pDeleteReplace = nullptr;
if(!pDelete->left)
{
pDeleteReplace = pDelete->right;//no child //only right
}
else
{
pDeleteReplace = pDelete->left; //only left
if(pDelete->right) //both
{
Node* pSucc = pDelete->succ;
if(pSucc != pDelete->right)
{
Node* pSuccParent = parent(pRoot, pSucc);
if(pSucc->key < pSuccParent->key)
pSuccParent->left = pSucc->right;
else
pSuccParent->right = pSucc->right;
pSucc->right = pDelete->right;
}
pSucc->left = pDelete->left;
pDeleteReplace = pSucc;
}
}
//update parent's child, pred's succ
Node* pPred;
Node* pDeleteParent = parent_pred(pRoot, pDelete, pPred);
bool bLeft;
if(pDeleteParent)
bLeft = pDeleteParent->left == pDelete;
if(pDeleteParent)
{
if(bLeft)
pDeleteParent->left = pDeleteReplace;
else
pDeleteParent->right = pDeleteReplace;
}
else
{
pRoot = pDeleteReplace;
}
if(pPred)
{
pPred->succ = pDelete->succ;
}
}
Node* search(Node* pRoot, int key)
{
Node* pCurrent = pRoot;
int keyCurrent;
while(pCurrent)
{
keyCurrent = pCurrent->key;
if(key == keyCurrent)
break;
if(key < keyCurrent)
pCurrent = pCurrent->left;
else
pCurrent = pCurrent->right;
}
return pCurrent;
}
Node* minimum(Node* pRoot)
{
Node* pMin = pRoot;
while(pMin->left)
{
pMin = pMin->left;
}
return pMin;
}
Node* parent_pred(Node* pRoot, int key, Node*& pPred)
{
Node* pCurrent = pRoot;
Node* pParent = nullptr;
pPred = nullptr;
while(pCurrent)
{
pParent = pCurrent;
if(key < pCurrent->key)
{
pCurrent = pCurrent->left;
}
else
{
pCurrent = pCurrent->right;
pPred = pParent;
}
}
return pParent;
}
Node* parent(Node* pRoot, Node* pNode)
{
Node* pCurrent = pRoot;
Node* pParent = nullptr;
while(pNode != pCurrent)
{
assert(pCurrent);
pParent = pCurrent;
if(pNode->key < pCurrent->key)
pCurrent = pCurrent->left;
else
pCurrent = pCurrent->right;
}
return pParent;
}
Node* parent_pred(Node* pRoot, Node* pNode, Node*& pPred)
{
Node* pCurrent = pRoot;
Node* pParent = nullptr;
pPred = nullptr;
while(pNode != pCurrent)
{
assert(pCurrent);
pParent = pCurrent;
if(pNode->key < pCurrent->key)
pCurrent = pCurrent->left;
else
{
pCurrent = pCurrent->right;
pPred = pParent;
}
}
return pParent;
}
Node* pred(Node* pNode)
{
Node* pPred = pNode->left;
while(pPred->right)
pPred = pPred->right;
return pPred;
}
void walk(Node* pRoot)
{
Node* pCurrent = minimum(pRoot);
while(pCurrent)
{
cout << pCurrent->key << "\t";
pCurrent = pCurrent->succ;
}
cout << endl;
}
void test()
{
//build
Node* pRoot = new Node(4);
insert(pRoot, 2);
insert(pRoot, 5);
insert(pRoot, 1);
insert(pRoot, 3);
insert(pRoot, 7);
insert(pRoot, 6);
insert(pRoot, 8);
walk(pRoot);
//search
cout << search(pRoot, 3)->key << endl;
cout << search(pRoot, 6)->key << endl;
cout << search(pRoot, 4)->key << endl;
//delete
Delete(pRoot, pRoot->right);//delete 5
Delete(pRoot, pRoot->left); //delete 2
Delete(pRoot, pRoot->left); //delete 3
Delete(pRoot, pRoot);// delete 4
Delete(pRoot, pRoot->left);//delete 1
walk(pRoot);
//destroy
Node* pCurrent = minimum(pRoot);
while(pCurrent)
{
delete pCurrent;
pCurrent = pCurrent->succ;
}
pCurrent = nullptr;
}
/*output
1 2 3 4 5 6 7 8
3
6
4
6 7 8
*/