《數據結構》C語言版——認識樹和二叉樹

今天和大家聊聊數據結構中樹和二叉樹的相關知識,溫故而知新,開始了喲!

一.樹的定義
從數據結構角度看,樹包含n(n≥0)個結點,當n=0時,稱爲空樹;非空樹的定義如下: T=(D,R)
其中,D爲樹中結點的有限集合,關係R滿足以下條件:
●有且僅有一個結點d0∈D,它對於關係R來說沒有前驅結點,結點d0稱作樹的根結點
●除根結點d0外,D中的每個結點有且僅有一個前驅結點,但可以有多個後繼結點
●D中可以有多個終端結點

假如我們有一棵樹T=(D,R),其中:D={A,B,C,D,E,F,G,H},R={r}
r={<A,B>,<A,C>,<A,D>,<C,E>,<C,F>,<D,G>,<E,H>},那我們該如何畫出其邏輯結構圖呢?

解:A是根結點,其餘結點分成三個互不相交的子集:  T1={B},T2={C,E,F,H},T3={D,G}; T1、T2、T3都是根結點A的子樹,且各自本身也是一棵樹

於是我們得到了邏輯結構圖,如下:
在這裏插入圖片描述
說明:樹中結點之間的關係應爲有向關係,在上圖中,結點之間的連線即分支線都是有向的,默認箭頭向下的。

二.樹的邏輯結構表示
1.樹形表示法:使用一棵倒置的樹表示樹結構,非常直觀和形象
2.文氏圖表示法:使用集合以及集合的包含關係描述樹結構,如圖:
在這裏插入圖片描述
3.凹入表示法:使用線段的伸縮關係描述樹結構
在這裏插入圖片描述
4.括號表示法:將樹的根結點寫在括號的左邊,除根結點之外的其餘結點寫在括號中並用逗號分隔
在這裏插入圖片描述
三.樹的基本術語
1.什麼是結點的度?

樹中每個結點具有的子樹數或者後繼結點數稱爲該結點的度

在這裏插入圖片描述
2.什麼是樹的度?

樹中所有結點的度的最大值稱之爲樹的度

在這裏插入圖片描述
3.什麼是分支結點?

度大於0的結點稱爲分支結點或非終端結點。 度爲1的結點稱爲單分支結點,度爲2的結點稱爲雙分支結點,依次類推…

在這裏插入圖片描述
4.什麼是葉子節點?

度爲零的結點稱爲葉子結點或終端結點

在這裏插入圖片描述
5.什麼是孩子結點?

一個結點的後繼稱之爲該結點的孩子結點

在這裏插入圖片描述
6.什麼是雙親結點?

一個結點稱爲其後繼結點的雙親結點

在這裏插入圖片描述
7.什麼是子孫結點?

一個結點的子樹中除該結點外的所有結點稱之爲該結點的子孫結點

在這裏插入圖片描述
8.什麼是祖先結點?

從樹根結點到達某個結點的路徑上通過的所有結點稱爲該結點的祖先結點(不含該結點自身)

在這裏插入圖片描述
9.什麼是兄弟結點?

具有同一雙親的結點互相稱之爲兄弟結點

在這裏插入圖片描述
10.什麼是結點層次?

樹具有一種層次結構,根結點爲第一層,其孩子結點爲第二層,如此類推得到每個結點的層次

在這裏插入圖片描述
11.什麼是樹的高度?

樹中結點的最大層次稱爲樹的高度或深度

在這裏插入圖片描述
12.什麼是森林?

零棵或多棵互不相交的樹的集合稱爲森林

在這裏插入圖片描述
四.樹的性質
性質1: 樹中的結點數等於所有結點的度數加1

性質2:度爲m的樹中第i層上至多有m^(i-1)個結點,這裏應有i≥1

推廣:當一棵m次樹的第i層有m^(i-1)個結點(i≥1)時,稱該層是滿的,若一棵m次樹的所有葉子結點在同一層,除該層外其餘每一層都是滿的,稱爲滿m次樹。顯然,滿m次樹是所有相同高度的m次樹中結點總數最多的樹。也可以說,對於n個結點,構造的m次樹爲滿m次樹或者接近滿m次樹,此時樹的高度最小。

讓我們來道經典例題鞏固一下:
若一棵三次樹中度爲3的結點爲2個,度爲2的結點爲1個,度爲1的結點爲2個,則該三次樹中總的結點個數和度爲0的結點個數分別是多少?

