資料結構之遍歷二叉樹

顧小豆發表於2018-02-24

遍歷二叉樹

在二叉樹的一些應用中,常常要求在樹中查詢具有某種特徵的結點。這就提出遍歷二叉樹的問題,即如何按某條搜尋路徑巡訪樹中每個結點,使得每個結點均被訪問一次,而且僅被訪問一次。遍歷對線性結構來說,是一個容易解決的問題。而對二叉樹則不然,由於二叉樹是一種非線性結構,每個結點都可能有兩顆子樹,因而需要尋找一種規律,以便使二叉樹上的結點能排列在一個線性佇列上,從而便於遍歷。

回顧二叉樹的遞迴定義可知,二叉樹是由3個基本單元組成:根結點、左子樹和右子樹。因此,若能依次遍歷這三部分,便是遍歷了整個二叉樹。例如以L、D、R分別表示遍歷左子樹、訪問根結點和遍歷右子樹,則可有DLR、LDR、LRD、DRL、RDL、RLD這6種遍歷二叉樹的方案。若限定先左後右、則只有3種情況,分別稱之為前(根)序遍歷、中(根)序遍歷和後(根)序遍歷。基於二叉樹的遞迴定義,可得下述遍歷二叉樹的遞迴演算法定義。

1. 前序遍歷二叉樹的操作:

若二叉樹為空,則空操作返回;否則

1.1.訪問根結點中的資料;

1.2.前序遍歷左子樹;

1.3.前序遍歷右子樹。


前序遍歷二叉樹程式碼如下:

// 前序遍歷二叉樹
void pre_order_traversal(BTreeNode* root)
{
    // 二叉樹不為空
    if( root != NULL )
    {
    	// 訪問根結點中的資料並列印
        printf("%c, ", ((struct Node*)root)->v);
        // 前序遍歷左子樹
        pre_order_traversal(root->left);
        // 前序遍歷右子樹
        pre_order_traversal(root->right);
    }
}

2.中序遍歷二叉樹的操作:

若二叉樹為空,則空操作返回;否則

2.1.中序遍歷左子樹;

2.2.訪問根結點中的資料;

2.3.中序遍歷右子樹。


中序遍歷二叉樹程式碼如下:

// 中序遍歷二叉樹
void middle_order_traversal(BTreeNode* root)
{
    // 二叉樹不為空
    if( root != NULL )
    {
    	// 中序遍歷左子樹
        middle_order_traversal(root->left);
        // 訪問根結點中的資料並列印
        printf("%c, ", ((struct Node*)root)->v);
        // 中序遍歷右子樹
        middle_order_traversal(root->right);
    }
}

3.後序遍歷二叉樹的操作:

若二叉樹為空,則空操作返回;否則

3.1.中序遍歷左子樹;

3.2.中序遍歷右子樹;

3.3.訪問根結點中的資料。


後序遍歷二叉樹程式碼如下:

// 後序遍歷二叉樹
void post_order_traversal(BTreeNode* root)
{
    // 二叉樹不為空
    if( root != NULL )
    {
    	// 後序遍歷左子樹
        post_order_traversal(root->left);
        // 後序遍歷右子樹
        post_order_traversal(root->right);
        // 訪問根結點中的資料並列印
        printf("%c, ", ((struct Node*)root)->v);
    }
}

從上述二叉樹遍歷的定義可知,3種遍歷演算法之不同處僅在於訪問根結點和遍歷左、右子樹的先後關係。

對二叉樹進行遍歷的搜尋路徑除了上述前序、中序和後序外,還可以從上到下、從左到右按層次進行。

4.層次遍歷二叉樹的操作:

若二叉樹為空,則空操作返回;否則

4.1. 訪問根結點中的資料

4.2. 訪問第二層所有結點的資料

4.3. 訪問第三層所有結點的資料

4.4. 。。。。。。

顯然,層次遍歷二叉樹不能使用遞迴的方法,那我們應該怎麼辦你?我們可以應用佇列來實現,佇列相關內容參考資料結構之鏈式佇列的優化。首先將根結點入列,然後迴圈查詢佇列中是否有元素,有元素就出列一次,再將出列元素代表的結點的左右子樹入列,周而復始,直至佇列中沒有元素為止。實現程式碼如下:

// 層次遍歷二叉樹
void level_order_traversal(BTreeNode* root)
{
    // 二叉樹不為空
    if( root != NULL )
    {
       // 建立佇列
       LinkQueue* queue = LinkQueue_Create();
       // 佇列建立成功
       if( queue != NULL )
       {
       	    // 將二叉樹根結點入列
            LinkQueue_Append(queue, root);
            // 佇列長度不為零,即佇列中有元素
            while( LinkQueue_Length(queue) > 0 )
            {
            	// 將結點元素出列
                struct Node* node = (struct Node*)LinkQueue_Retrieve(queue);
                // 列印出列的元素
                printf("%c, ", node->v);
                // 將結點的左右子樹入列
                LinkQueue_Append(queue, node->header.left);
                LinkQueue_Append(queue, node->header.right);
            }
       }
       // 銷燬佇列
       LinkQueue_Destroy(queue);
    }
}

驗證程式碼如下:

int main(int argc, char *argv[])
{
    // 建立二叉樹
    BTree* tree = BTree_Create();
    // 定義二叉樹內容
    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("Pre Order Traversal:\n");
    
    pre_order_traversal(BTree_Root(tree));
    
    printf("\n");    
    // 測試中序遍歷二叉樹功能
    printf("Middle Order Traversal:\n");
    
    middle_order_traversal(BTree_Root(tree));
    
    printf("\n");
    // 測試後序遍歷二叉樹功能    
    printf("Post Order Traversal:\n");
    
    post_order_traversal(BTree_Root(tree));
    
    printf("\n");    
    // 測試層次遍歷二叉樹功能
    printf("Level Order Traversal:\n");
    
    level_order_traversal(BTree_Root(tree));
    
    printf("\n");
    // 銷燬二叉樹
    BTree_Destroy(tree);
    
	return 0;
}

顯然,遍歷二叉樹的演算法中的基本操作是訪問結點,則不論按哪一種次序進行遍歷,對含有n個結點的二叉樹,其時間複雜度均為O(n)。所需輔助空間為遍歷過程中棧的最大容量,即樹的深度,在最壞情況下為n,則空間複雜度也為O(n)。

通過以上程式碼發現,二叉樹僅僅比單連結串列多了一個指標域,但其遍歷演算法的種類卻增加了很多 。遞迴定義的資料結構採用遞迴的演算法進行遍歷往往能達到簡單可靠的效果 。


遍歷二叉樹程式碼:遍歷二叉樹C實現程式碼


相關文章