1. 程式人生 > >二叉樹經典問題——已知中序和前序重建二叉樹

二叉樹經典問題——已知中序和前序重建二叉樹

運用前序和中序序列重建二叉樹及其相關應用

重建過程
1,在二叉樹的學習中經常會遇到一類問題,就是給出一棵二叉樹的前序和中序序列(後序和中序類似)然後求樹的深度、樹的後序序列、樹的各種遍歷等等問題,這個時候如果能根據相關的序列把其代表的二叉樹重建出來,那麼所有的問題便會迎刃而解。博文的第一部分就給出相關的重建步驟。
2,重建中最關鍵的一點是從前序中找根然後在後序中用相應的根把樹‘分解’。舉個例子:
這裡寫圖片描述
上面這個二叉樹對應的3種遍歷序列如下:

PreOrder: GDAFEMHZ

InOrder: ADEFGHMZ

PostOrder: AEFDHZMG
1,因為前序遍歷的第一個節點一定是一個二叉樹的根,所以從前序的第一個資料開始也就是G,把G對映到中序序列中,並記下在中序序列中的位置(這個位置十分重要!這個位置如果不在中序序列的最左端說明:前序序列中G的下一個資料必定是G的左子樹),又因為在中序序列中是按照leftChild—root—rightChild的方式遍歷的所以在中序中以上面記下的位置為分界,得到以G為根的左右子樹(分別是ADEF和HMZ)。(結合上面的圖示容易理解)
2,上面第一步只是把整個二叉樹分出左右子樹,然後再在前序中找到下一個資料也就是D,再把D在中序中對應的位置記錄下來,此時,D的位置並不在中序序列的最左端(最左端是A),也就說明D還可以繼續向下‘派生’左子樹,那麼繼續訪問前序序列中的下一個元素也就是A。
3,同樣的步驟,A在中序序列中的位置處於最左端(即A的左子樹長度為0),這說明A不能夠再有左子樹,此時便可以把A的左子樹置為nullptr,這時候再考慮A的右子樹是否存在,因為在中序序列中A的右面是D,但是D是A的父節點(這個地方看程式碼會更易於理解,因為程式碼中明確的顯示出A的右子樹長度也為0),所以A的右子樹置為nullptr。
4,類似的方式再考慮D的右子樹存在問題,如果右子樹長度不是0,那麼就在前序中選出相應的資料……
5,……
整體看上述步驟,其實是一個遞迴的過程(結合下面的程式碼看上面的解釋更能體會出遞迴的思想),下面結合程式碼做簡要的解釋:
根據前序中序序列建立二叉樹函式


理解上面這個遞迴函式有兩種方式,一種是通過編譯器的除錯功能,輔助你進入整個遞迴過程(畢竟測試資料長度有限,浪費不了你多長時間),我這裡用的是Visual Studio 2013的除錯,例如像下面這樣:
這裡寫圖片描述
通過呼叫堆疊的視窗你可以看到遞迴具體的過程和細節,遞迴深度及各個函式的執行順序,圖中的遞迴深度已經到達3.
這裡寫圖片描述
通過變數的監視視窗,你可以實時的鎖定某個變數當前的狀態從而可以驗證你的一些猜測與想法。甚至你可以開啟某個變數的層級目錄,通過層級目錄的方式來理解二叉樹的指標指向關係。
上面這幾個工具視窗非常實用,儘管我下面要用另一種方式來理解這個遞迴函式,但是我依舊鼓勵你用這種方式來自己總結出遞迴的真正細節,這是一個極棒的體驗!這樣你會在之後的遞迴程式中迅速掌握各種遞迴過程,而不需要來我這裡浪費時間(^_^)。
另一種方式是不深入遞迴的具體函式、變數呼叫的過程,就是隻看最外層的函式使用。這樣會有一個缺點,就是感覺自己不能真正掌控整個程式,有一種模糊的感覺。當然這擺脫了讓大腦遭受超強的負荷,更易於理解遞迴的實現和功能。
像上面的那個函式,我們只來想整個二叉樹