解:設該三次樹中總結點個數、度爲0的結點個數、度爲1的結點個數、度爲2的結點個數和度爲3的結點個數分別爲n、n0、n1、n2和n3。
顯然,每個度爲i的結點在所有結點的度數之和中貢獻i個度。依題意有:n1=2,n2=1,n3=2。
由樹的性質1可知:n = 所有結點的度數之和+1 
= 0×n0+1×n1+2×n2+3×n3+1
= 1×2+2×1+3×2+1
=11   
又因爲n=n0+n1+n2+n3   
即:n0=n-n1-n2-n3=11-2-1-2=6   
所以該三次樹中總的結點個數和度爲0的結點個數分別是11和6。

五.二叉樹
1.二叉樹的遞歸定義
二叉樹或者是一棵空樹,或者是一棵由一個根結點和兩棵互不相交的分別稱做根結點的左子樹和右子樹所組成的非空樹,左子樹和右子樹又同樣都是一棵二叉樹。

注意:二叉樹與度爲2的樹是不同的!
度爲2的樹至少有3個結點,而二叉樹的結點數可以爲0;
度爲2的樹不區分子樹的次序,而二叉樹中的每個結點最多有兩個孩子結點,且必須要區分左右子樹,即使在結點只有一棵子樹的情況下也要明確指出該子樹是左子樹還是右子樹

2.二叉樹的5種形態
在這裏插入圖片描述
3.二叉樹的性質
性質1:非空二叉樹上葉結點數等於雙分支結點數加1,即n0=n2+1
(我們約定:二叉樹上葉結點數爲n0,單分支結點數爲n1,雙分支結點數爲n2)

例如:一棵二叉樹中總結點個數爲200,其中單分支結點個數爲19,求其葉子結點個數。
解:n=200,n1=19。又n=n0+n1+n2,由性質1得,n2=n0-1,所以有: n=2n0-1+n1
即n0=(n-n1+1)/2=91,所以這樣的二叉樹中葉子結點個數爲91。

性質2:非空二叉樹上第i層上至多有2^(i-1)個結點,這裏應有i≥1

性質3:高度爲h的二叉樹至多有2^h-1個結點(h≥1)

性質4:對完全二叉樹中編號爲i的結點(1≤i≤n,n≥1,n爲結點數)有:
(1)若i≤|n/2|,即2i≤n,則編號爲i的結點爲分支結點,否則爲葉子結點;
(2)若n爲奇數,則每個分支結點都既有左孩子結點,也有右孩子結點。若n爲偶數,則編號最大的分支結點只有左孩子結點,沒有右孩子結點,其餘分支結點都有左、右孩子結點。
(3)若編號爲i的結點有左孩子結點,則左孩子結點的編號爲2i;若編號爲i的結點有右孩子結點,則右孩子結點的編號爲(2i+1)。
(4)除樹根結點外,若一個結點的編號爲i,則它的雙親結點的編號爲|i/2|,也就是說,當i爲偶數時,其雙親結點的編號爲i/2,它是雙親結點的左孩子結點,當i爲奇數時,其雙親結點的編號爲(i-1)/2,它是雙親結點的右孩子結點。

4.滿二叉樹
在一棵二叉樹中,當第i層的結點數爲2^(i-1)個時,則稱此層的結點數是滿的,當一棵二叉樹中的每一層都滿時,且葉子結點在同一層上,則稱此樹爲滿二叉樹。
在這裏插入圖片描述

滿二叉樹特性:除葉子結點以外的其他結點的度皆爲2。

5.完全二叉樹
在一棵二叉樹中,除最後一層外,若其餘層都是滿的,並且最後一層或者是滿的,或者是在右邊缺少連續若干個結點,則稱此樹爲完全二叉樹。
在這裏插入圖片描述

完全二叉樹特性:二叉樹中至多隻有最下邊兩層結點的度數小於2。

6.層序編號
從根結點爲1開始,按照層次從小到大、同一層從左到右的次序順序編號。如圖:
在這裏插入圖片描述
六.二叉樹的遍歷
二叉樹的遍歷運算是指按一定的次序訪問樹中的所有結點,使每個結點恰好被訪問一次。其中遍歷次序保證了二叉樹上每個結點均被訪問一次且僅有一次。

1.先序遍歷
① 訪問根結點
② 先序遍歷左子樹
③ 先序遍歷右子樹

2.中序遍歷
① 中序遍歷左子樹
② 訪問根結點
③ 中序遍歷右子樹

