深入理解數據結構——樹的遍歷

概述

    樹是一個種非線性數據結構,由於線性數據結構的訪問時間太慢,因此推演出“樹”結構,其一般操作複雜度都爲O(logn)。樹的應用非常廣泛:如文件系統,計算表達式的值,數據存儲磁盤上的索引等。

    樹的遍歷有很多種方法,主要包括先序遍歷(遞歸、非遞歸),中序遍歷(遞歸、非遞歸),後序遍歷(遞歸、非遞歸),層次遍歷。樹的數據結構中主要包含:節點值,指向左孩子的指針和指向右孩子指針。

struct TreeNode{
    int value;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x):value(x),left(NULL),right(NULL){ }
};

先序遍歷

    先序遍歷就是在遍歷樹的過程中,先讀取根節點的數據,然後再向左子樹和右子樹遞歸。比較簡單的遞歸版本如下:

void preorder_recursion(TreeNode *t){
    if(t==NULL){
        return;
    }
    cout<<t->value;
    preorder_recursion(t->left);
    preorder_recursion(t->right);
    return;
}

    主要來看非遞歸版本。非遞歸版本DFS的話肯定要用到棧,當棧不爲空的時候,每次循環從棧中彈出一個節點,輸出該節點的值,然後分別將該節點的右節點和左節點壓入棧(注意順序不能更改),畫一下圖就知道了,當棧爲空的時候則結束循環。

void preorder_nonrecursion(TreeNode *root){
    stack<TreeNode*> s1;
    if(root==NULL){
        return ;
    }
    TreeNode *t=root;
    s1.push(t);
    while(!s1.empty()){
        TreeNode *tmp=s1.top();
        s1.pop();
        cout<<tmp->value<<" ";
        if(tmp->right!=NULL){
            s1.push(tmp->right);
        }
        if(tmp->left!=NULL){
            s1.push(tmp->left);
        }
        
    }
    return;
}

中序遍歷

    中序遍歷的順序是“左子樹—節點—右子樹”,遞歸版本也很簡單,只有更改輸出節點元素值語句的位置即可。

void inorder_recursion(TreeNode *t){
    if(t==NULL){
        return;
    }
    inorder_recursion(t->left);
    cout<<t->value<<" ";
    inorder_recursion(t->right);
}

    非遞歸版本也要用到棧,這裏就比較複雜。首先最重要的一點是中序遍歷時候,當遍歷到一個節點時候,如果這個節點有左孩子,則需一直向左遞歸遍歷,如果左孩子不爲空,則將其壓入棧中,等遞歸回來的時候再去遍歷這個節點的右孩子;如果左孩子爲空的時候,再去考慮棧是否爲空,如果棧不爲空則彈出一個節點,輸出該節點的值遍歷其右孩子。所以循環過程中不僅要判斷棧是否爲空,還需判斷該節點是否爲空節點(即遍歷到盡頭)。

void inorder_nonrecursion(TreeNode *root){
    if(root==NULL){
        return;
    }
    stack<TreeNode *> s;
    while (root != NULL || !s.empty()) {
        if (root != NULL) {
            s.push(root);
            root = root->left;
        }
        else {
            root = s.top();
            cout << root->value << " ";
            s.pop();
            root = root->right;
        }
    }
}

後序遍歷

    後序遍歷的順序是“左子樹—右子樹—節點”,遞歸式算法也很簡單。

void postorder_recursion(TreeNode *t){
    if(t==NULL){
        return;
    }
    postorder_recursion(t->left);
    postorder_recursion(t->right);
    cout<<t->value<<" ";
    return;
}

    重點來看非遞歸式後序遍歷。該算法的難點在於要先判斷到達一個節點的遍歷路徑是從左子樹到的,還是右子樹到的。如果是從左子樹到的就先要遍歷其右節點,如果是已經遍歷其右節點後回來的,則輸出該節點的值。所以要多申明一個變量存儲上一個遍歷的節點,並且如果要遍歷其右節點還需要將其重新插入棧。

void postorder_nonrecursion(TreeNode *root){
    if(root==NULL){
        return;
    }
    TreeNode *tmp=root;
    TreeNode *last=NULL;
    stack<TreeNode*> s;
    while(tmp){
        s.push(tmp);
        tmp=tmp->left;
    }
    while(!s.empty()){
        tmp=s.top();
        s.pop();
        if(tmp->right==NULL || tmp->right==last){
            cout<<tmp->value<<" ";
            last=tmp;
        }
        else{
            s.push(tmp);
            tmp=tmp->right;
            while(tmp){
                s.push(tmp);
                tmp=tmp->left;
            }
        }
        
    }
}

層次遍歷

    層次遍歷也比較簡單,BFS採用隊列即可。遇到一個節點將其加入隊列,如果隊列不爲空,則彈出一個節點,並將其的左右兒子加入隊列即可。

void level_nonrecursion(TreeNode *root){
    queue<TreeNode *> q;
    if(root==NULL){
        return;
    }
    q.push(root);
    while(!q.empty()){
        TreeNode *tmp=q.front();
        q.pop();
        cout<<tmp->value<<" ";
        if(tmp->left!=NULL){
            q.push(tmp->left);
        }
        if(tmp->right!=NULL){
            q.push(tmp->right);
        }
    }
    return;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章