《算法導論》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完畢。html

  • 12.3-3 由12.3-2可得,insert過程和search過程同樣,是O(h)-time的,h表明樹的高度。ios

    設一共有n個節點,構建一棵樹須要調用n次insert,這個過程耗時O(nh),後續的中序遍歷耗時是O(n)。因此排序過程的時間複雜度是由構建過程決定的。算法

    最壞的狀況集合按照正序或者倒序排列,則h = n,總時間是O(n^2)。性能

    最好狀況是集合按照層次遍歷順序排列,最後構建成一棵徹底二叉樹,h = lg(n) ,總時間爲O(n*lg(n))。ui

  • 12.3-4 從同一棵二叉搜索樹上先刪除x再刪除y,和先刪除y再刪除x,最終獲得的樹必定是同樣的嗎?spa

    不必定,舉個反例:翻譯

//如今有一棵樹,上面有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
//獲得的結果是不同的。

下面說說,我是怎樣想到這個反例的。code

要刪除x,根據刪除的規則,若是x沒有孩子,直接刪除;若是x只有一個孩子,就用惟一的孩子頂替x的位置;若是x有兩個孩子,就將x的後繼節點s頂替x的位置。htm

從這個規則中能夠發現,x有幾個孩子會影響到x的接班人人選。若是刪除的順序能夠影響到刪除x時候x的孩子個數,就會影響到最終樹的形狀。blog

那麼,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
*/
  • 12.3-6 當x有兩個孩子的時候,x的接班人y取x的前驅和後繼都是能夠的,具體實現方法是:用一個bool量控制下一個y取二者中的哪個,交替着選取。在y取後繼節點時,須要將y現有的右孩子託付給本身的父親(已經在《算法導論》12.3節習題 實現);若y取前驅節點,須要作的修改是把本身的左孩子託付給本身的父親。
相關文章
相關標籤/搜索