資料結構之線索化二叉樹

顧小豆發表於2018-02-24

線索化二叉樹 

在一些專案中需要頻繁的遍歷二叉樹,但是二叉樹的遍歷比單連結串列的遍歷複雜多了,並且遞迴總是會有額外開銷。。。

能不能像連結串列那樣方便的快速遍歷二叉樹呢?

線索化二叉樹指的是將二叉樹中的結點進行邏輯意義上的“重排列”,使其可以線性的方式訪問每一個結點。

二叉樹線索化之後每個結點都有一個線性下標,通過這個下標可以快速訪問結點,而不需要遍歷二叉樹。

這個感覺很像組織連結串列,但是大家要知道組織連結串列結點之間的先後關係是沒有任何邏輯關係的,只是為了插入、刪除等操作方便。

那麼我們應該怎樣線索化二叉樹呢?方法有二:

線索化方法1

利用結點中的空指標域,使其指向後繼結點。這也是教科書式的方法,許多教科書都會這麼講。


演算法思想:

1.初始化位置指標p = NULL;

2.前序遍歷二叉樹:

    2.1.若p不為空,將p->left指向當前結點,並將p置為NULL;

    2.2.若當前結點的左子樹為空時,將p指向當前結點。

由以上演算法我們可知,線索化的實質就是將二叉連結串列中的空指標改為指向前驅或後繼的線索,而前驅或後繼的資訊只有在遍歷才

能得到,因此線索化的過程即為在遍歷的過程中修改空指標的過程。

實現程式碼如下:

// 通過空指標線索化二叉樹
void thread_via_left(BTreeNode* root, BTreeNode** pp)
{
    // 入口引數合法性檢查OK
    if( (root != NULL) && (pp != NULL) )
    {
    	// 若pp不為空,將pp->left指向當前結點,並將pp置為NULL
        if( *pp != NULL )
        {
            (*pp)->left = root;
            *pp = NULL;
        }
        // 若當前結點的左子樹為空時,將pp指向當前結點
        if( root->left == NULL )
        {
            *pp = root;
        }
        // 遞迴線索化(遍歷)左右子樹
        thread_via_left(root->left, pp);
        thread_via_left(root->right, pp);
    }
}

既然是前序遍歷,首先就得有前序遍歷的框架,即1.訪問根結點中的資料;2.前序遍歷左子樹;3.前序遍歷右子樹。

線索化方法2

利用線性表儲存二叉樹的遍歷順序。此方法異常簡單。

 

演算法思想:(感謝國嵌嵌入式工作者們)

1.初始化順序表s1

2.前序遍歷二叉樹,遍歷過程中將當前結點插入順序表s1

實現程式碼如下:

// 通過順序表線索化二叉樹
void thread_via_list(BTreeNode* root, SeqList* list)
{
    // 入口引數合法性檢查OK
    if( (root != NULL) && (list != NULL) )
    {
    	// 在順序表尾部插入順序表,訪問根結點資料
        SeqList_Insert(list, (SeqListNode*)root, SeqList_Length(list));
        // 線索化(前序遍歷)左子樹
        thread_via_list(root->left, list);
        // 線索化(前序遍歷)右子樹
        thread_via_list(root->right, list);
    }
}

測試驗證程式碼如下:

int main(int argc, char *argv[])
{
    // 建立二叉樹
    BTree* tree = BTree_Create();
    // 定義二叉樹結點指標
    BTreeNode* current = NULL;
    BTreeNode* p = NULL;
    // 定義順序表變數
    SeqList* list = NULL;
    // 定義迴圈變數
    int i = 0;
    // 定義二叉樹內容
    struct Node n1 = {{NULL, NULL}, 'A'};
    struct Node n2 = {{NULL, NULL}, 'B'};
    struct Node n3 = {{NULL, NULL}, 'C'};
    struct Node n4 = {{NULL, NULL}, 'D'};
    struct Node n5 = {{NULL, NULL}, 'E'};
    struct Node n6 = {{NULL, NULL}, 'F'};
    // 插入二叉樹
    BTree_Insert(tree, (BTreeNode*)&n1, 0, 0, 0);
    BTree_Insert(tree, (BTreeNode*)&n2, 0x00, 1, 0);
    BTree_Insert(tree, (BTreeNode*)&n3, 0x01, 1, 0);
    BTree_Insert(tree, (BTreeNode*)&n4, 0x00, 2, 0);
    BTree_Insert(tree, (BTreeNode*)&n5, 0x02, 2, 0);
    BTree_Insert(tree, (BTreeNode*)&n6, 0x02, 3, 0);
    // 顯示整體二叉樹
    printf("Full Tree: \n");
    
    BTree_Display(tree, printf_data, 4, '-');
    
    printf("Thread via List:\n");
    // 驗證順序表線索化二叉樹
    list = SeqList_Create(BTree_Count(tree));
    
    thread_via_list(BTree_Root(tree), list);
    
    for(i=0; i<SeqList_Length(list); i++)
    {
        printf("%c, ", ((struct Node*)SeqList_Get(list, i))->v);
    }
    
    printf("\n");
    
    // 驗證結點空指標線索化二叉樹
    printf("Thread via Left:\n");
    
    current = BTree_Root(tree);
    
    thread_via_left(current, &p);
    
    while( current != NULL )
    {
        printf("%c, ", ((struct Node*)current)->v);
        
        current = current->left;
    }
    
    printf("\n");
    // 銷燬二叉樹
    BTree_Destroy(tree);
    
    return 0;
}

通過上面程式碼比較發現,利用結點空指標線索化的方法會破壞樹的結構,同時,利用結點空指標線索化二叉樹之後不能夠再恢

復。當然上面的問題可以通過在樹結點中加入一個線索化指標而得以解決,然而線索化指標的加入又會浪費記憶體空間,不夠靈

活。連結串列線索化方法不會破化樹的結構,不需要線索化時銷燬連結串列即可;連結串列線索化方法可以很容易的以任何一種遍歷順序對二

叉樹進行線索化 。

有的人會問第一種線索化二叉樹是否可以實現其他遍歷方法來實現,當然可以,不過由於會破壞二叉樹結構,並且不能在復位且

會浪費記憶體空間,所以不再講述。

線索化二叉樹整體實現程式碼:線索化二叉樹程式碼C實現



相關文章