二分搜尋樹系列之「深度優先-層序遍歷 (ergodic) 」

ice_moss發表於2021-05-19

一.遍歷的分類

二分搜尋樹遍歷分為兩大類,深度優先遍歷和層序遍歷。
深度優先遍歷分為三種:先序遍歷(preorder tree walk)、中序遍歷(inorder tree walk)、後序遍歷(postorder tree walk),分別為:
1、前序遍歷:先訪問當前節點,再依次遞迴訪問左右子樹。
2、中序遍歷:先遞迴訪問左子樹,再訪問自身,再遞迴訪問右子樹。
3、後序遍歷:先遞迴訪問左右子樹,再訪問自身節點。

二. 深度優先遍歷

  • 前序遍歷:先訪問當前節點,再依次遞迴訪問左右子樹。
  • 中序遍歷:先遞迴訪問左子樹,再訪問自身,再遞迴訪問右子樹。
  • 後序遍歷:先遞迴訪問左右子樹,再訪問自身節點。
    為了更好理解深度優先遍歷我們使用下圖模型:

二分搜尋樹系列之【 深度優先-層序遍歷 (ergodic) 】

1. 前序遍歷:

我們對二分搜尋樹中所有節點都分別標記3個點:
二分搜尋樹系列之【 深度優先-層序遍歷 (ergodic) 】
開始遍歷:
前序遍歷是對每一個節點第一次訪問的時候進行遍歷:
28
二分搜尋樹系列之【 深度優先-層序遍歷 (ergodic) 】
遍歷:28, 16
二分搜尋樹系列之【 深度優先-層序遍歷 (ergodic) 】

遍歷:28, 16, 13
二分搜尋樹系列之【 深度優先-層序遍歷 (ergodic) 】

遍歷:28, 16, 13
二分搜尋樹系列之【 深度優先-層序遍歷 (ergodic) 】

遍歷:28, 16, 13
二分搜尋樹系列之【 深度優先-層序遍歷 (ergodic) 】

遍歷:28, 16, 13, 22
二分搜尋樹系列之【 深度優先-層序遍歷 (ergodic) 】

遍歷:28, 16, 13, 22
二分搜尋樹系列之【 深度優先-層序遍歷 (ergodic) 】

遍歷:28, 16, 13, 22
二分搜尋樹系列之【 深度優先-層序遍歷 (ergodic) 】

遍歷:28, 16, 13, 22
二分搜尋樹系列之【 深度優先-層序遍歷 (ergodic) 】

依次類推 ……

最後完成整個前序遍歷:

遍歷:28, 16, 13, 22, 30, 29, 42
二分搜尋樹系列之【 深度優先-層序遍歷 (ergodic) 】

**程式碼實現(使用遞迴,c++實現)
在public中定義:

//前序遍歷,傳入節點,列印節點相應資訊
void preOrder() {
    return preOrder(root);
}

在private中定義:

//前序遍歷,以node為根節點的二分搜尋樹進行前序遍歷,列印節點相應資訊
void preOrder(Node *node) {
    if (node != NULL) {
        //不一定用列印,還可以對node->key和node->value進行操作
  cout << node->key << endl;

  preOrder(node->left);

  preOrder(node->right);
  }
}
2. 中序遍歷

按照前序遍歷的模型和順序,很容易看出中序遍歷就是在中間點的時候進行遍歷:(過程省略)
遍歷:13, 16, 22, 28, 29, 30, 42
如下圖:(可以看出由中序遍歷可以看出遍歷結果是有序的)

二分搜尋樹系列之【 深度優先-層序遍歷 (ergodic) 】
**程式碼實現(使用遞迴,c++實現)
在public中定義:

//中序遍歷,以節點為node的節點為根節點
void inOrder() {
    return inOrder(root);
}

在private中定義:

//中序遍歷,以node為根節點的二分搜尋樹進行前序遍歷,列印節點相應資訊
void inOrder(Node *node) {
    if (node != NULL) {
        inOrder(node->left);

  cout << node->key << endl;

      inOrder(node->right);
  }
}
3. 後序遍歷

