面試100題:1.把二元查找樹轉變成排序的雙向鏈表

轉載並參考July的博客http://topic.csdn.net/u/20101126/10/b4f12a00-6280-492f-b785-cb6835a63dc9.html,萬分感謝!

題目

輸入一棵二元查找樹,將該二元查找樹轉換成一個排序的雙向鏈表。要求不能創建任何新的結點,只調整指針的指向。

        10

      /      \

    6        14

   /  \      /   \

 4   8   12  16

轉換成雙向鏈表4<=>6<=>8<=>10<=>12<=>14<=>16。

分析

要明白二元查找樹和雙向鏈表的各自特點

  1. 二元查找樹有左右子樹,左節點永遠小於根節點,右子樹永遠大於(等於)根節點。
  2. 二元查找樹的中序遍歷,會將樹按照從小到大有序排列。所以該題目應該考慮從中序遍歷着手,在中序遍歷的同時修改二元查找樹的左右子樹鏈接指向,從而構造一個排序的雙向鏈表。
  3. 二元查找樹的左右子節點可以作爲雙向鏈表的左鏈接和右鏈接的候選,只不過需要合理的調整它們使之指向正確的左右鏈接結點。

解一

利用中序遍歷,遞歸求解

/*Title: 1.把二元查找樹轉變成排序的雙向鏈表:解一
Author:  gocode
Date:    2012-09-27*/

/*       10
      /      \
     6        14
   /  \      /   \
  4    8    12   16    */

#include <stdio.h>
#include <iostream>
using namespace std; //如果不加std,則cout需要寫成std::cout

// 定義雙向鏈表的節點
typedef struct BSTreeNode
{
    int m_nValue; // 結點值
    BSTreeNode *m_pLeft; // 左節點
    BSTreeNode *m_pRight; // 右節點
} DoubleList;
// 定義全局變量
DoubleList *pHead;
DoubleList *pListIndex; // 輔助節點指針,指示雙向鏈表的當前節點

//void  convertToDoubleList(BSTreeNode * pCurrent); // 如果該函數放在ergodicBSTree函數之後,則此處必須聲明convertToDoubleList

// 二叉樹轉換成雙向鏈表
void  convertToDoubleList(BSTreeNode * pCurrent)
{
    // 當前pCurrent結點是後加入的節點,它永遠把pListIndex作爲左節點,
    // 因爲pListIndex是作爲雙向鏈表的當前節點指示器,所以pListIndex在pCurrent的左邊,相應的pListIndex的右節點就是pCurrent
    pCurrent->m_pLeft = pListIndex;
    if (NULL != pListIndex)
        pListIndex->m_pRight = pCurrent;
    else
        pHead = pCurrent; // 建立雙向鏈表的頭節點
 
    pListIndex = pCurrent; // 移動pListIndex指示到最新加入的節點
    cout<<pCurrent->m_nValue<<" ";
}

// 創建二元查找樹
void addBSTreeNode(BSTreeNode *& pCurrent, int value)
{
    // 當前節點爲空,則創建新節點並賦值
    if (NULL == pCurrent)
    {
        BSTreeNode * pBSTree = new BSTreeNode();
        pBSTree->m_pLeft = NULL;
        pBSTree->m_pRight = NULL;
        pBSTree->m_nValue = value;
        pCurrent = pBSTree;
    }
    // 當前節點不爲空,則與value比較大小,決定放在左還是右
    else
    {
        if ((pCurrent->m_nValue) > value)
            addBSTreeNode(pCurrent->m_pLeft, value);
        else if ((pCurrent->m_nValue) < value)
            addBSTreeNode(pCurrent->m_pRight, value);
        else
        {
            // 假定不允許有重複值節點存在
            // cout<<"重複加入節點"<<endl;
        }
    }
}

// 遍歷二元查找樹中序將按照從小到大排序,符合要求
void ergodicBSTree(BSTreeNode * pCurrent)
{
    if (NULL == pCurrent)
        return;
    // 左子樹不爲空,則遍歷左子樹
    if (NULL != pCurrent->m_pLeft)
        ergodicBSTree(pCurrent->m_pLeft);

    // 節點接到鏈表尾部
    convertToDoubleList(pCurrent);

    // 右子樹不爲空,則遍歷右子樹
    if (NULL != pCurrent->m_pRight)
        ergodicBSTree(pCurrent->m_pRight);
}

// 打印雙向鏈表
void displayDoubleList(DoubleList * pHead)
{
    pListIndex = pHead;
    while(NULL != pListIndex)
    {
        cout<<pListIndex->m_nValue<<" ";
        pListIndex= pListIndex->m_pRight;
    }
    cout<<endl;
}

