二叉樹的遍歷常見的分爲三種方式:前序遍歷、中序遍歷、後序遍歷。簡單的理解,所謂的前、中、後的不同實際上就是訪問根節點時機的不同。
本文默認的樹的結構如下表示:
typedef struct _BiNode
{
int data;
struct _BiNode *Lchild, *Rchild;
}Binode, *BiTree;
一、
對於樹的遍歷操作,常見的方式是採用遞歸的形式,此處不詳細介紹遞歸。常見的採用遞歸實現中序遍歷的思想就是在判斷完輸入的樹的節點符合函數要求後,優先調用便利函數訪問左子樹,在訪問打印根節點,最後再遞歸調用函數訪問右子樹。代碼如下:
//中序遍歷
bool InOrder(Binode *root)
{
if (root == NULL)
{
return false;
}
else
{
InOrder(root->Lchild);<span style="white-space:pre"> </span>//<span style="font-family: Arial, Helvetica, sans-serif;">首先訪問左子樹</span>
cout << root->data << endl;<span style="white-space:pre"> </span>//訪問根節點
InOrder(root->Rchild);<span style="white-space:pre"> </span>//訪問右子樹
return true;
}
}
二、
下面說一種不常用的方式——不採用遞歸。中序遍歷,顧名思義,根節點要在中間訪問,但不能保證是不是第幾次經過的時候訪問。舉例來說,第一次經過根節點並不訪問而在左子樹訪問完畢第二次經過根節點的時候才進行節點數據的訪問。所以,符合先經過後訪問,後經過先訪問的特點。那麼,很明顯,這個特點比較適合用棧結構來實現。畢竟棧最大的特點就是先入後出,後入先出。
在不考慮棧結構與代碼實現的前提下,本文選擇了調用STL庫中的棧來實現。
中序遍歷要從左子樹“最左”的節點開始訪問。那麼我們第一個任務就是找到這個結點:在這個過程中,我們把所有的有左子樹的節點入棧,直至找到第一個沒有左子樹的結點。代碼如下:
BiNode *GoFarLeft(BiNode *T, stack<BiNode *> &s)
{
if (T == NULL)
{
return NULL;
}
while (T->lchild)
{
s.push(T);
T = T->lchild;
}
return T;
}
1在找到了“最左”的節點以後,我們就可以開始遍歷工作。
2.1首先,“最左”的節點此時必定沒有左子樹,那麼此節點就是“本地”的根節點,因爲中序遍歷的特點,默認地訪問並打印這個結點。
2.2其次,中序遍歷最後訪問的是右子樹,那麼此時應該訪問“最左”結點的右子樹,有兩種結果:(1)存在右子樹,那麼爲了判斷右子樹中的節點是否還繼續擁有左節點,則應調用上方的函數繼續查找此處的“最左”結點,即重複步驟1,直至找到不存在右子樹的節點爲止。(2)如若不存在右子樹,那麼在已經訪問完左、根節點的前提下,可以認定這個結點已經訪問完畢。
2.3回退,將棧頂元素打印出棧,此節點爲“最左”節點的根節點,接着訪問右子樹,重複步驟1.
3.重複執行上述操作後即可遍歷完畢根節點的整個左子樹,打印根節點,再次同樣步驟遍歷右子樹,直到棧中元素爲空且沒有右子樹位置。
代碼如下:
void InOrder(BiNode *T)
{
stack<BiNode *> s;
//步驟1:一直往左走,找到中序遍歷的起點
BiTree t = GoFarLeft(T, s);
while (t)
{
printf("%d ", t->data); //中序遍歷打印
//如果t節點有右子樹,那麼重複步驟1
if (t->rchild != NULL)
{
t = GoFarLeft(t->rchild, s);
}
//如果t沒有右子樹,根據棧頂指示,訪問棧頂元素
else if (!s.empty())
{
t = s.top();
s.pop();
}
//如果t沒有右子樹,並且棧爲空
else
{
t = NULL;
}
}
}