篇一:二叉樹-遍歷終極版
篇二:二叉樹-創建、重建、轉化
篇三:二叉樹-詳解二叉排序樹
篇四:二叉樹-詳解平衡二叉樹AVL
篇五:二叉樹-常見簡單算法題
對於二叉樹的遍歷,最熟悉的就是遞歸遍歷了,對二叉樹的非遞歸遍歷大致知道一些,但是不太熟悉,尤其是後續非遞歸遍歷的實現,一直比較懵逼,於是上網查詢了一下,果然大神無處不在,那個後序遍歷的雙棧法,簡直讓人拍案叫絕,下面總結下。
1、先序遍歷
先序遍歷即順序爲:根節點->左節點->右節點
遞歸版:
void PreOrderTraverse(BTree T)
{
if (T != NULL)
{
cout << T->val;
PreOrderTraverse(T->left);
PreOrderTraverse(T->right);
}
}
非遞歸版:
根據前序遍歷訪問的順序,優先訪問根結點,然後再分別訪問左孩子和右孩子。即對於任一結點,其可看做是根結點,因此可以直接訪問,訪問完之後,若其左孩子不爲空,按相同規則訪問它的左子樹;當訪問完其左子樹後,再訪問它的右子樹。因此其處理過程如下:
對於任一結點P:
1)訪問結點P,並將結點P入棧;
2)判斷結點P的左孩子是否爲空,若不爲空,則將P的左孩子置爲當前的結點P;若爲空,則取棧頂結點並進行出棧操作,並將棧頂結點的右孩子置爲當前的結點P,循環至1)
3)直到P爲NULL並且棧爲空,則遍歷結束。
代碼如下:
void PreOrderTraverse(BTree T)
{
std::stack<BTree> S;
while(T||!S.empty())
{
while(T)
{
S.push(T);
cout<<T->val;
T=T->left;
}
if(!S.empty())
{
T=S.top();
S.pop();
T=T->right;
}
}
}
上面那個遞歸版是常規版,這個我覺得更好,思路是這樣的,節點的右左節點分別入棧,根絕棧的“先進後出”特性,那麼先輸出的必然是左節點。這個方法代碼簡潔,思路獨特。
void PreOrderTraverse(BTree T) //先序遍歷的非遞歸
{
if(!T)
return ;
stack<BiTree> s;
s.push(T);
while(!s.empty())
{
BiTree temp = s.top();
cout<<temp->data<<" ";
s.pop();
if(temp->rchild)
s.push(temp->rchild);
if(temp->lchild)
s.push(temp->lchild);
}
}
2、中序遍歷
中序遍歷的順序是:左節點、根節點、右節點
遞歸版:
void MidOrderTraaerse(BTree T)
{
if(T!=NULL)
{
MidOrderTraverse(T->left);
cout<<T->val;
MideOrderTraverse(T->right);
}
}
非遞歸版:
根據中序遍歷的順序,對於任一結點,優先訪問其左孩子,而左孩子結點又可以看做一根結點,然後繼續訪問其左孩子結點,直到遇到左孩子結點爲空的結點輸出節點值,按相同的規則訪問其右子樹。
因此其處理過程如下:
對於任一結點P,
1)若節點p不爲空,則將P入棧並將P的左孩子置爲當前的P,然後對P再進行相同的處理;
2)若訪問到左孩子爲空後,則取棧頂元素並進行出棧操作,輸出該棧頂結點值,然後將當前的P置爲棧頂結點的右孩子;
3)直到P爲NULL並且棧爲空則遍歷結束
代碼如下:
void MidOrderTraverse(BTree T)
{
std::stack<BTree> S;
while(T||!S.empty())
{
while(T)
{
S.push(T);
T=T->eft;
}
T=S.top();
cout<<T->val;
S.pop();
T=T->ight;
}
}
3、後序遍歷
後序遍歷的順序是:左節點、右節點、根節點
遞歸版:
void BackOrderTraverse(BTree T)
{
if(T!=NULL)
{
BackOrderTraverse(T->left);
BackOrderTraverse(T->right);
cout<<T->val;
}
}
非遞歸版:
1)版本1:
void PostOrder_Nonrecursive1(BTree T) // 後序遍歷的非遞歸
{
stack<BiTree> S;
BiTree curr = T ; // 指向當前要檢查的節點
BiTree previsited = NULL; // 指向前一個被訪問的節點
while(curr != NULL || !S.empty()) // 棧空時結束
{
while(curr != NULL) // 一直向左走直到爲空
{
S.push(curr);
curr = curr->lchild;
}
curr = S.top();
// 當前節點的右孩子如果爲空或者已經被訪問,則訪問當前節點
if(curr->rchild == NULL || curr->rchild == previsited)
{
cout<<curr->data<<" ";
previsited = curr;
S.pop();
curr = NULL;
}
else
curr = curr->rchild; // 否則訪問右孩子
}
}
2)版本2:
採用雙棧法,這個真是神來之筆,對於想出這個辦法的人,真是佩服的五體投地。爲了解釋原理,的看個實際例子,如下。
對於上面上面的二叉樹,對樹先左後右的將各個節點逐個放到棧1中,然後將棧1元素放到棧2中,過程如下:
棧1:A
棧1:空 | 棧2: A
棧1:B C -> B | 棧2:A C
棧1:B F G ->B F | 棧2:A C G
棧1:B F -> B | 棧2:A C G F
棧1:B -> D E | 棧2:A C G F B
棧1:D E -> D | 棧2:A C G F B E
棧1:空 | 棧2:A C G F B E D
最後輸出棧2,就是D E B F G C A,你說6不6.
void PostOrder_Nonrecursive(BTree T) // 後序遍歷的非遞歸 雙棧法
{
stack<BiTree> s1 , s2;
BiTree curr ; // 指向當前要檢查的節點
s1.push(T);
while(!s1.empty()) // 棧空時結束
{
curr = s1.top();
s1.pop();
s2.push(curr);
if(curr->lchild)
s1.push(curr->lchild);
if(curr->rchild)
s1.push(curr->rchild);
}
while(!s2.empty())
{
printf("%c ", s2.top()->data);
s2.pop();
}
}
4、深度優先遍歷
深度優先遍歷,也就深入的遍歷,沿着每一個分支直到走到最後,然後才返回來遍歷剩餘的節點。二叉樹不同於圖,圖需要標記節點是否已經訪問過,因爲可能會存在環,而二叉樹不會出現環,所以不需要標記。那麼,我們只需要一個棧空間,來壓棧就好了。因爲深度優先遍歷,遍歷了根節點後,就開始遍歷左子樹,所以右子樹肯定最後遍歷。我們利用棧的性質,先將右子樹壓棧,然後在對左子樹壓棧。此時,左子樹節點是在top上的,所以可以先去遍歷左子樹。
void DepthFirstTravel(BTree T)
{
stack<BTree> s;
s.push(T);
while(!s.empty())
{
T = s.top();
cout << T->data << " ";
s.pop();
if(T->rchild != NULL)
{
s.push(T->rchild);
}
if(T->lchild != NULL)
{
s.push(T->lchild);
}
}
}
5、廣度優先遍歷
對於廣度優先遍歷二叉樹,也就是按層次的去遍歷。依次遍歷根節點,然後是左孩子和右孩子。所以要遍歷完當前節點的所有孩子,這樣纔是層次遍歷嘛。此時我們就不能用棧這個數據結構了,因爲棧只能在棧頂操作。在這裏,我們需要根據左右孩子的順序來輸出,所以就是先進先出的原則,那麼我們當然就想到了隊列這個數據結構。可以在rear依次插入左右孩子,在front依次讀取並刪除左右孩子,這樣就保證了層次的輸出。
void BreadthFirstTravel(BTree T)
{
queue<BTree> q;
q.push(T);
while(!q.empty())
{
T = q.front();
cout << T->data << " ";
q.pop();
if(T->lchild != NULL)
{
q.push(T->lchild);
}
if(T->rchild != NULL)
{
q.push(T->rchild);
}
}
}