int main()
{
    BSTreeNode * pRoot = NULL;

    addBSTreeNode(pRoot, 10);
    addBSTreeNode(pRoot, 4);
    addBSTreeNode(pRoot, 6);
    addBSTreeNode(pRoot, 8);
    addBSTreeNode(pRoot, 12);
    addBSTreeNode(pRoot, 14);
    addBSTreeNode(pRoot, 15);
    addBSTreeNode(pRoot, 16);

    cout<<"List the converting result: "<<endl;
    ergodicBSTree(pRoot);
    cout<<endl<<"List the converting DoublList: "<<endl;
    displayDoubleList(pHead);
    cout<<"End!"<<endl;
    getchar();
    return 0;
}

結果


解二

遞歸求解

/*Title: 1.把二元查找樹轉變成排序的雙向鏈表:解二
Author:  gocode
Date:    2012-09-27*/

/*       10
      /      \
     6        14
   /  \      /   \
  4    8    12   16  */

#include <iostream>
using namespace std;

// Node類
class Node{
public:
    int data;
    Node *left;
    Node *right;

    Node(int d = 0, Node *lr = NULL, Node *rr = NULL):data(d), left(lr), right(rr){}
};

// 創建二叉查找樹
// 先創建左右節點,然後創建父節點
Node *create()
{
    Node *root;

    /*Node *p7 = new Node(7);
    Node *p9 = new Node(9);
    Node *p8 = new Node(8, p7, p9);*/

    Node *p4 = new Node(4);
    Node *p8 = new Node(8);
    Node *p6 = new Node(6, p4, p8);

    Node *p12 = new Node(12);
    Node *p16 = new Node(16);
    Node *p14 = new Node(14, p12, p16);

    Node *p10 = new Node(10, p6, p14);
    root = p10;

    return root;
}

// 改變鏈接
Node *change(Node *p, bool asRight)
{
    if (!p)
        return NULL;

    // 遍歷左子樹
    // 把左孩子與父節點的關係調整成雙向鏈表的左右鏈接
    Node *pLeft = change(p->left, false);
    if (pLeft)
        pLeft->right = p;
    p->left = pLeft;

    // 遍歷右子樹
    // 把右孩子與父節點的關係調整成雙向鏈表的左右鏈接
    Node *pRight = change(p->right, true);
    if (pRight)
        pRight->left = p;
    p->right = pRight;

    // 最關鍵的一段程序,需要仔細理解
    // 如果是右孩子,並且有左子孩子,則一直移動當前指針到左葉子節點
    // 如果是左孩子,並且有右子孩子,則一直移動當前指針到右葉子結點
    // 比如在構建12<=>14<=>16的時候,節點14是根節點10的右孩子asRight==true,它有左子孩子,此時當前指針一直向左移動到最左子孩子12上,12是根節點的後繼
    // 比如在構建4<=>6=>8的時候,節點6是根節點10的左孩子asRight==false,它有右子孩子,此時當前指針一直向右移動到最右子孩子8上,8是根節點的前驅
    // 比如在構建6<=>10<=>14的時候,節點10是根節點asRight可爲ture或false,此時asRight==false,有右子孩子,此時當前指針一直向右移動到最右子孩子16上
    Node *r = p;
    if (asRight)
    {// 右子樹的開頭節點是根節點的後繼,即左葉子結點
        while (r->left)
            r = r->left;
    }else{// 左子樹的最末尾節點是根節點的前驅,即右葉子結點
        while (r->right)
            r = r->right;
        }
    return r;
}

void main(){
    Node *root = create();
    Node *tail = change(root, false); // 給出root節點,但是asRight==false,則返回二叉查找樹最末尾節點
    while (tail)
    {
        cout << tail->data << " ";
        tail = tail->left;
    }
    cout << endl;

    root = create();
    Node *head = change(root, true); // 給出root節點,但是asRight==ture,則返回二叉查找樹最開頭節點
    while (head)
    {
        cout << head->data << " ";
        head = head->right;
    }
    cout << endl;
    getchar();
}
結果

 

總結

解一在構建二叉查找樹使用了addBSTreeNode函數,此方法可以方便的自動建立二叉查找樹的關係。這比解二一個節點一個節點手工建立要省事,而且可以複用。解一利用中序遍歷時,拆解重構左右鏈接從而構造出排序的雙向鏈表。解二代碼量少,可以通過改變asRight變量控制雙向鏈表的輸出。這充分利用了鏈表的靈活性,更是建立在對二叉查找樹的遍歷和鏈表構建有充分的正確認識。從理解難易程度看,解一更好理解些。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章