數據結構之二叉樹的遍歷

前言:在上一篇中介紹了二叉樹的基礎知識,這一篇介紹遍歷二叉樹的實現

一、二叉樹的存儲結構:

        二叉樹的存儲結構可以採用順序存儲,也可以採用鏈式存儲,其中鏈式存儲更加靈活。

        在鏈式存儲結構中,與線性鏈表類似,二叉樹的每個結點採用結構體表示,結構體包含三個域:數據域、左指針、右指針。

二、二叉樹的遍歷:

二叉樹實現需要用到的數據結構代碼如下:

struct BiTNode{     //定義二叉樹
	char data;     //每個結點的數據
	BiTNode *lchild, *rchild;     //左右孩子指針
};

struct Stack{     //定義棧
	int top;       //棧頂指針
	BiTNode *stacksize[100];      //棧的容量
};

void InitStack(Stack &S)    //初始化一個空棧
{
	S.top = -1;
}

int Push(Stack &S, BiTNode *pt)     //元素進棧
{
	S.stacksize[++S.top] = pt;
	return 1;
}

BiTNode * Pop(Stack &S)   //元素出棧
{
	BiTNode *pt;
	pt = S.stacksize[S.top--];
	return pt;
}

BiTNode * GetTop(Stack S)      //獲取棧頂元素
{
	BiTNode *pt;
	if (S.top == -1)
		return 0;
	else
	{
		pt = S.stacksize[S.top];
		return pt;
	}
}

int IsEmptyStack(Stack S)   //判斷是否空棧,空棧返回1,否則返回0
{
	return S.top == -1;
}


 二叉樹遍歷通常借用“棧”這種數據結構實現,有兩種方式:遞歸方式及非遞歸方式。

(1)在遞歸方式中,棧是由操作系統維護的,用戶不必關心棧的細節操作,用戶只需關心“訪問順序”即可。因而,採用遞歸方式實現二叉樹的遍歷比較容易理解,算法簡單,容易實現。

(2)借用“棧”採用非遞歸方式,也能實現遍歷。但是,這時的棧操作(push、pop等)是由用戶進行的,因而實現起來會複雜一些,而且也不容易理解,但有助於我們對樹結構的遍歷有一個深刻、清晰的理解。

(a)非遞歸先序遍歷:

在遍歷某一個二叉(子)樹時,以一當前指針記錄當前要處理的二叉(左子)樹,以一個棧保存當前樹之後處理的右子樹。首先訪問當前樹的根結點數據,接下來應該依次遍歷其左子樹和右子樹,然而程序的控制流只能處理其一,所以考慮將右子樹的根保存在棧裏面,當前指針則指向需先處理的左子樹,爲下次循環做準備;若當前指針指向的樹爲空,說明當前樹爲空樹,不需要做任何處理,直接彈出棧頂的子樹,爲下次循環做準備,代碼如下

int PreOrderTraverse(BiTNode *T)       //二叉樹先序遍歷的非遞歸算法
{
	Stack S;
	InitStack(S);
	BiTNode *pt = NULL;
	pt = T;
	while (pt != NULL || IsEmptyStack(S)!=1)
	{
		if (pt != NULL)
		{
			cout << pt->data;
			Push(S, pt->rchild);  //右子樹進棧,讓當前指針指向左子樹,爲下一個循環做準備
			pt = pt->lchild;
		}
		else
			pt=Pop(S);  //左子樹爲空,不做任何處理,直接彈出右子樹,爲下一個循環準備
	}
	return 1;    //遍歷完成
}

相對於非遞歸先序遍歷,非遞歸的中序/後序遍歷稍複雜一點。

(b)非遞歸中序遍歷:

若當前樹不爲空樹,則訪問其根結點之前應先訪問其左子樹,因而先將當前根節點入棧,然後考慮其左子樹,不斷將非空的根節點入棧,直到左子樹爲一空樹;當左子樹爲空時,不需要做任何處理,彈出並訪問棧頂結點,然後指向其右子樹,爲下次循環做準備。代碼如下:

int InOrderTraverse(BiTNode *T)   //二叉樹中序非遞歸遍歷
{
	Stack S;
	InitStack(S);
	BiTNode *pt = NULL;
	pt = T;
	while (pt != NULL || IsEmptyStack(S)!=1)
	{
		if (pt != NULL)
		{
			Push(S, pt);    //訪問根節點之前先訪問左結點,所以現將根節點進棧
			pt = pt->lchild;
		}
		else        //根指針退棧,訪問根節點,遍歷右子樹
		{
			pt=Pop(S);
			cout << pt->data;
			pt = pt->rchild;     //訪問根節點之後,訪問右子樹
		}
	}
	return 1;
}

(c)後序遍歷的非遞歸實現是三種遍歷方式中最難的一種。因爲在後序遍歷中,要保證左孩子和右孩子都已被訪問,並且左孩子在右孩子之前訪問才能訪問根結f點,這就爲流程控制帶來了難題。下面介紹一種思路。

     要保證根結點在左孩子和右孩子訪問之後才能訪問,因此對於任一結點p,先將其入棧。若p不存在左孩子和右孩子,則可以直接訪問它,或者p存在左孩子或右孩子,但是其左孩子和右孩子都已經被訪問過了,則同樣可以直接訪問該結點。若非上述兩種情況,則將p的右孩子和左孩子依次入棧,這樣就保證了每次取棧頂元素的時候,左孩子在右孩子之前別訪問,左孩子和右孩子都在根結點前面被訪問。代碼如下:

