今年暑假准備復習一下數據結構與算法
今天看到二叉樹這一節,發現自己對二叉樹的遍歷的非遞歸方法還沒有掌握,因此寫個博客記錄一下
typedef struct node
{
int value;
struct node * left;
struct node * right;
} *Binarytree;
首先先擺遞歸版本
前序遍歷
void r_pre_traverse(Binarytree b)
{
if (b!=NULL)
{
printf("%d\n",b->value);
pre_traverse(b->left);
pre_traverse(b->right);
}
}
其他遞歸遍歷方法只是換了個位置而已,就不放在這里占位置了
非遞歸循環版本
中序遍歷(中序遍歷比較簡單先寫這個)
首先我們來分析一下主要操作
- 尋找左子樹,將當前節點壓入棧,直到左子樹為空
- 從棧中彈出來一個節點,訪問他的值(出棧即代表訪問),然后切換到他的右子樹,將右子樹壓入棧
循環結束的情況分析
- 節點為空,棧為空
- 結束循環
- 節點不為空,棧為空
- 剛剛開始執行,函數還未進入循環體時
- 在循環過程中,棧為空即代表當前節點沒有父節點,所以此時執行到根節點,正准備切換到根節點的右子樹
- 也有可能在循環體的訪問最后一個右節點時出現
- 節點為空,棧不為空
- 棧不為空代表有父節點,節點為空,說明該節點為
st.top()
的右子樹
- 棧不為空代表有父節點,節點為空,說明該節點為
- 節點不為空,棧不為空
- 這個沒什么好說的
總體的流程大概是
while(b!=nullptr||!st.empty()){//右子樹為空且棧空時結束
while(b!=nullptr){//直到左子樹為空
執行1;
}
if(!st.empty()){
執行2;
}
}
丟代碼
void inoder_traverse(Binarytree b)
{
if (b==nullptr)
return;
stack<Binarytree > st;
while (b!=nullptr||!st.empty())
{
while (b!=nullptr)//節點不為空
{
st.push(b);//壓入當前節點
b=b->left;//切換到左子樹
}
//左子樹為空,且棧不為空的情況下切換到父節點
if(!st.empty()){
b=st.top();
st.pop();
//訪問節點
cout<<b->value<<endl;
//如果有右子樹,則切換到右子樹,沒有的話為空,再次循環時也會正常,所以不需要判斷
b=b->right;
}
}
}
前序遍歷
首先我們來分析一下主要操作
- 邊訪問節點邊輸出當前節點的值,並把節點存入棧中,然后訪問左子樹,直到訪問節點為空
- 如果棧不空,從棧中彈出節點,訪問他的右子樹
循環結束的情況分析
- 感覺情況和中序遍歷差不多的樣子,就不列舉了
總體的流程大概是
while(b!=nullptr||!st.empty()){
while(b!=nullptr){
執行1;
}
if(!st.empty()){
執行2;
}
}
丟代碼
void pre_traverse(Binarytree b)
{
if (b==nullptr)
return;
stack<Binarytree > st;
while (b!=nullptr||!st.empty())
{
while (b!=nullptr)//節點不為空
{
//訪問節點
cout<<b->value<<endl;
st.push(b);//壓入當前節點
b=b->left;//切換到左子樹
}
//左子樹為空,且棧不為空的情況下切換到父節點
if(!st.empty()){
b=st.top();
st.pop();
//如果有右子樹,則切換到右子樹,沒有的話為空,再次循環時也會正常,所以不需要判斷
b=b->right;
}
}
}
后序遍歷,這個好像有點復雜來着(有兩種思路,一種是節點增加一個參數用以確認是否第一次訪問,具體方法呢和上面兩種差不多,第二種是使用雙指針的方法,這里着重討論第二種)
具體思路
利用兩個指針 cur和pre進行操作,其中cur用來保存當前節點的指針,pre前一次訪問的節點(pre和cur沒有確定的父子關系).要保證根結點在左孩子和右孩子訪問之后才能訪問,因此對於任一結點P,先將其入棧。
- 如果P不存在左孩子和右孩子,則可以直接訪問它;
- 或者P存在左孩子或者右孩子,但是其左孩子和右孩子都已被訪問過了,則同樣可以直接訪問該結點。
- 若非上述兩種情況,則將P的右孩子和左孩子依次入棧,這樣就保證了每次取棧頂元素的時候,左孩子在右孩子前面被訪問,左孩子和右孩子都在根結點前面被訪問。
首先我們來分析一下主要操作
- 訪問節點 直接將節點壓入棧中
- 如果節點不存在子樹,直接訪問
- 如果節點存在節點,待按照順序訪問子節點后再從棧中彈出父節點進行訪問
循環結束的情況分析(循環開始前將根節點壓入)
- 節點為空,棧為空
- 空樹,直接過濾
- 節點不為空,棧為空
- 根節點輸出完畢,結束循環
- 節點為空,棧不為空
- 右端子樹訪問完畢,
- 節點不為空,棧不為空
- 這個沒什么好說的
總體的流程大概是
while(!st.empty()){
if((cur->left==nullptr&&cur->right==nullptr)||
((pre!=nullptr)&&(pre==cur->left||pre==cur->right))){//節點不存在子樹,或者子樹已經都被訪問過了
執行1;
}
else{
執行2;
}
}
丟代碼
void postorder_traversal(Binarytree b)
{
if (b==nullptr)
return;
Binarytree cur;//是指針哦 保存當前訪問的節點
Binarytree pre=nullptr;
stack<Binarytree > st;
st.push(b);//先把根節點壓入堆棧
while (!st.empty())//堆棧不空,堆棧空的時候表示根節點已經被訪問了
{
cur=st.top();//當前訪問的節點為棧頂節點 不彈出
//節點沒有子樹
if ((cur->left==nullptr&&cur->right==nullptr)||
(pre!=nullptr&&(pre==cur->left||pre==cur->right)))//或者節點的子樹都被訪問過了,即上一個訪問的節點是當前節點的子節點
{
//訪問當前節點
cout<<cur->value<<endl;
st.pop();//彈出堆棧 彈出就代表訪問了該節點
pre=cur;//為下一次訪問做准備
}
else//存在還未訪問的子樹,進行子樹的訪問
{
if (cur->left!=nullptr)
st.push(cur->left);
if (cur->right!=nullptr)
st.push(cur->right);
}
}
}
這個算法的實現我參考了這個大佬的博客:二叉樹的非遞歸遍歷