1. 程式人生 > >《演算法導論》12.3節習題

《演算法導論》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
*/