SDUST 2020/資料結構/期末集合.part3
07 樹
1、(4)樹的型別定義
①基本概念:
結點:資料元素+若干指向子樹的分支
結點的度:分支的個數(子樹的個數)
樹的度:樹中所有結點的度的最大值
葉子結點:度為0的結點
分支結點:度大於0的結點(包含根和中間結點)
深度:對任意結點ni,ni的深度等於根到該點的唯一路徑的長
高度:對任意結點ni,其高度等於該點到一片樹葉的最長的路徑
寬度:
結點的層次:假設根節點在第一層,那麼其孩子結點在第二層,以此類推
②樹的基本分類
有向樹:有確定的根、樹根與子樹之間是有向關係
有序樹:子樹之間存在確定的次序關係
無序樹:子樹之間不存在確定的次序關係
樹和森林:
③基本操作
2、二叉樹
①定義
二叉樹:或為空樹,或是由一個根結點加上兩棵分別稱為左子樹和右子樹的、互不交的二叉樹組成(樹的度最大為2)。
二叉樹的五種基本形態:空樹、只含根節點、左子樹為空樹、右子樹為空樹、左右都不為空樹
②重要特性
對於結點為n的二叉樹,它的分支樹b=n-1;
任意的二叉樹中葉子節點都比度為2的節點多1
在二叉樹的第二層至多有 2i-1 個結點;
深度為k的二叉樹上至多含有 2k-1 個結點;
對任何一個二叉樹,若它有n0個葉子結點,n2個度為2的結點,那麼必存在關係式n0=n2+1;
證明:設二叉樹結點總數是n=n0+n1+n2
則分支總數為b=n1+2xn2
又因為b=n-1。聯立得出結論。
具有n個結點的完全二叉樹的深度為 ⌊log2n⌋+1
③二叉樹的分類
滿二叉樹:深度為k且有2k-1個結點,即滿樹葉的二叉樹
完全二叉樹:樹中所含的n個結點和滿二叉樹中編號為1到n的結點一一對應
特點:葉子結點出現在最後兩層;對於任意結點,若其右分支下的子孫最大層次為L,則左分支下的子孫的最大層次為L或L+1
④二叉樹的儲存結構
♦順序儲存方式
♦鏈式儲存結構
二叉連結串列:這是解題時沒有特殊情況最常用的結構
typedef struct BiTNode { TElemType data; structBiTNode *lchild,*rchild; }BiTNode,*BiTree;
三叉連結串列:
typedef struct TriTNode { TElemType data; struct TriTNode *lchild,*rchild; struct TriTNode *parent; }TriTNode,*TriTree;
雙親連結串列:
3、二叉樹的遍歷
單獨把遍歷拿出來說一下
遍歷一共有三種方式:先序、中序、後序,先後都是相對於根節點講的
如下一棵樹,先序遍歷是-+a*b-cd/ef,後序遍歷是a+b*c-d-e/f,中序遍歷是abcd-*+ef/-
總結:無論先序、中序、後序遍歷二叉樹,遍歷時的搜尋路線是相同的:從根結點出發,逆時針延二叉樹外緣移動對每個結點均途徑三次。
先序遍歷:第一次經過結點時訪問。
中序遍歷:第二次經過結點時訪問。
後序遍歷:第三次經過結點時訪問。
②演算法遞迴的描述
如下是先中後序的遞迴演算法
void preorderT(BiTree T) { if(T) { printf("%d",T->data); preorderT(T->lchild); preorderT(T->rchild); } } void inorderT(BiTree T) { if(T) { inorderT(T->lchild); printf("%d",T->data); inorderT(T->rchild); } } void postorderT(BiTree T) { if(T) { postorderT(T->lchild); postorderT(T->rchild); printf("%d",T->data); } }
特殊地,出現層次遍歷:
void LevelorderTraversal( BinTree BT )//逐層遍歷 { if(!BT) return; BinTree a[10000],b; a[0]=BT; int len=1;//len記錄當前層的節點數量 while(1) { if(len==0) return; int pos=0; BinTree b[10000]; for(int i=0; i<len; i++) { if(a[i]!=NULL) printf(" %c",a[i]->Data); if(a[i]->Left!=NULL) b[pos++]=a[i]->Left; if(a[i]->Right!=NULL) b[pos++]=a[i]->Right; } len=pos;//更新下一層寬度,為下一次迴圈做準備 for(int i=0; i<len; i++) //將下層的b賦給a,為下一次迴圈做準備 a[i]=b[i]; } }
④應用舉例
在樹的整章學習中,遞迴的思想極大地簡化了解題步驟,而涉及遍歷的題目,大都與遞迴有關
♦先序輸出葉結點/求葉子結點的個數:
void PreorderPrintLeaves( BinTree BT ) { if(BT) { if(BT->Left==NULL&&BT->Right==NULL) printf(" %c",BT->Data); else { if(BT->Left) PreorderPrintLeaves(BT->Left); if(BT->Right) PreorderPrintLeaves(BT->Right); } } }
int LeafCount(Bitree T) { if(!T) return 0; else if(!T->lchild&&!T->rchild) return 1; else return LeafCount(T->lchild)+LeafCount(T->rchild); }
♦求二叉樹的深度
經典題目。關鍵是要搞清楚深度的定義,即左右子樹深度的最大值+1。演算法仍是遞迴。
int BitreeDepth(Bitree T) { if(!T) depth=0; else { depthleft=BitreeDepth(T->lchild); depthright=BitreeDepth(T->rchild); depth=1+(depthleft>depthright?depthleft:depthright); } return depth; }
♦建立二叉樹的儲存結構
以字串的形式建樹
Status Creatbitree (Bitree *T) { scanf("%s",ch); if(ch=='') T=NULL; else { if(!(T=(BiTNode*)malloc(sizeof(BiTNode))))d exit(OVERFLOW); T->data=ch; Creatbitree(T->lchild); Creatbitree(T->rchild); } return OK; }
按給定的表示式建樹
scanf(&ch);//由字首表示式建樹 if(In(ch,字符集)) 建立葉子結點; else { 建立根節點; 遞迴左子樹; 遞迴右子樹; }
根據後續和中序遍歷輸出先序遍歷
4、線索二叉樹
對線索連結串列中結點的約定:
在二叉連結串列的結點中增加兩個標誌域,並作如下規定:
♦若該結點的左子樹不空,則lchild域的指標指向其左子樹,且左標誌域的值為“Link(指標)”;否則,lchild域的指標指向其“前驅”,且左標誌的值為“Thread(線索)”
♦若該結點的右子樹不空,則rchild域的指標指向其右子樹,且右標誌域的值為“Link(指標)”;否則,rchild域的指標指向其“後繼”,且右標誌的值為“Thread(線索)”。
如此定義的二叉樹的儲存結構稱作“線索連結串列”。