深入理解数据结构——树的遍历

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/puliao4167/article/details/88899855

概述

    树是一个种非线性数据结构,由于线性数据结构的访问时间太慢,因此推演出“树”结构,其一般操作复杂度都为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;
}

猜你喜欢

转载自blog.csdn.net/puliao4167/article/details/88899855