int PostOrderTraverse(BiTNode *T)       //二叉樹的後序非遞歸算法
{
	Stack S;
	InitStack(S);
	BiTNode *pt = NULL;
	pt = T;
	BiTNode *pre, *cur;
	pre = NULL;
	if (!pt)             //空樹
	{
		cout << "the tree is null" << endl;
		return 0;
	}
	Push(S, pt);
	while (IsEmptyStack(S)!=1)
	{
		cur =GetTop(S);  //獲取棧頂元素
		if ((cur->lchild == NULL&&cur->rchild == NULL) || (pre != NULL && (pre == cur->lchild || pre == cur->rchild)))
		{
			//如果當前結點沒有孩子或者左右孩子都已經訪問過,就直接訪問該結點
			cout << cur->data;
			Pop(S);
			pre = cur;       //標識它成爲上一個被訪問的元素
		}
		else
		{
			if (cur->rchild != NULL)      //當前結點既有左右孩子沒有被訪問,那麼就該結點就進棧
				Push(S, cur->rchild);
			if (cur->lchild != NULL)
				Push(S, cur->lchild);
		}
	}
	return 1;
}


三、完整程序代碼:

#include "stdafx.h"
#include <iostream>

using namespace std;

struct BiTNode{     //定義二叉樹
	char data;     //每個結點的數據
	BiTNode *lchild, *rchild;     //左右孩子指針
};

struct Stack{     //定義棧
	int top;       //棧頂指針
	BiTNode *stacksize[100];      //棧的容量
};

void InitStack(Stack &S)    //初始化一個空棧
{
	S.top = -1;
}

int Push(Stack &S, BiTNode *pt)     //元素進棧
{
	S.stacksize[++S.top] = pt;
	return 1;
}

BiTNode * Pop(Stack &S)   //元素出棧
{
	BiTNode *pt;
	pt = S.stacksize[S.top--];
	return pt;
}

BiTNode * GetTop(Stack S)      //獲取棧頂元素
{
	BiTNode *pt;
	if (S.top == -1)
		return 0;
	else
	{
		pt = S.stacksize[S.top];
		return pt;
	}
}

int IsEmptyStack(Stack S)   //判斷是否空棧,空棧返回1,否則返回0
{
	return S.top == -1;
}

int PreOrderTraverse(BiTNode *T)       //二叉樹先序遍歷的非遞歸算法
{
	Stack S;
	InitStack(S);
	BiTNode *pt = NULL;
	pt = T;
	while (pt != NULL || IsEmptyStack(S)!=1)
	{
		if (pt != NULL)
		{
			cout << pt->data;
			Push(S, pt->rchild);  //右子樹進棧,讓當前指針指向左子樹,爲下一個循環做準備
			pt = pt->lchild;
		}
		else
			pt=Pop(S);  //左子樹爲空,不做任何處理,直接彈出右子樹,爲下一個循環準備
	}
	return 1;    //遍歷完成
}

int InOrderTraverse(BiTNode *T)   //二叉樹中序非遞歸遍歷
{
	Stack S;
	InitStack(S);
	BiTNode *pt = NULL;
	pt = T;
	while (pt != NULL || IsEmptyStack(S)!=1)
	{
		if (pt != NULL)
		{
			Push(S, pt);    //訪問根節點之前先訪問左結點,所以現將根節點進棧
			pt = pt->lchild;
		}
		else        //根指針退棧,訪問根節點,遍歷右子樹
		{
			pt=Pop(S);
			cout << pt->data;
			pt = pt->rchild;     //訪問根節點之後,訪問右子樹
		}
	}
	return 1;
}
int PostOrderTraverse(BiTNode *T)       //二叉樹的非遞歸算法
{
	Stack S;
	InitStack(S);
	BiTNode *pt = NULL;
	pt = T;
	BiTNode *pre, *cur;
	pre = NULL;
	if (!pt)
	{
		cout << "the tree is null" << endl;
		return 0;
	}
	Push(S, pt);
	while (IsEmptyStack(S)!=1)
	{
		cur =GetTop(S);  //獲取棧頂元素
		if ((cur->lchild == NULL&&cur->rchild == NULL) || (pre != NULL && (pre == cur->lchild || pre == cur->rchild)))
		{
			//如果當前結點沒有孩子或者左右孩子都已經訪問過,就直接訪問該結點
			cout << cur->data;
			Pop(S);
			pre = cur;       //標識它成爲上一個被訪問的元素
		}
		else
		{
			if (cur->rchild != NULL)      //當前結點既有左右孩子沒有被訪問,那麼就該結點就進棧
				Push(S, cur->rchild);
			if (cur->lchild != NULL)
				Push(S, cur->lchild);
		}
	}
	return 1;
}

int CreateBiTree(BiTNode *&T)   //採用先序遍歷法創建二叉樹
{
	char c;
	c = getchar();
	if (c == '#')    //  空樹
		T = NULL;
	else
	{
		T = new BiTNode;
		T->data = c;
		CreateBiTree(T->lchild);
		CreateBiTree(T->rchild);
	}
	return 1;
}

int _tmain(int argc, _TCHAR* argv[])   
{
	BiTNode *T = NULL;
	cout << "先序輸入字符創建一個二叉樹:" << endl;
	CreateBiTree(T);
	cout << "前序遍歷二叉樹序列爲:" << endl;
	PreOrderTraverse(T);
	cout << endl;
	cout << "中序遍歷二叉樹序列爲:" << endl;
	InOrderTraverse(T);
	cout << endl;
	cout << "後序遍歷二叉樹序列爲:" << endl;
	PostOrderTraverse(T);
	cout << endl;
	return 0;
}

參考:嚴蔚敏《數據結構》(c語言版)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章