二叉樹,非遞迴實現(前序、中序、後序)
阿新 • • 發佈:2018-11-13
一、結合棧的方式實現,先讓右孩子入棧,再讓左孩子入棧。棧為空後,結束遍歷。
標頭檔案.根據具體的函式名自己建立,另外需要使用棧,引用棧的標頭檔案
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);
}
}