【数据结构】二叉树的存储和遍历
二叉樹的存儲結(jié)構(gòu)
順序存儲結(jié)構(gòu)
二叉樹的順序存儲結(jié)構(gòu)是指用一組地址連續(xù)的存儲單元依次自上而下、從左到右存儲完全二叉樹上的結(jié)點,即將完全二叉樹上編號為 i 的結(jié)點存儲在數(shù)組下標(biāo)為 i?1的數(shù)組分量中,然后通過一定方式確定結(jié)點在邏輯上的父子或兄弟關(guān)系。
???
空間浪費比較大
鏈?zhǔn)酱鎯Y(jié)構(gòu)
二叉樹每個結(jié)點最多兩個孩子,所以設(shè)計二叉樹的結(jié)點結(jié)構(gòu)時考慮兩個指針指向該結(jié)點的兩個孩子。
二叉樹結(jié)點結(jié)構(gòu):
這種結(jié)構(gòu)的鏈表由于有兩個指針,所以叫二叉鏈表
另外還可以增加指針指向該結(jié)點的雙親結(jié)點,那這時三個指針的鏈表就叫三叉鏈表
二叉樹的遍歷(遞歸)
遍歷,是指按照某條搜索路徑訪問樹中的每一個結(jié)點,使得每個結(jié)點均會被訪問一次,而且僅被訪問一次。
由二叉樹的遞歸定義可知,遍歷一棵二叉樹首先需要確定對根結(jié)點、左子樹和右子樹的訪問順序。常見的遍歷次序有先序遍歷 (NLR) 、中序遍歷 (LNR) 和后序遍歷 (LRN) 三種。
遞歸先序遍歷
若二叉樹為空,則不采取操作,否則:
對應(yīng)的遞歸算法:
void PreOrder(BiTree T){if(T != NULL){visit(T); //訪問根結(jié)點PreOrder(T->lchild); //遞歸遍歷左子樹PreOrder(T->rchid); / /遞歸遍歷右子樹} }遞歸中序遍歷
若二叉樹為空,則不進(jìn)行操作,否則:
對應(yīng)的遞歸算法:
void InOrder(BiTree T){if(T != NULL){InOrder(T->lchild); //遞歸訪問左子樹visit(T); //訪問根結(jié)點InOrder(T->rchild); //遞歸訪問右子樹} }遞歸后序遍歷
若二叉樹為空,則不進(jìn)行操作,否則:
對應(yīng)的遞歸算法:
void PostOrder(BiTree T){if(T != NULL){PostOrder(T->lchild); //遞歸遍歷左子樹PostOrder(T->rchild); //遞歸遍歷右子樹visit(T); //訪問根結(jié)點} }二叉樹的遍歷(非遞歸)
借助到棧
非遞歸先序遍歷
Void PreOrderTraverse(BiTree b){InitStack(S);BiTree p=b; //工作指針pwhile(p || !IsEmpty(S)){while(p){printf(“%c”,p->data); //先序先遍歷結(jié)點Push(S,p); //進(jìn)棧保存p=p->lchild; //指向左孩子}if(!IsEmpty(S)){p=Pop(S); //出棧p=p->rchild; //指向右孩子}} }例子:
非遞歸中序遍歷
Void PreOrderTraverse(BiTree b){InitStack(S);BiTree p=b; //工作指針pwhile(p || !IsEmpty(S)){while(p){ //中序先將結(jié)點進(jìn)棧保存Push(S,p);p=p->lchild;}//遍歷到左下角盡頭再出棧訪問p=Pop(S);printf(“%c”,p->data);p=p->rchild; //遍歷右孩子} }非遞歸后序遍歷
Void PostOrderTraverse(BiTree b){InitStack(S);BiTree p=b,r=NULL; //工作指針p 輔助指針rwhile(p || !IsEmpty(S)){//1.從根結(jié)點到最左下角的左子樹都入棧if(p){Push(S,p);p=p->lchild;}//2.返回棧頂?shù)膬煞N情況else{GetTop(S,p); //取棧頂 注意不是出棧!//①右子樹還未訪問,而且右子樹不空,第一次棧頂if(p->rchild &&p->rchild !=r) p=p->rchild;//②右子樹已經(jīng)訪問或為空,接下來出棧訪問結(jié)點else{Pop(S,p);printf(“%c”,p->data);r=p; //指向訪問過的右子樹根結(jié)點p=NULL; //使p為空從而繼續(xù)訪問棧頂}}} }層序遍歷
如圖所示為二叉樹的層次遍歷,即按照箭頭所指方向,按照層次1,2,3,4的順序,橫向遍歷對二叉樹中的結(jié)點進(jìn)行訪問。
進(jìn)行層次遍歷需要借助隊列:先將二叉樹根結(jié)點入隊,然后出隊并訪問該結(jié)點,若有左子樹,則將左子樹根結(jié)點入隊;若有右子樹,則將右子樹根結(jié)點入隊。然后出隊并對出隊結(jié)點進(jìn)行訪問,重復(fù)上述操作直到隊列為空。
思路:出隊→訪問→左右孩子入隊
二叉樹的層次遍歷算法:
void LevelOrder(BiTree T){InitQueue(Q); //創(chuàng)建空隊列 QBiTree p; //創(chuàng)建遍歷指針 pEnQueue(Q, T); //根結(jié)點入隊while(! IsEmpty Q){ //隊列非空則繼續(xù)循環(huán)進(jìn)行遍歷DeQueue(Q, p); //隊頭元素出隊visit(p); //訪問出隊結(jié)點if(p->lchild != NULL) //若左子樹存在EnQueue(Q, p->Lchild); //左子樹根結(jié)點入隊if(p->child != NULL) //若右子樹存在EnQueue(Q, p->rchild); //右子樹根結(jié)點入隊} }線索二叉樹
二叉鏈表表示的二叉樹存在大量空指針
N個結(jié)點的二叉鏈表,每個結(jié)點都有指向左右孩子的結(jié)點指針,所以一共有2N個指針,而N個結(jié)點的二叉樹一共有N-1條分支,也就是說存在2N-(N-1)=N+1個空指針。比如上圖二叉樹中有6個結(jié)點,那么就有7個空指針。
大量空余指針能否利用起來?
指向前驅(qū)和后繼的指針成為線索,加上線索的二叉鏈表就成為線索鏈表,相應(yīng)的二叉樹被稱為線索二叉樹
對二叉樹以某種次序遍歷使其變?yōu)榫€索二叉樹的過程就叫做線索化
如何區(qū)分指針是指向左孩子還是前驅(qū),或者指向右孩子還是后繼?
在二叉鏈表結(jié)點的結(jié)構(gòu)基礎(chǔ)上增加兩個標(biāo)志位ltag和rtag
typedef struct ThreadNode{ElemType data;struct ThreadNode *lchild,*rchild;int ltag,rtag; }ThreadNode,*ThreadTree; //線索鏈表ltag == 0 代表lchild指向該結(jié)點的左孩子
ltag ==1 代表lchild指向該結(jié)點的前驅(qū)
rtag == 0 代表rchild指向該結(jié)點的右孩子
rtag ==1 代表rchild指向該結(jié)點的后繼
遍歷線索二叉樹
void InOrderTraverse(ThreadTree T){ThreadTree p=T;while(p){while(p->ltag==0)p=p->lchild;printf(“%c”,p->data);while(p->rtag==1&&p->rchild){p=p->rchild;printf(“%c”,p->data);}p=p->rchild;} }參考資料
王道數(shù)據(jù)結(jié)構(gòu)
總結(jié)
以上是生活随笔為你收集整理的【数据结构】二叉树的存储和遍历的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 是否同一棵二叉搜索树(c语言实现)
- 下一篇: 04-树7 二叉搜索树的操作集(c语言实