3.後序遍歷
① 後序遍歷左子樹
② 後序遍歷右子樹
③ 訪問根結點

4.層次遍歷
層次遍歷是從根結點出發,按照從上向下,同一層從左向右的次序訪問所有的結點,採用層次遍歷得到的訪問結點序列稱爲層次遍歷序列。
層次遍歷序列的特點:其第一個元素值爲二叉樹中根結點值。

七.二叉樹的存儲結構
1.順序存儲結構
在這裏插入圖片描述
用一組連續的存儲單元存放二叉樹中的結點
在這裏插入圖片描述
在這裏插入圖片描述
用“#”補齊爲一個完整二叉樹
在這裏插入圖片描述
2.二叉鏈存儲結構
鏈表中的每個結點包含兩個指針,分別指向對應結點的左孩子和右孩子(注意在樹的孩子兄弟鏈表存儲結構中,每個結點的兩個指針分別指向對應結點的第一個孩子和下一個兄弟)

typedef struct tnode
{  ElemType data;			//數據域
   struct tnode *lchild,*rchild;	//指針域
} BTNode;

●data表示數據域,用於存儲放入結點值(默認情況下爲單個字母)
●lchild 和 rchild 分別表示左指針域和右指針域,分別存儲左孩子和右孩子結點(即左、右子樹的根結點)的存儲地址
●當某結點不存在左或右孩子時,其 lchild 或 rchild 指針域取特殊值NULL
在這裏插入圖片描述
八.二叉樹基本運算實現算法
採用括號表示法表示的二叉樹字符串str,且每個結點的值是單個字符。
用ch掃描str,其中只有4類字符,各類字符的處理方式:
1.若ch=’(’:表示前面剛創建的p結點存在着孩子結點,需將其進棧,以便建立它和其孩子結點的關係。然後開始處理該結點的左孩子,因此置k=1,表示其後創建的結點將作爲這個結點(棧頂結點)的左孩子結點;

2.若ch=’)’:表示以棧頂結點爲根結點的子樹創建完畢,將其退棧;

3.若ch=’,’:表示開始處理棧頂結點的右孩子結點,置k=2;

4.其他情況:只能是單個字符,表示要創建一個新結點p,根據k值建立p結點與棧頂結點之間的聯繫,當k=1時,表示p結點作爲棧頂結點的左孩子結點,當k=2時,表示p結點作爲棧頂結點的右孩子結點。

(1)創建二叉樹

void CreateBTree(BTNode * &bt,char *str)
{  BTNode *St[MaxSize],*p=NULL;
   int top=-1,k,j=0;
   char ch;
   bt=NULL;		//建立的二叉樹初始時爲空
   ch=str[j];
   while (ch!='\0')			    //str未掃描完時循環
   {	switch(ch)
	{
	case '(':top++;St[top]=p;k=1; break;    //爲左孩子結點
	case ')':top--;break;
	case ',':k=2; break;		     //爲右孩子結點
	default:p=(BTNode *)malloc(sizeof(BTNode));
		p->data=ch;p->lchild=p->rchild=NULL;
		if (bt==NULL)		     //p爲二叉樹的根結點
		  bt=p;
		else			     //已建立二叉樹根結點
		{   switch(k) 
		  {
		  case 1:St[top]->lchild=p;break;
		  case 2:St[top]->rchild=p;break;
		  }
		}
      }
      j++; ch=str[j];
   }
}

(2)銷燬二叉樹

void DestroyBTree(BTNode *&bt)
{   if (bt!=NULL)
  {	
  	DestroyBTree(bt->lchild);
		DestroyBTree(bt->rchild);
        free(bt);
  }
}

(3)求二叉樹高度

int BTHeight(BTNode *bt) 
{  
	int lchilddep,rchilddep;
    if (bt==NULL) return(0); 		   //空樹的高度爲0
    else
   {  
		lchilddep=BTHeight(bt->lchild);  //求左子樹的高度
		rchilddep=BTHeight(bt->rchild);  //求右子樹的高度
		return (lchilddep>rchilddep)? (lchilddep+1):(rchilddep+1);
   }
}

(4)求二叉樹結點個數

int NodeCount(BTNode *bt)		//求二叉樹bt的結點個數
{  
	int num1,num2;
	if (bt==NULL)			//爲空樹時返回0
	return 0;
	else
  	 {   
		num1=NodeCount(bt->lchild);	//求左子樹結點個數
		num2=NodeCount(bt->rchild);	//求右子樹結點個數
		return (num1+num2+1);		//返回和加上1
     }
}

