之前遞歸實(shí)現(xiàn)的方法可以看我前段時(shí)間的博客:
http://www.lxweimin.com/p/1a81f7a9d106
那么這次來(lái)寫(xiě)一下非遞歸如何實(shí)現(xiàn)前序,中序與后序遍歷。
前序,中序,后序非遞歸遍歷
思路:
- 利用STL適配器stack,也就是入棧出棧功能來(lái)實(shí)現(xiàn)。
- 實(shí)際寫(xiě)代碼時(shí),要注意跟著遍歷思路去走,走不通的時(shí)候適當(dāng)假設(shè)。
步驟:
- 首先先在原有BinaryTree.h頭文件中添加如下接口:
void PreNoRecursion(BtreeNode * root);//前序非遞歸遍歷
void InNoRecursion(BtreeNode * root);//中序非遞歸遍歷
void PostNoRecursion(BtreeNode * root);//后序非遞歸遍歷
- 之后在具體實(shí)現(xiàn)中寫(xiě)代碼,代碼如下:
void BinaryTree::PreNoRecursion(BtreeNode * root)
{
if(!root)
return;
stack<BtreeNode*> s;
while(!s.empty() || root)
{
while(root)
{
s.push(root);
cout << root->alpha << " ";
root = root->lchild;
}
if(!s.empty())
{
root = s.top();
s.pop();
root = root->rchild;
}
}
}
void BinaryTree::InNoRecursion(BtreeNode * root)
{
if(!root)
return;
stack<BtreeNode*> s;
while(!s.empty() || root)
{
while(root)
{
s.push(root);
root = root->lchild;
}
if(!s.empty())
{
root = s.top();
cout << root->alpha << " ";
s.pop();
root = root->rchild;
}
}
}
void BinaryTree::PostNoRecursion(BtreeNode * root)
{
if(!root)
return;
stack<BtreeNode*> s;
BtreeNode * p1 = root;
BtreeNode * pLast = NULL;
while(p1)
{
s.push(p1);
p1 = p1->lchild;
}
while(!s.empty())
{
p1 = s.top();
s.pop();
if (p1->rchild == NULL || p1->rchild == pLast)
//注意此時(shí)左子樹(shù)已經(jīng)到頭了,下面沒(méi)有了,只需判斷右子樹(shù)有沒(méi)有遍歷過(guò)。
//如果有就可以打印了。
{
cout << p1->alpha << " ";
pLast = p1;
}
else//else if(p1->lchild == pLast)
{
s.push(p1);
p1 = p1->rchild;
while(p1)
{
s.push(p1);
p1 = p1->lchild;
}
}
}
}
那么,我們分別分析一下前序,中序,后序遍歷時(shí)應(yīng)注意的細(xì)節(jié)。
前序非遞歸遍歷
- 首先建立Stack模板類(lèi)容器,用于對(duì)BtreeNode*類(lèi)型壓棧出棧的操作。用棧的操作模擬遍歷操作(遍歷與棧都是先進(jìn)后出型操作)
- 先建立大的循環(huán)體,也就是當(dāng)棧內(nèi)有元素或是根節(jié)點(diǎn)有值時(shí)就進(jìn)行操作。(到最后棧內(nèi)有元素時(shí)可能已經(jīng)打印完畢,但我們要出棧,釋放資源)
- 從根結(jié)點(diǎn)開(kāi)始,每遍歷一個(gè)數(shù)就打印,用while循環(huán)體結(jié)構(gòu),一直遍歷到結(jié)點(diǎn)沒(méi)有左孩子為止,循環(huán)體結(jié)束。此時(shí)root為葉子結(jié)點(diǎn)的空左孩子,方便跳出循環(huán)。
- 此時(shí)上一子樹(shù)的根結(jié)點(diǎn)左孩子已經(jīng)遍歷且為空,那么繼續(xù)遍歷該根結(jié)點(diǎn)的右孩子。此時(shí)有兩種情況:
1)如果為空結(jié)點(diǎn),那么跳過(guò)上面while循環(huán),繼續(xù)彈出棧返回上一層結(jié)點(diǎn),尋找上一層結(jié)點(diǎn)的右孩子。
2)如果不為空結(jié)點(diǎn),那么執(zhí)行上面while語(yǔ)句,再對(duì)這個(gè)結(jié)點(diǎn)的所有子樹(shù)做前序遍歷。如果不能理解,將這個(gè)結(jié)點(diǎn)看作整棵樹(shù)的根結(jié)點(diǎn),那么我們上面分析的都是從樹(shù)的根結(jié)點(diǎn)出發(fā)的,只是出發(fā)點(diǎn)的不同罷了。代碼都是一樣的。
中序非遞歸遍歷
中序與前序非常相像,唯一的變化便是cout位置的變化,因?yàn)樗菍?duì)結(jié)點(diǎn)先遍歷左孩子,然后打印該結(jié)點(diǎn),再遍歷右孩子。
- 先遍歷結(jié)點(diǎn)所有左孩子,此時(shí)指向最左端葉子結(jié)點(diǎn)的空左孩子(NULL)。
- 進(jìn)入if語(yǔ)句,將棧頂元素(即那個(gè)左孩子)賦值給root,此時(shí)沒(méi)有左孩子可以遍歷了,因此根據(jù)中序遍歷的原理先打印結(jié)點(diǎn)。然后彈出棧(可以看成遞歸返回了上一級(jí)),再遍歷這個(gè)結(jié)點(diǎn)的右孩子,此時(shí)有兩種情況:
1)右孩子里有左孩子或只有右孩子,那么像前序遍歷一樣把這個(gè)右孩子看成根結(jié)點(diǎn)(上面有提到這個(gè)思路),利用while和if進(jìn)行中序遍歷。
注意:打印完的結(jié)點(diǎn)就要從棧內(nèi)彈出了,因?yàn)楸闅v結(jié)束且打印了,它就沒(méi)有繼續(xù)在棧內(nèi)存在的必要了,只有未打印的結(jié)點(diǎn)才能留在棧內(nèi)。
2)右孩子為空,那么這個(gè)結(jié)點(diǎn)執(zhí)行完畢返回上一層結(jié)點(diǎn)(在遞歸中為返回上一級(jí)遞歸,在非遞歸中,即為跳出while循環(huán),繼續(xù)在if程序塊中彈棧并打印上一級(jí)根結(jié)點(diǎn))。
后序非遞歸遍歷
后序遞歸遍歷稍微有些復(fù)雜,不過(guò)我們稍微分析一下就很簡(jiǎn)單了。
后序遍歷需要額外建立一個(gè)BTreeNode*類(lèi)型的變量pLast,用來(lái)指向上一個(gè)遍歷的結(jié)點(diǎn),初始值為NULL。之所以這么做,是因?yàn)榍耙粋€(gè)遍歷的結(jié)點(diǎn)如果是一個(gè)結(jié)點(diǎn)的左孩子,那么就須跳過(guò)它的根結(jié)點(diǎn),先遍歷右結(jié)點(diǎn);如果是一個(gè)結(jié)點(diǎn)的右孩子,那么直接打印該根結(jié)點(diǎn)。
那么,為什么要這么判斷呢?舉一個(gè)簡(jiǎn)單的例子。
二叉樹(shù)舉例.png
這是一個(gè)非常簡(jiǎn)單的二叉樹(shù)模型,由這個(gè)模型我們可以知道:
1)前序遍歷不存在先遍歷下兩個(gè)孩子的情況。
2)中序遍歷在之前遍歷的只能是B,而不可能是C。
因此,先遍歷左右孩子都有可能的只能是后序遍歷,所以我們要分情況討論。
步驟:
- 首先依舊是先遍歷到最左下角。
- p1就表示當(dāng)前結(jié)點(diǎn)(即上面兩個(gè)的root),棧頂元素賦值給它。賦值完就彈出。
- 判斷它的右孩子是不是上一個(gè)遍歷過(guò)的也即pLast或?yàn)榭眨绻沁@兩種情況,那么根據(jù)后序遍歷思想,可以打印該結(jié)點(diǎn)。然后根據(jù)遞歸思想,將當(dāng)前結(jié)點(diǎn)設(shè)置成上一個(gè)結(jié)點(diǎn)。
- 判斷它的左孩子是不是上一個(gè)遍歷過(guò)的,如果是,它的結(jié)點(diǎn)原來(lái)被棧彈出,這時(shí)要重新壓入。再遍歷它的右孩子,再將當(dāng)前右孩子看做樹(shù)的根結(jié)點(diǎn),再后序遍歷。于是就有了下面的那段與上面相同的循環(huán)代碼。