1. 程式人生 > >二叉樹:後序,遞迴和非遞迴,應用(求祖先問題)

二叉樹:後序,遞迴和非遞迴,應用(求祖先問題)

1  宣告

2 後序

a 遞迴

void PostOrder(BiTree T) {
    if (T) {
        PostOrder(T->lChild);
        PostOrder(T->rChild);
        visit(T);
    }
}

b 非遞迴

 i)由於是後序遍歷,必須先訪問左右,再訪問根,只有通過設定棧儲存之前訪問過的節點,這樣才能在訪問完右子樹之後回到根節點。

ii)這樣,就有兩種情況訪問到根節點。一種是第一次訪問,另一種是已經訪問過子樹後回到根。 為了區分兩種情況,設定一個指標用於標識訪問過的節點。

void PostOrder_2(BiTree T) {
    if (!T) {
        Error();
        return;
    }
    
    std::stack<BiTNode*> st;
    BiTNode *p = T;
    BiTNode *flag = nullptr;    //標識被訪問過的節點
    
    while (p || !st.empty()) {
        if (p) {
            st.push(p);
            p = p->lChild;
        } else {
            p = st.top();       //取當前棧頂
            if (p->rChild && p->rChild != flag) {
                p = p->rChild;
            } else {
                st.pop();
                
                visit(p);
                
                flag = p;
                p = nullptr;  //經過迴圈條件後,直接再取棧頂
            }
        }
    }
}

3  應用

a 求給定某節點的所有祖先

i)思路分析:後序遍歷中,訪問到節點x時,當前棧中所有節點即為該節點的祖先。因此讀取棧中的內容即可。

               但因為STL棧只提供top()介面,不方便順序訪問。故此處,用vector模擬棧的實現。

ii)關鍵分析:一旦找到節點之後,列印棧中所有元素,直接返回。

void Ancestor(BiTree T, std::vector<BiTNode*>& v, BTElemType e) {
    if (!T) {
        Error();
        return;
    }
    v.clear();
    
    BiTNode *p = T;
    BiTNode *pFlag = nullptr;
    
    while (T || !v.empty()) {
        if (p) {
            if (p->data == e) {
                //找到e時,該vector中所有元素即為e的ancestor
                for (auto iter = v.begin(); iter != v.end(); ++iter) {
                    cout << (*iter)->data << " ";
                }
                return;
            } else {
                v.push_back(p); //模擬入棧
                p = p->lChild;
            }
        } else {
            p = *(v.end() - 1); //模擬取棧頂
            if (p->rChild && p->rChild != pFlag) {
                p = p->rChild;
            } else {
                v.pop_back();   //模擬出棧
                pFlag = p;
                p = nullptr;
            }
        }
    }
}

b 求給定兩個節點的最近公共祖先

i)思路分析:

先得到一個節點x的後序遍歷棧st1,把該棧拷貝到另一個棧中st2,作為該節點的後序遍歷棧

然後繼續遍歷,直到找到另一個元素y,此時st1棧中元素即為y的後序遍歷棧

最後,尋找兩個棧中最後一個相同的元素,即為兩個元素的最近公共祖先

ii)缺陷

該方法的主要缺陷是:x必須出現在y左邊。

程式碼如下:

//尋找最近公共祖先主程式
BTElemType CommonAncestor(BiTree T, BTElemType x, BTElemType y) {
    std::vector<BiTNode*> st1, st2;
    
    BiTNode *p = T;
    BiTNode *pFlag = nullptr;
    
    BTElemType result = '?'; //標識是否有公共祖先
    
    while (p || !st1.empty()) {
        if (p) {
            if (p->data == x) {
                //b把當前所有元素拷貝到st2中
                st2 = st1;
            }
            if (p->data == y){
                result = FindLastCommonElem(st1, st2, T);
                break;
            }
            st1.push_back(p);
            p = p->lChild;
        } else {
            p = *(st1.end() - 1);
            if (p->rChild && p->rChild != pFlag) {
                p = p->rChild;
            } else {
                st1.pop_back();
                pFlag = p;
                p = nullptr;
            }
        }
    }
    
    return result;  //呼叫者通過判斷 result == '?'來得出是否存在公共組選
}

//當找到x和y之後,查詢兩個棧中最後一個相同元素
BTElemType FindLastCommonElem(std::vector<BiTNode*>& st1, std::vector<BiTNode*>& st2, BiTree T) {
    if (st1.size() == 0 || st2.size() == 0) {   //x和y中至少有一個為根節點
        return T->data;
    }
    
    BiTNode *pResult = nullptr;
    
    //筆者起初想的用最簡單的辦法直接掃描兩個vector,然後找出最後一個公共元素,但時間複雜度是O(n^2),而此方法為O(n)
    
    if (st1.size() < st2.size()) {
        pResult = *(st1.end() - 1); //st1中最後一個元素即為公共元素
    } else if (st1.size() > st2.size()){
        pResult = *(st2.end() - 1); //st2中最後一個元素即為公共元素
    } else {
        auto iter1 = st1.begin();
        auto iter2 = st2.begin();
        for (; iter1 != st1.end(); ++iter1, ++iter2) {
            if ((*iter1)->data != (*iter2)->data) {
                break;
            }
        }
        pResult = *(iter1 - 1);
    }
    
    return pResult->data;
}

4 總結

1  後序遍歷的非遞迴程式碼給我的啟示:在適當的時候做標記,

2  一旦掌握了後序遍歷非遞迴,那麼其的應用可以根據模版,適當的修改,即可解決問題。