1. 程式人生 > >二叉樹,非遞迴實現(前序、中序、後序)

二叉樹,非遞迴實現(前序、中序、後序)

一、結合棧的方式實現,先讓右孩子入棧,再讓左孩子入棧。棧為空後,結束遍歷。

標頭檔案.根據具體的函式名自己建立,另外需要使用棧,引用棧的標頭檔案

 

stack.h

# pragma oncee
# include<stdio.h>
# include<stdlib.h>
# include<string.h>
# include<assert.h>
#define MAX_SIZE 10
extern struct BinTreeBTNode;//這個檔案是在其他檔案內部定義的,當前檔案中沒有,
//要到其他檔案中去找
typedef struct BinTreeBTNode* PBTNode;
//指標的大小是確定的,要在當前檔案中定義這種變數,必須知道當前型別的存在
//但是此型別定義於其他檔案中,那麼若是要定義出這種型別,編譯器並不知道
//應該給多少個位元組,所以此時需要使用指標(指標的大小是確定的)
typedef PBTNode DataTYpe;

typedef struct Stack
{
	DataTYpe _array[MAX_SIZE];//有效元素的個數
	int _top; //棧頂元素的位置
}Stack;

//初始化
void StackInit(Stack *s);

//入棧
void StackPush(Stack *s, DataTYpe data);

//出棧
void StackPop(Stack *s);

//取棧頂元素
DataTYpe StackTop(Stack *s);

//有效元素的個數
int StackSize(Stack *s);

//檢測棧是否為空
int Stackempty(Stack *s);

1、前序遍歷:

法1:

void PreOrderNor(PBTNode pRoot)
{
	Stack s;
	if (NULL == pRoot)
		return;
	StackInit(&s);
	StackPush(&s, pRoot);//把當前根節點入棧
	while (!Stackempty(&s))
	{
		//遍歷:取棧頂元素
		PBTNode pCur = StackTop(&s);//取到棧頂元素,但是元素還在棧裡
		printf("%c ", pCur->_data);
		//移除棧頂元素:後進先出
		StackPop(&s);
		if (pCur->_pRight)//優先讓右孩子入棧
			StackPush(&s, pCur->_pRight);
		if (pCur->_pLeft)
			StackPush(&s, pCur->_pLeft);
	}
}

法2:

 

void PreOrderNor(PBTNode pRoot)
{
	Stack s;
	if (NULL == pRoot)
		return;
	StackInit(&s);
	StackPush(&s, pRoot);//把當前根節點入棧
	while (!Stackempty(&s)){
		PBTNode pCur = StackTop(&s);//取棧頂元素
		StackPop(&s);//把棧頂元素移除
		while (pCur){
			printf("%c ", pCur->_data);
			if (pCur->_pRight)
				StackPush(&s,pCur->_pRight);
			pCur = pCur->_pLeft;
		}
	}
}

2、中序遍歷:找當前樹最左邊的節點,並儲存所經路徑中的所有節點

void InOrder(PBTNode pRoot)
{
	PBTNode pCur = pRoot;
	Stack s;
	if (NULL == pRoot)
		return;
	StackInit(&s);
	//找以pCur為根樹的最左邊的節點,並且儲存所經路徑中的所有節點
	while (pCur||!Stackempty(&s))
	{
		while (pCur){
			StackPush(&s, pCur);
			pCur = pCur->_pLeft;
		}
		pCur = StackTop(&s);
		printf("%c ", pCur->_data);
		pCur = pCur->_pRight;
	}
}

3、後序遍歷:

void PostOrderNor(PBTNode pRoot)
{
	PBTNode pCur = pRoot, pTop;
	PBTNode pPrev = NULL;//標記最近訪問過的節點
	Stack s;
	if (NULL == pRoot){
		return;
	}
	StackInit(&s);
	while (pCur||!Stackempty(&s))
	{
		//找最左邊的節點並儲存所經路徑中的所有節點
		while (pCur){
			StackPush(&s, pCur);
			pCur = pCur->_pLeft;
		}
		pTop = StackTop(&s);
		if (NULL == pTop->_pRight||pTop->_pRight==pPrev){
			printf("%c  ", pTop->_data);
			pPrev = pTop;
			StackPop(&s);
		}
		else
		{
			pCur = pTop->_pRight;
		}
	}
}

 根據前序、中序;後序、中序可以還原二叉樹;但是通過前序、後序不能還原二叉樹。

通過前序和中序還原二叉樹,程式實現:

void ReBuildBinTree(pre, PreSize, in, left,right)
{
	int index = 0;
	//pre:前序遍歷的結果,in:中序遍歷的結果;Size:遍歷的元素個數
	if (PreSize != InSize)
		return;//前序後中序遍歷的元素個數不相等則返回
 	pre[index];//確認根節點
	int i = left;
	while (in[i] != pre[index])
		i++;//沒有找到根
	//確認左右子樹,在中序遍歷的結果中查詢
	//重建根
	pRoot = BuyBinTreeBTNode(pre[index]);
	//重建根的左子樹
	++index;
	ReBuildBinTree(pre, PreSize, left,i);
	//重建根的右子樹
	++index;
	ReBuildBinTree(pre, PreSize, i+1,right);
}

a、前序、後序、中序--->遞迴和非遞迴速度的比較:迴圈沒有空間開闢、給定是靜態棧時,效率比較高,涉及擴容,則效率比較低。

我是藉助stack實現的非遞迴,但是stack可分為靜態棧和動態棧,動態棧需要擴容時需要(開闢新空間(新空間需要在系統中查詢哪一塊空間合適,找到了則返回,查詢的過程也浪費時間)、搬移元素、釋放舊空間);而遞迴中開闢新空間只需要執行幾個指令,很快。

b、遞迴和迴圈遍歷的時間複雜度和空間複雜度:

時間複雜度:遞迴的時間時間複雜度:遞迴的總次數(2n)*每次遞迴的次數=O(n)

時間複雜度:用了一個棧,時間複雜度變高O(n)

遞迴容易在成棧溢位

那麼可以不使用棧,用迴圈的方式遍歷二叉樹麼?線索化二叉樹

二、二叉樹的線索化(二叉樹中總共右n個節點,那麼有幾個空的指標域:n+1;總共有2*n個指標域,有n-1個指標域已經用過了。【通過指標域的指向,使用迴圈的方式實現遍歷,而不是使用遞迴的方式】

//對節點進行改造
typedef enum {
	LINK,//指向孩子
	THREAD,//指向後繼
}PointerFlag;

//節點的型別
typedef struct BinTreeNode
{
	struct BinTreeNode* _pLeft;
	struct BinTreeNode* _pRight;
	DataTYpe _data;//值域
	//線索
	PointerFlag _leftThread;
	PointerFlag _rightThread;
}BinTreeNode;
//前序線索化
void PreOrderThd(BinTreeNode* pRoot, BinTreeNode* pPrev)
{
	//pPrev標記剛剛線索化過的節點
	if (pRoot)
	{
		//線索化當前節點的左指標域
		if (NULL == pRoot->_pLeft)
		{
			pRoot->_pLeft= pPrev;
			pRoot->_leftThread = THREAD;//左孩子指向前驅節點
		}
		//線索化當前節點的右指標域
		if (pPre&&NULL == pPrev->_pRight){
			pPrev->_pRight = pRoot;
			pPrev->_rightThread = THREAD;
		}
		pPrev = pRoot;
		//判斷左孩子是否存在
		if (pRoot->_leftThread==LINK)//存在左孩子
		//線索化當前節點的左子樹
		PreOrderThd(pRoot->_pLeft, pPrev);
		if (pRoot->_rightThread==LINK)//存在右孩子
		//線索化當前節點的右子樹
		PreOrderThd(pRoot->_pRight, pPrev);
	}
}