(5)求二叉樹葉子結點個數

int LeafCount(BTNode *bt)	//求二叉樹bt的葉子結點個數
{  
	int num1,num2;
	if (bt==NULL)		//空樹返回0
	return 0;
	else if (bt->lchild==NULL && bt->rchild==NULL) 
	return 1;		//爲葉子結點時返回1
	else
	 {	
		num1=LeafCount(bt->lchild);	//求左子樹葉子結點個數
		num2=LeafCount(bt->rchild); 	//求右子樹葉子結點個數
		return (num1+num2);		//返回和
   }
}

(6)以括號表示法輸出二叉樹
1.對於非空二叉樹bt,先輸出其元素值;
2.當存在左孩子結點或右孩子結點時,輸出一個“(”符號;
3.遞歸處理左子樹;
4.有右子樹時輸出一個“,”符號;
5.遞歸處理右子樹;
6.最後輸出一個“)”符號;

void DispBTree(BTNode *bt) 
{ 
 if (bt!=NULL)
   {  
   		printf("%c",bt->data);
        if (bt->lchild!=NULL || bt->rchild!=NULL)
         { 
          printf("(");			//有子樹時輸出'('
	     DispBTree(bt->lchild);	//遞歸處理左子樹
		  if (bt->rchild!=NULL)	//有右子樹時輸出','
	   	  printf(",");
		 DispBTree(bt->rchild);	//遞歸處理右子樹
		 printf(")");			//輸出一個')'
    	 }
   }
}

(7)先序遍歷

void PreOrder(BTNode *bt)
{   
	if (bt!=NULL)
 	 {	
 		printf("%c ",bt->data);
		PreOrder(bt->lchild);
		PreOrder(bt->rchild);
    }
}

先序遍歷序列的特點:其第一個元素值爲二叉樹中根結點值。

(8)中序遍歷

void InOrder(BTNode *bt)
{   
	if (bt!=NULL)
 	 {	
  	InOrder(bt->lchild);
		printf("%c ",bt->data);
		InOrder(bt->rchild);
    }
}

中序遍歷序列的特點:若已知二叉樹的根結點值,以該值爲界,將中序遍歷序列分爲兩部分,前半部分爲左子樹的中序遍歷序列,後半部分爲右子樹的中序遍歷序列。

(9)後序遍歷

void PostOrder(BTNode *bt)
{  
	 if (bt!=NULL)
 	 {	
  	PostOrder(bt->lchild);
		PostOrder(bt->rchild);
		printf("%c ",bt->data);
     }
}

後序遍歷序列的特點:最後一個元素值爲二叉樹中根結點值

(10)層次遍歷
層次遍歷算法的實現過程:
1.先將根結點進隊;
2.在隊不空時循環:從隊列中出隊一個結點p,訪問它;若它有左孩子結點,將左孩子結點進隊;若它有右孩子結點,將右孩子結點進隊。
3.如此操作直到隊空爲止。

void LevelOrder(BTNode *bt)
{   
	BTNode *p;
  BTNode *qu[MaxSize];	//定義循環隊列,存放二叉鏈結點指針
  int front,rear;		//定義隊頭和隊尾指針
  front=rear=0;		//置隊列爲空隊列
  rear++; 
  qu[rear]=bt;	//根結點指針進入隊列
  while (front!=rear)		//隊列不爲空循環
  {	
  	front=(front+1)%MaxSize;
		p=qu[front];			//出隊結點p
		printf("%c ",p->data);	//訪問該結點
		if (p->lchild!=NULL)		//有左孩子時將其進隊
		{   
			rear=(rear+1)%MaxSize;
	 		qu[rear]=p->lchild;
		}
		if (p->rchild!=NULL)		//有右孩子時將其進隊
		{   
			rear=(rear+1)%MaxSize;
	  	qu[rear]=p->rchild;
		}
  }
}

(11)二叉樹的拷貝

void CopyBTree(BTNode *bt,BTNode *&nt)
//由二叉樹bt複製產生二叉樹nt
{   
	if (bt!=NULL)
 	 {	
  		nt=(BTNode *)malloc(sizeof(BTNode));	//複製根結點
			nt->data=bt->data;
			CopyBTree(bt->lchild,nt->lchild);	//遞歸複製左子樹
			CopyBTree(bt->rchild,nt->rchild);	//遞歸複製左子樹
 	 }
  else nt=NULL;		//bt爲空樹時nt也爲空樹
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章