1. 程式人生 > >資料結構入門--線索二叉樹

資料結構入門--線索二叉樹

當我們遍歷一棵二叉樹時,我們會得到整棵樹資料的一個序列(前or中or後),可以知道一個數據結點的前驅或者後繼是哪個結點。但是,對於每個結點的結構,我們只能從中得知某結點的左右孩子的資訊,要得到前驅/後繼結點的資訊就要遍歷一遍二叉樹,這樣豈不是很麻煩?我們能不能有一種方法,可以輕易的知道某個結點的前驅結點或後繼結點呢?答案顯然是肯定的

我們知道二叉樹的一個結點有兩個指標域,那麼對於n個結點的二叉樹就會有2n個指標域。而且n個結點的二叉樹存在n-1條分支數,也就是n-1個非空的指標域。剩餘的n+1個指標域都是NuLL。那麼既然有這麼多的空閒的指標,我們完全可以充分的利用它們。我們將空閒的指標域用來指向結點的前驅結點或後繼結點,這樣的二叉樹稱為線索二叉樹

。我們將一棵二叉樹以某種遍歷順序將其變為線索二叉樹的過程稱為線索化
如下圖的二叉樹
這裡寫圖片描述
這裡寫圖片描述
這是一箇中序線索化的例子,我們將空閒的左孩子指標(黑色弧線)用來指向結點的前驅,空閒的右孩子指標(紅色弧線)用來指向結點的後繼。
但現在出現了一個問題,我們如何知道一個指標指向的是孩子結點還是前驅後繼結點??所以我們要對結點的結構做一些手腳

/*線索二叉樹的結點結構定義*/
enum Tag
{
    child, thread  //       tag為child指向孩子結點,tag為thread指向線索結點
};
struct Node {
    char data;
    Tag lTag, rTag;
    Node *lchild, *rchild;
};

我們在結點結構中添加了lTag與rTag,分別標記左指標與右指標是孩子指標還是線索指標。
那麼修改了結點的結構,我們將線索化實現為具體的程式碼。我們知道線索二叉樹的目的是利用空閒的空指標,那麼線索化的過程也就是修改空指陣的過程。某種次序的線索化的程式碼與遍歷原理大致相同,如下的中序遍歷依然只是將Operation()改成線索化操作

/*中序線索化操作--遞迴,類似中序遍歷。pre始終指向上一個訪問的結點*/
void ThrBinTree::Threading(Node *&BT)
{
    if (BT != NULL)
    {
        Threading(BT->
lchild); // 線索化左子樹 /*左孩子為空,則這個空指標可作為線索指標指向前驅結點*/ if (BT->lchild == NULL) { BT->lTag = thread; BT->lchild = pre; } /*由於後繼結點還沒有訪問到,所以當前訪問的結點就作為pre的後繼結點*/ if (pre->rchild == NULL) { pre->rTag = thread; pre->rchild = BT; } pre = BT; // 更新pre Threading(BT->rchild); // 線索化右子樹 } } /*將樹線索化,加上頭結點*/ void ThrBinTree::InOrderThreading(Node *&h, Node *&BT) { h = new Node; // 生成頭結點 h->lTag = child; // 頭結點左孩子即是樹的根結點,所以左標記為child h->rTag = thread; // 頭結點右孩子指向中序遍歷的最後一個元素,是線索指標 h->rchild = h; // 初始化將h的右孩子回指 if (!BT) h->lchild = h; //如果樹為空,頭結點左孩子也回指 /*樹不為空*/ else { pre = h; // pre先指向h,這樣前序遍歷的第一個結點原本指向空,現在可以指向頭結點 h->lchild = BT; //根結點掛載到頭結點的左子樹 Threading(BT); //將樹線索化 pre->rchild = h; // 線索化後pre指向中序遍歷的最後一個結點,所以該結點的右孩子原本指空,現在作為線索結點指向頭結點 pre->rTag = thread; h->rchild = pre; // 頭結點的有孩子指向中序遍歷的最後結點pre } }

程式碼中添加了一個頭結點,我們將頭結點的左指標指向樹的根結點,右指標指向遍歷序列的最後一個結點,同時,我們將遍歷序列的第一個結點的左指標與序列最後一個結點的右指標均指向頭結點,這樣的設計是我們既可以從序列第一個結點通過前驅線索遍歷又可以從最後一個結點通過後繼線索遍歷。

/*中序線索遍歷*/
void ThrBinTree::InOrderThrTraverse(Node *&BT)
{
    Node *p = BT->lchild;   //  頭結點的右孩子為樹的根結點
    while (p != BT) //如果p不等於BT,迴圈 當p==BT時,樹為空樹或者遍歷完畢
    {
        while (p->lTag == child)    //  如果左標記為child,則一路指向左孩子
            p = p->lchild;
        cout << p->data << " "; // 訪問到中序遍歷的第一個元素
        while (p->rTag == thread&&p->rchild != BT)  //  結點為線索結點且結點右孩子不等於頭結點
        {
            /*遍歷線索路線*/
            p = p->rchild;
            cout << p->data << " ";
        }
        /*p的右標記是孩子標記,轉到右孩子繼續遍歷 或者 p的右孩子指向頭結點,p指向頭結點,遍歷結束*/
        p = p->rchild;
    }
}