一樣的邏輯,後序遍歷就是在第三個點時進行遍歷:(過程省略)
遍歷:13, 22, 16, 29, 42, 30, 28
如下圖:
二分搜尋樹系列之【 深度優先-層序遍歷 (ergodic) 】
後序遍歷有個重要的應用:二叉樹的銷燬(從子節點依次向上刪除)

**程式碼實現(使用遞迴,c++實現)
在public中定義:

//後序遍歷,以node為根節點的二分搜尋樹進行前序遍歷,列印節點相應資訊
void postOrder() {
    return postOrder(root);
}

在private中定義:

//後序遍歷,以node為根節點的二分搜尋樹進行前序遍歷,列印節點相應資訊
void postOrder(Node *node) {
    if (node != NULL) {
        postOrder(node->left);

        postOrder(node->right);

  cout << node->key << endl;
  }
}

下面我們來使用後序遍歷將二分搜尋樹銷燬:


//解構函式的實現,其本質是後序遍歷
    void distroy(Node *node) {
        if (node != NULL) {
            distroy(node->left);
            distroy(node->right);
            delete node;
            count--;
        }
    }

三. 廣度優先遍歷

1.介紹

二分搜尋樹的廣度優先(層序遍歷),即逐層進行遍歷,即將每層的節點存在佇列當中,然後進行出隊(取出節點)和入隊(存入下一層的節點)的操作,以此達到遍歷的目的。
透過引入一個佇列來支撐層序遍歷:

  • 如果根節點為空,無可遍歷;

  • 如果根節點不為空:

    • 先將根節點入隊;

    • 只要佇列不為空:

      • 出隊隊首節點,並遍歷;
      • 如果隊首節點有左孩子,將左孩子入隊;
      • 如果隊首節點有右孩子,將右孩子入隊;
2.具體資料

以下圖為例:
二分搜尋樹系列之[ 深度優先-層序遍歷 (ergodic) ]

  1. 我們使用一個佇列——front
    將28放入佇列中

出:空
入:28
佇列:28
遍歷情況:空
二分搜尋樹系列之[ 深度優先-層序遍歷 (ergodic) ]

出:28
入:16, 30
佇列:16, 30
遍歷情況:28
二分搜尋樹系列之[ 深度優先-層序遍歷 (ergodic) ]

出:16
入:13 ,22
佇列:30, 13, 22
遍歷情況:28, 16
二分搜尋樹系列之[ 深度優先-層序遍歷 (ergodic) ]

出:30
入:29 ,42
佇列: 13, 22, 29, 42
遍歷情況:28, 16, 30
二分搜尋樹系列之[ 深度優先-層序遍歷 (ergodic) ]

出:13
入:空
佇列: 22, 29, 42
遍歷情況:28, 16, 30, 13
二分搜尋樹系列之[ 深度優先-層序遍歷 (ergodic) ]

出:22
入:空
佇列: 29, 42
遍歷情況:28, 16, 30, 13, 22
二分搜尋樹系列之[ 深度優先-層序遍歷 (ergodic) ]

出:29
入:空
佇列: 42
遍歷情況:28, 16, 30, 13, 22, 29
二分搜尋樹系列之[ 深度優先-層序遍歷 (ergodic) ]

出:42
入:空
佇列: 空
遍歷情況:28, 16, 30, 13, 22, 29, 42
二分搜尋樹系列之[ 深度優先-層序遍歷 (ergodic) ]

遍歷完成:
遍歷情況:28, 16, 30, 13, 22, 29, 42
二分搜尋樹系列之[ 深度優先-層序遍歷 (ergodic) ]

3.程式碼實現(使用遞迴,c++實現)
//層序遍歷
void levelOrder(){
    queue<Node*> q;   //佇列d
    q.push(root);       //將root入隊
  //佇列不為空的情況
  while(!q.empty()){
        Node *node  = q.front();    //將佇列第一個元素取出
        q.pop();             //是刪除棧頂元素

        cout<<node->key<<endl;
       if(node->left)
            q.push(node->left);
       if(node->right)
            q.push(node->right);
  }
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章