C++ | 二叉樹前序、中序、後序遍歷的遞歸和非遞歸實現 +層序遍歷+深度優先遍歷

二叉樹的遍歷是學習二叉樹最基本卻極爲重要的一環。當你熟練掌握二叉樹的遍歷之後,你會發現很多題目都是建立在遍歷的基礎上來解決的。本文章就是爲了盤點一下各種遍歷算法的原理和實現。

前序遍歷

前序遍歷也叫先序遍歷(preorder),整個操作過程比較簡單,先訪問根結點,在訪問左子樹,左子樹訪問完之後訪問右子樹。

  1. 若二叉樹爲空,則什麼也不做
  2. 否則:
    2.1 訪問根結點
    2.2. 先序遍歷左子樹
    2.3. 先序遍歷右子樹

遞歸實現

void preOrder(BinTree* BT){
    if(BT){
        printf("%d ", BT->data);
        preOrder(BT->left);
        preOrder(BT->right);
    }
}

非遞歸實現

void preOrder(TreeNode* root){
    if(root == NULL) return;
    stack<TreeNode*> S;    // 初始化棧
    TreeNode* node = root; // 聲明遍歷指針
    while(node || !S.empty()){
    	if(node != NULL)}{  // 不斷訪問樹的根節點,並且存儲左子樹(若存在的話)
       		 cout << node->val << " ";   
 			 S.push(node);
        	 node = node->left;
    	}
    	else{ // 遇到空指針,訪問棧頂指針指向的右子樹結點
        	node = S.top()->right;
             S.pop();
    	}  
    }
}

中序遍歷

中序遍歷(inorder),整個操作過程也比較簡單。

  1. 若二叉樹爲空,則什麼也不做
  2. 否則:
    2.1 中序遍歷左子樹
    2.2. 訪問根結點
    2.3. 中序遍歷右子樹

遞歸實現

void preOrder(TreeNode* root){
    if(root != NULL){
        preOrder(root->left);
        cout << root->val << " ";
        preOrder(root->right);
    }
}

非遞歸實現

void preOrder(TreeNode* root){
    if(root == NULL) return;
    stack<TreeNode*> S;
    TreeNode* node = root;
    while(node!=NULL || !S.empty()){
    	if(node != NULL)}{
    	     // cout << node->val << " ";  先序遍歷:在push時訪問結點值
 			 S.push(node);
        	  node = node->left;
    	}
    	else{
            cout << node->val << " ";   // 中序遍歷:在彈出棧頂指針時訪問
        	node = S.top()->right;
             S.pop();
    	}  
    }
}

後序遍歷

後序遍歷(postorder),整個操作過程也比較簡單。

  1. 若二叉樹爲空,則什麼也不做
  2. 否則:
    2.1 後序遍歷左子樹
    2.2. 後序遍歷右子樹
    2.3 訪問根結點

後序遍歷的遞歸實現比較容易,而非遞歸實現則比較難。

遞歸實現

void preOrder(TreeNode* root){
    if(root != NULL){
        preOrder(root->left);
        preOrder(root->right);
        cout << root->val << " ";
    }
}

非遞歸實現

非遞歸實現比較難,同樣的可以使用棧來存儲臨時值,但是我們需要區分返回根結點時,是從左子樹返回還是右子樹返回的。

// 先訪問左子樹,然後訪問右子樹,再訪問根節點
// 使用堆棧來存儲結點指針,需要分清返回根結點時是左子樹或者右子樹
// 我們使用一個輔助指針r來指向最近訪問的結點,藉助這個指針來區分根節點是從左子樹還是右子樹返回的。 
void postOrder(BiTree* root){
    // 初始化一個棧 S
    initStack(S);
    // 聲明一個遍歷指針 p
    BiTree* p = root;
    // 聲明一個空的輔助指針
    BiTree* r = NULL;
    while(p ||! S.empty()){
        if(p){ // 非空結點,壓棧
            push(S, p); // 壓棧,走左子樹
            p = p->left;
        }
        else{ // 空結點
            getTop(S, p);    // 取出棧頂結點賦值給p
            if(p->right && p->right != r){  // 存在右子樹,並且未訪問過
                p = p->right;   // 取右子樹根結點,入棧
                push(S,  p);
                p = p->left;
            }
            else{  // 不存在右子樹或者 已經訪問完右子樹
                pop(S, p);      // 彈出棧頂指針並賦值給p
                visit(p->val);  // 訪問結點值
                r = p;          // 記錄最近訪問的結點
                p = NULL;       // 將p置爲空
            }
        }
    }
}

層序遍歷

層序遍歷的思路也比較簡單易懂,屬於廣度優先的一種方法,在訪問結點之後,我們使用隊列來存儲一些左右結點指針。

void levelOrder(TreeNode* root){
    if(root == NULL) return;
    queue<TreeNode*> Q;
    Q.push(root);
    while(!Q.empty()){
        TreeNpde* node = Q.front();
        Q.pop();
        cout << node->val << " ";
        if(node->left != NULL)
            Q.push(node->left);
        if(node->right != NULL)
            Q.push(node->right);
    }
}

深度優先遍歷

深度優先的思路也不難,思路類似層序遍歷,但不同的是使用了棧來存儲臨時指針。

void depthOrder(TreeNode* root){
    if(root == NULL) return;
    stack<int> S;
    S.push(root);
    while(!S.empty()){
       	TreeNode* node = S.pop();
       	cout << node->val << " ";
        if(node->left != NULL)
            S.push(node->left);
        if(node->right != NULL)
            S.push(node->right);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章