先說下遞歸遍歷吧,特別簡單,交換三者的順序,就能得到三種遞歸方式。
以先序遍歷爲例:
void preOrder1(BinTree *root) //遞歸前序遍歷
{
if(root!=NULL)
{
cout<<root->data<<" ";
preOrder1(root->lchild);
preOrder1(root->rchild);
}
}
一:先序遍歷非遞歸方式-----其實也可以看做深度優先算法
1、用棧實現
要設置一個棧,來模擬向上回溯的方式。
(1)如果左節點不空,就一直進行“訪問該結點,入棧,p指向左子樹”
(2)當p指向的結點爲空的時候,就開始出棧了,出棧的是p的父節點pp,那麼將p = pp->right,使得p指向其兄弟結點,再返回(1)進行遞歸訪問。
(3)每次出棧的結點的右結點肯定沒有被訪問過,因此讓p指向其右節點就行了。
void preOrder2(BinTree *root) //非遞歸前序遍歷
{
stack<BinTree*> s;
BinTree *p=root;
while(p!=NULL||!s.empty())
{
while(p!=NULL)
{
cout<<p->data<<" ";
s.push(p);
p=p->lchild;
}
if(!s.empty())
{
p=s.top();
s.pop();
p=p->rchild;
}
}
}
2、用棧實現
改進一:在方法一中,我們讓根結點入棧,是爲了方便找到根結點的右節點。這裏我們可以直接讓右節點入棧。
改進二:爲了省去上面的不規則代碼,先while循環,再if判斷。我們可以直接先讓右結點入棧,再讓左結點入棧,這樣的話,下次循環開始,就直接出棧就行了。相當於沒入棧的時候,讓p = p->left。
代碼如下:
void preOrder(Node *p) //非遞歸
{
if(!p) return;
stack<Node*> s;
Node *t;
s.push(p);
while(!s.empty()){
t=s.top();
cout << t.data << " ";
s.pop();
if(t->right) s.push(t->right);
if(t->left) s.push(t->left);
}
}
3、不用棧,但需要額外添加父節點指針和訪問標記
先判斷左孩子的訪問標記,再判斷右孩子的訪問標記。如果左右孩子都被訪問過,就讓root指向root->parent。
// 先序遍歷僞代碼:非遞歸版本,不用棧,增加指向父節點的指針
void preOrder3(TNode* root)
{
while ( root != NULL ){ // 回溯到根節點時爲NULL,退出
if( !root->bVisited ){ // 判定是否已被訪問
Visit(root);
root->bVisited = true;
}
if ( root->left != NULL && !root->left->bVisited ){ // 訪問左子樹
root = root->left;
}
else if( root->right != NULL && !root->right->bVisited ){ // 訪問右子樹
root = root->right;
}
else{ // 回溯
root = root->parent;
}
}
}
這個方法不太好,不能直接找到要被訪問的右結點,需要一層一層向上走。也需要了額外的空間來存儲父節點指針和訪問標記。但有點是沒有用到棧。
二:中序遍歷非遞歸方式
1、用棧實現
參照先序遍歷的方案一,我們直接將訪問的方式,放到出棧的時候,因爲這個時候出棧的是父節點。利用這個代碼最簡潔。
void InOrderTraverse1(BiTree T) // 中序遍歷的非遞歸
{
if(!T)
return ;
BiTree curr = T; // 指向當前要檢查的節點
stack<BiTree> s;
while(curr != NULL || !s.empty())
{
while(curr != NULL)
{
s.push(curr);
curr = curr->lchild;
}//while
if(!s.empty())
{
curr = s.top();
s.pop();
cout<<curr->data<<" ";
curr = curr->rchild;
}
}
}
2、用棧實現
如果參照先序遍歷的方案二的話,就不能實現了,因爲那裏的棧中只保存了左右子樹,沒有跟結點信息。導致了沒有辦法實現中序遍歷。
因此三個節點都要入棧,而且入棧的先後順序必須爲:右節點,根節點,左節點。但是,當入棧以後,根節點與其左右子樹的節點就分不清楚了。因此必須引入一個標誌位,表示 是否已經將該節點的左右子樹入棧了。每次入棧時,根節點標誌位爲true,左右子樹標誌位爲false。
下面用c++來實現,就用到pair類型。
void inOrder(Node *p)
{
if(!p)
return;
stack< pair<Node*,int> > s;
Node *t;
int unUsed;
s.push(make_pair(p,1));
while(!s.empty()){
t=s.top().first;
unUsed = s.top().second;
s.pop();//退出棧頂結點
if(unUsed){//如果其左右子樹都沒有放入棧中
if(t->right) //按照右子樹,根結點,左子樹的順序放入
s.push( make_pair(t->right,1) );
s.push( make_pair(t,0) ); //只有放入根結點的時候,要設置標記爲0
if(t->left)
s.push( make_pair(t->left,1));
}
else printf("%d\n",t->data);
}
}
3、不用棧
這個方法比較繞,參考下就行
中序遍歷的第三個非遞歸版本:採用指向父節點的指針回溯。這個與先序遍歷是非常類似的,不同之處在於,先序遍歷只要一遇到節點,那麼沒有被訪問那麼立即訪問,訪問完畢後嘗試向左走,如果左孩子補課訪問,則嘗試右邊走,如果左右皆不可訪問,則回溯;中序遍歷是先嚐試向左走,一直到左邊不通後訪問當前節點,然後嘗試向右走,右邊不通,則回溯。(這裏不通的意思是:節點不爲空,且沒有被訪問過)
// 中序遍歷僞代碼:非遞歸版本,不用棧,增加指向父節點的指針
void InOrder3(TNode* root)
{
while ( root != NULL ) // 回溯到根節點時爲NULL,退出
{
while ( root->left != NULL && !root->left->bVisited )
{ // 沿左子樹向下搜索當前子樹尚未訪問的最左節點
root = root->left;
}
if ( !root->bVisited )
{ // 訪問尚未訪問的最左節點
Visit(root);
root->bVisited=true;
}
if ( root->right != NULL && !root->right->bVisited )
{ // 遍歷當前節點的右子樹
root = root->right;
}
else
{ // 回溯至父節點
root = root->parent;
}
}
}
三、後序遍歷非遞歸方式
1、用棧
(1)
void PostOrder_Nonrecursive1(BiTree 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)
void PostOrder_Nonrecursive(BiTree 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();
}
}
2、用棧
這個參考了中序遍歷的第二種方法,入棧的順序是,根結點、右子樹、左子樹。
void postOrder(Node *p)
{
if(!p) return;
stack<pair<Node*,int> > s;
Node *t;
int unUsed;
s.push(make_pair(p,1));
while(!s.empty()){
t=s.top().first;
unUsed=s.top().second;
s.pop();
if(unUsed){
s.push(make_pair(t,0);
if(t->right)
s.push(make_pair(t->right,1));
if(t->left)
s.push(make_pair(t->left,1));
}
else printf("%d\n",t->data);
}
}
四、層序遍歷
利用隊列,很簡單
void levelOrderTraverse(const BiTree& T)
{
queue<BiTree> q;
BiTree p = NULL;
if(T)//若根結點非空,則入隊列
{
q.push(T);
}
while(!q.empty())//隊列非空
{
p = q.front();
q.pop();
cout<<p->data<<" ";
if(p->lchild)//左孩子不空,入隊列
{
q.push(p->lchild);
}
if(p->rchild)//右孩子不空,入隊列
{
q.push(p->rchild);
}
}
}