1)如果二叉樹的長度是0,毫無疑問直接返回nullptr。
2)不是0,我們應該開闢一塊節點的記憶體,並且把前序序列中的第一個資料(必定是根)裝進去。
3)然後我們要通過迴圈找出這個根資料在中序序列中的位置,並記錄在rootIndex裡面。
4)我們終於來到了遞迴的門口,函式要開始呼叫他自己了!rootIndex不在中序的最左端,也就是根存在左子樹了,我們讓node–>leftChild開闢並裝入前序序列的根的下一個資料。我才不會繼續想他的下一層遞迴呢!
5)挺快,整個二叉樹的左子樹我們完成了(你也許感覺有些唐突,那不怪我,我已經在第一種方式中說過),我們接下來要看G的右子樹是否存在。注意看
node->rightChild = CreateBinTreeByPreInOrder(preSeq + rootIndex + 1, InSeq + rootIndex + 1, subStrLen - (rootIndex + 1));
這個函式的引數,對於整個二叉樹來說,rootIndex此時是4(即G在中序序列中的下標),preSeq作為一個頭指標加上左子樹的長度(即rootIndex + 1),當然是右子樹的第一個節點的資料,自然也應該把搜尋的區間縮減到只在右子樹的部分(即InSeq + rootIndex + 1),最後確定長度,整個序列的長度減去左子樹的長度自然是右子樹的總長度(即subStrLen - (rootIndex + 1));這樣只要想象每次進入一棵子樹他都會像對待整棵樹那樣對待每一棵子樹,那麼我們的目的就達到了!
下面是完整的程式碼

/*
*已知前序遍歷序列和中序遍歷序列建立二叉樹
*並求其後序遍歷序列和層序遍歷序列
*/
#include <iostream>
#include <string>
#include <queue>
#include <string.h>
//二叉樹節點結構定義
struct BIN_NODE {
    char data;
    BIN_NODE *leftChild, *rightChild;
};

//序列儲存變數
//使用者輸入字串測試
//char preSequence[100];
//char inSequence[100];
//指定字串測試
char *preSequence = "GDAFEMHZ";
char* inSequence = "ADEFGHMZ";
//樹根定義
BIN_NODE *tree;
//儲存下一層節點的佇列
std::queue<BIN_NODE *> memoryNextLevel;

//函式宣告
void PrintBinTreeByPostOrder(BIN_NODE *subTree);        //後序列印二叉樹資料域
void PrintBinTreeByLevelOrder(BIN_NODE *subTree);       //層序列印二叉樹資料域
//通過前序、中序序列建立二叉樹
BIN_NODE * CreateBinTreeByPreInOrder(char* preSeq, char* InSeq, int subStrLen);

int main()
{
    int groupsAmount = 0;
    std::cin >> groupsAmount;
    while (groupsAmount--) {
        //std::cin >> preSequence >> inSequence;
        tree = CreateBinTreeByPreInOrder(preSequence, inSequence, strlen(inSequence));
        PrintBinTreeByPostOrder(tree);
        std::cout << std::endl;
        PrintBinTreeByLevelOrder(tree);
        std::cout << std::endl;
    }

    return 0;
}

//函式定義

BIN_NODE * CreateBinTreeByPreInOrder(char* preSeq, char* InSeq, int subStrLen) {
    if (0 == subStrLen) {
        return nullptr;
    }
    BIN_NODE *node = new BIN_NODE;
    if (node == nullptr) {
        std::cerr << "error" << std::endl;
        exit(1);
    }
    node->data = *preSeq;
    //前序相應元素在中序中的下標索引值
    int rootIndex = 0;                  
    //求解這個索引值
    for (; rootIndex < subStrLen; rootIndex ++) {
        if (InSeq[rootIndex] == *preSeq) {
            break;
        }
    }
    node->leftChild = CreateBinTreeByPreInOrder(preSeq + 1, InSeq, rootIndex);
    node->rightChild = CreateBinTreeByPreInOrder(preSeq + rootIndex + 1, 
        InSeq + rootIndex + 1, subStrLen - (rootIndex + 1));
    return node;
}

相信你也覺得我說的相當玄乎(哈哈),但是我的水平也只能講到這個程度,我沒跟你說過你可以用方式一嗎?(自力更生的方法)。到此我們便可以重建出這兩個序列所代表的二叉樹。下面我們來看看有哪些簡單的二叉樹操作問題在等著我們。
請看下一遍博文<<<<
參考及引用出自:http://blog.csdn.net/feliciafay/article/details/6816871