二叉樹的前中後序可以採用遞歸方式實現,但是當樹的層數較大時,遞歸效率就會降低,因此可以試着採用非遞歸方式進行樹的前中後序遍歷。
但樹的遍歷涉及到左右孩子,它不像鏈表那樣只有一個前驅後繼,因此我們需要藉助一些簡單的數據結構來幫助我們實現遍歷:
以下只給出功能實現部分的的代碼,其中樹的創建及遞歸遍歷配套代碼可以查看我的上一篇博文,c語言實現二叉樹的創建及遍歷
你可以將本次代碼和上篇的代碼合併驗證。
樹的層序遍歷
層序遍歷是對樹逐層遍歷,從上到下,自左向右遍歷
使用隊列作爲輔助空間時;具體思路是:先打印根節點,然後左孩子入隊,右孩子入隊(根據隊列的先入先出特點,左孩子在左部分,我們要從左向右打印,因此先將左孩子入隊)
其中記錄每一層的未遍歷節點數,每出隊一個節點打印,該層未遍歷節點數減 1 ,當減爲 0 時說明該層打印結束,此時隊列中保存的節點數就是下一層的節點個數。這樣就可以不斷層序遍歷
當使用棧作爲輔助空間的話,和上面實現不同之處在於棧是先入先出的特點,那麼就要先右孩子入棧,再左孩子入棧。其餘均相同。
代碼實現:
void BinaryTreeLevelOrder(BTNode* root)
{
assert(root!=NULL);
queue<BTNode*> qu;
qu.push(root);
int nLeveNum=1;//第一行節點個數爲1
while(!qu.empty())
{
BTNode*tmp=qu.front();
qu.pop();
printf("%d ",tmp->_data);
//printf("%c ",tmp->_data);
nLeveNum--;
if(tmp->_left)
{
qu.push(tmp->_left);
}
if(tmp->_right)
{
qu.push(tmp->_right);
}
if(nLeveNum==0)
{
nLeveNum=qu.size();
putchar('\n');
}
}
}
樹的前序遍歷
前序遍歷就是先遍歷根節點,再訪問左右子樹。
因此我們需要將右節點保存起來,當訪問完根節點和左子樹時,再去訪問右子樹;
具體思路:
- 不斷遍歷根節點,並在操作後將右孩子入棧,然後再進入根節點的左孩子。直到左孩子爲空,
- 然後取棧頂節點(上個根節點的右孩子)作爲根節點,再次執行步驟 1
- 當棧爲空時,說明右孩子遍歷結束了,那麼前序遍歷也結束
代碼實現:
void BinaryTreePrevOrderNonR(BTNode* root)
{
assert(root!=NULL);
stack<BTNode*> st;
st.push(root);
BTNode*cur=root;
while(!st.empty())
{
printf("%d ",cur->_data);
//printf("%c ",cur->_data);
if(cur->_right)
{
st.push(cur->_right);
}
if(cur->_left)
{
cur=cur->_left;
}
else
{
cur=st.top();
st.pop();
}
}//棧爲空且右孩子爲空時(樹中最右下角的節點處)遍歷結束
}
樹的中序遍歷
中序遍歷是先遍歷左子樹後遍歷根節點,再去遍歷右子樹
仍然藉助棧結構幫我們保存節點,我們需要先遍歷左子樹,但是同時還要保存根節點,當在遍歷根節點時將右孩子保存。
具體實現:
- 先將根節點入棧(保存根節點),再進入左孩子,
- 重複步驟 1,直到左孩子爲空,打印根節點,出棧,取棧頂(上一個根節點)
- 打印上一個根節點,出棧,進入該根節點的右孩子,該右孩子作爲根節點回到步驟 1
代碼實現:
void BinaryTreeInOrderNonR(BTNode* root)
{
assert(root!=NULL);
stack<BTNode*> st;
BTNode*cur=root;
while(cur||!st.empty())
{
for(;cur;cur=cur->_left)
{
st.push(cur);
}
cur=st.top();
st.pop();
printf("%d ",cur->_data);
//printf("%c ",cur->_data);
cur=cur->_right;
}
}
樹的後序遍歷
後續遍歷是在遍歷完左右子樹之後再遍歷根節點
同樣是藉助棧實現,可以這裏情況比較複雜,中序遍歷中我們可以保存根節點,然後再遍歷根節點之後遍歷右子樹,但是我們在後序,如果我們遍歷完右子樹,我們還能回到根節點嗎?由於我們實現的是二叉鏈的鏈式存儲結構,所以我們不能從右孩子直接回到根節點,那麼就需要其他辦法了!
既然不能從右子樹回到根節點,那麼我們就不讓根節點出棧,先讓它待在棧中,當左右子樹都遍歷結束之後再通知它出棧遍歷。那麼如何通知它呢?什麼時候通知呢?
我們可以在增加一個輔助空間來標記每個根節點的右子樹是否被遍歷,當標記未遍歷時,我們就讓他乖乖待在棧中,當我們遍歷它的右子樹時改變它的標記,當右子樹遍歷結束後,它發現自己的標記改變了(說明右孩子遍歷結束了)然後就可以出獄了!
具體實現:
- 根節點入棧,並標記爲false,進入左孩子;重複步驟一,直到左孩子爲空,
- 取棧頂(上一個根節點),同時判斷該節點的標誌位。 標誌位爲false,說明右孩子未遍歷,去步驟三;如果標誌位爲true,打印該根節點,出棧。重複步驟二
- 將棧頂節點的標記改變爲true(說明該根節點的右孩子被遍歷),進棧頂節點的右孩子,右孩子作爲根節點回到步驟一
- 當棧爲空時(整顆樹的根節點被打印出棧)結束!
void BinaryTreePostOrderNonR(BTNode* root)
{
assert(root);
stack<BTNode*> st;
stack<bool> flag;//設標誌,標誌該節點是否已經被訪問過
BTNode*cur=root;
while(cur||!st.empty())
{
for(;cur;cur=cur->_left)
{
st.push(cur);
flag.push(false);
}
while(!st.empty()&&(flag.top()==true))
{
cur=st.top();
flag.pop();
st.pop();
printf("%d ",cur->_data);
//printf("%c ",cur->_data);
cur=NULL;
}
if(!st.empty())
{
cur=st.top();
flag.top()=true;
cur=cur->_right;
}
}
}
執行結果: