樹的四種遍歷案例和AVL樹的旋轉的四種情況

樹與二叉樹

一, 樹的基本術語

在這裏插入圖片描述

  1. 樹中一個結點的子結點個數稱爲該結點的度, 樹中結點的最大度數稱爲樹的度。如結點B的度爲2,結點D的度爲3,樹的度爲3.
  2. 度大於0的結點稱爲分支結點(又稱非終端結點),度爲0(沒有子女結點)的結點稱爲葉子結點(又稱終端結點)。在分支結點中,每個結點的分支數就是該結點的度。
  3. 結點的深度,高度和層次。

二, 樹的性質

  1. 樹中的結點數等於所有結點的度數加1。
  2. 度爲m的樹中第i層上至多有m^(i-1)個結點(i>=0)。
  3. 高度爲h的m叉樹至多有(m^h-1)/(m-1)個結點。
  4. 具有n個結點的m叉樹的最小高度爲[log m(n(m-1)+1)]。

三, 二叉樹的概念

1, 幾種特殊的二叉樹

在這裏插入圖片描述

  1. 滿二叉樹。一顆高度爲h。且含有2^h - 1個結點的二叉樹稱爲滿二叉樹,即樹中的每層都含有最多的結點,上圖所示。滿二叉樹的葉子結點都集中在二叉樹的最下一層,並且除葉子結點之外的每個結點度數均爲2。

可以對滿二叉樹按層續編號:約定編號從跟結點(跟結點編號爲1)起,自上而下,自左而右。這樣,每個結點對應一個編號,對於編號爲i的結點,若有雙親,則其雙親爲[i/2],若有左孩子,則左孩子爲2i; 若有右孩子,則右孩子爲2i+1。

在這裏插入圖片描述

  1. 完全二叉樹。設一個高度爲h,有n個結點的二叉樹,當且僅當其每個結點都與高度爲h的滿二叉樹中編號爲1到n的結點一一對應時,稱爲完全二叉樹,如上圖。這種樹的特點如下:

①. 如i<[i/2],則結點i爲分支結點,否則爲葉子結點。

②. 葉子節點只可能在層次最大的兩層上出現。對於最大層次中的葉子結點,都依賴排列在該層最左邊的位置上。

③. 若有度爲1的結點,則只可能有一個,且該結點只有左孩子而無右孩子。

④. 按層序編號後,一旦出現某個結點(編號爲i)爲葉子結點或只有左孩子, 則編號大於i的結點均爲葉子結點。

⑤. 若n爲奇數,則每個分支結點都有左子女和右子女; 若n爲二數,則編號最大的分支結點(編號爲n/2)只有左子女,沒有右子女,其中分支結點左,右子女都有。

2, 二叉樹的性質

在這裏插入圖片描述

  1. 非空二叉樹上的葉子結點數等於度爲2的結點數加1,即n0 = n2 + 1。
  2. 非空二叉樹上第k層上至多有2^k-1結點(k>=1)。
  3. 高度爲h的二叉樹至多有2^h-1個結點(h>=1)。
  4. 對於完全二叉樹按從上到下
  5. 具有n個(n>0)結點的完全二叉樹的高度爲[log2(n+1)]或者[|log2n|+1]。

四, 二叉樹的四種遍歷和線索二叉樹

1, 二叉樹的四種遍歷

在這裏插入圖片描述

所謂二叉樹的遍歷,是指按某條搜索路徑訪問樹中的每個結點,使得每個結點均被服務一次,而且僅被訪問一次。

由二叉樹的遞歸定義可知,遍歷一顆二叉樹便要決定根結點N,左子樹L和右子樹R的訪問順序。按照先遍歷左子樹再遍歷右子樹的原則,常見的遍歷次序有先序(NLR),中序(LNR),和後序(LRN)三種遍歷算法,其中"序"指的是根結點再何時被訪問。

① 先序遍歷

先序遍歷(preorder)的操作過程如下:

若二叉樹爲空,則什麼也不做,否則,

  1. 訪問根結點;
  2. 先序遍歷左子樹;
  3. 先序遍歷右子樹;

對應的遞歸算法如下:

void preorder(BiTree T)
{
	if (T)
	{
		//訪問根結點
		show(T); 
		//遞歸遍歷左子樹
		preorder(T->lchild);
		//遞歸遍歷右子樹
		preorder(T->rchild);
	}
}

如上圖所示, 先序遍歷所得的結點序列爲 1, 2, 4, 8, 9, 5, 10, 11, 3, 6, 12, 13, 7, 14, 15

② 中序遍歷

中序遍歷(inorder)的操作過程如下:
若二叉樹爲空,則什麼也不做,否則:

  1. 中序遍歷左子樹,
  2. 訪問根結點
  3. 中序遍歷右子樹

對應的遞歸算法如下:

void inorder(BiTree T)
{
	if (T)
	{
		// 遞歸遍歷左子樹
		inorder(T->lchild);
		//訪問根結點
		show(T);
		// 遞歸遍歷右子樹
		inorder(T->rchild);
	}
}

對應上圖二叉樹, 中序遍歷所得到的結點序列爲 8, 4, 9, 2, 10, 5, 11, 1, 12, 6, 13, 3, 14, 15, 7

③ 後序遍歷

後序遍歷(postorder)的操作過程如下
若二叉樹爲空,則什麼也不做;否則

  1. 後序遍歷左子樹
  2. 後序遍歷右子樹
  3. 訪問根結點

對應的遞歸算法如下

void postorder(BiTree T)
{
	if (T)
	{
		//遞歸遍歷左子樹
		postorder(T->lchild);
		// 遞歸遍歷右子樹
		postorder(T->rchild);
		//訪問根結點
		show(T);
	}
}

對應上圖二叉樹 :後序遍歷得到的結點序列爲 8, 9, 4, 10, 11, 5, 2, 12, 13, 6, 14, 15, 7, 3, 1

總結三種遍歷算法中,遞歸遍歷左,右子樹的順序都是固定的, 只是訪問根結點的順序不同。不管採用哪種遍歷算法,每個結點都訪問一次且僅訪問一次,故時間複雜度都是O(n)。在遞歸遍歷中,遞歸工作棧的棧深恰好爲樹的深度,所以在再壞情況下,二叉樹是有n個結點且深度爲n的單支樹,遍歷算法的空間複雜度爲O(n)。

④ 遞歸算法和非遞歸算法的轉換

藉助棧,可以將二叉樹的遞歸遍歷算法轉換爲非遞歸算法。下面以中序遍歷爲列子給出中序遍歷的非遞歸算法。先掃描(並非訪問)根結點的所有左結點並將它們一一進棧,然後出棧一個結點p(顯然結點p沒有左孩子結點或者左孩子結點已經被訪問過),訪問它。然後掃描該結點的右孩子結點,將其進棧,再掃描該右孩子結點的所有左結點並一一進棧,如此繼續,直到棧空爲止。

中序遍歷的非遞歸算法如下:

void inorder2(BiTree T)
{
	//初始化棧
	initstatck(s);
	//p是遍歷指針
	TiTree p = T;
	// 棧不空或者p不空時循環
	while (p || !s.emptr())
	{
		
		if (p)
		{
			//根指針進棧,遍歷左子樹
			s.push(p);
			//每遇到非空二叉樹先向左走
			p = p->lchild;
		}
		else 
		{
			// 根指針退棧, 訪問根結點,遍歷右子樹
			//退棧, 訪問根結點
			s.pop(p);
			show(p);
			// 再向右子樹走
			p = p->rchild;
		}
	}
}

後序遍歷算法如下

void postorder2(BiTree T)
{
	if (T)
	{
		s.push(T);
		BiTree p = NULL;
		while (!s.empty())
		{
			p = s.top();
			// 判斷當前lcheild和rchild是否已經再棧中 
			if (p->lchild && p->lchild != T && p->rchild != T)
			{
				s.push(p);
			}
			else if (p->rchild && p->rchild != T)
			{
				s.push(p);
			}
			else 
			{
				s.pop(p);
				show(p);
				T = p;
			}
		}
	}
}

再寫非遞歸遍歷時要有遞歸壓棧的模型 來使用棧的數據結構

⑤ 層次遍歷

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-EHHdrRzx-1574611452390)(https://github.com/chensongpoixs/cstructdata/raw/master/%E4%B8%89%2C%E6%A0%91%E4%B8%8E%E4%BA%8C%E5%8F%89%E6%A0%91/img/binary_tree_order_level.png?raw=true)]

如上圖所示爲二叉樹的層次遍歷,即按照箭頭所指方向,按照1, 2, 3, 4 層次順序,對二叉樹中的各個結點進行訪問。

要進行層次遍歷,需要藉助一個隊列。先將二叉樹根結點入隊,然後出隊,訪問該結點,若它有左子樹,則將左子樹根結點入隊,如它有右子樹,則將右子樹根結點入隊。然後出隊,對出隊結點訪問,如此反覆,直到隊列爲空。

二叉樹的層次遍歷算法如下

void level_order(BiTree T)
{
	initqueue(q);
	BiTree p;
	//將根結點入隊列中
	q.endqueue(T);
	//隊列不空循環
	while (!q.empty())
	{
		//隊頭元素出隊
		q.dequeue(p);
		show(p);
		
		if (p->lchild)
		{
			//左子樹不空,則左子樹入隊列
			q.endqueue(p->lchild);
		}
		if (p->rchild)
		{
			//右子樹不空, 則右子樹入隊列
			q.endqueue(p->rchild);
		}
	}
}

五, 樹與二叉樹的應用

1, 平衡二叉樹

① 平衡二叉樹的定義

在這裏插入圖片描述

爲避免樹的高度增長過快,降低二叉排序樹的性能, 我們規定在插入和刪除二叉樹的結點的時, 要保證任意結點的左, 右子樹高度差的絕對不超過1, 將這樣的二叉樹稱爲平衡二叉樹(Balanced Binary Tree), 簡稱平衡樹(AVL)。定義結點左子樹和右子樹的高度差爲該結點的平衡因子,則平衡二叉樹結點的平衡因子的值只可能是-1, 0或1。
因此,平衡二叉樹可定義的爲或者是一顆空樹, 或者是具有下列性質的二叉樹; 它的左子樹和右子樹都是平衡二叉樹,且左子樹和右子樹的高度差的絕對值不超過1。如上圖a所示 ,是平衡二叉樹,如上圖b所示是不平衡二叉樹。結點種的值爲該結點的平衡因子。

② 平衡二叉樹的插入

二叉排序樹保證平衡的基本思想如下:每當在二叉排序樹種插入(或刪除)一個結點時,首先檢查其插入路徑上的結點因爲此次操作而導致了不平衡。若導致了不平衡,在保持二叉排序樹特性的前提下,調整各結點的位置關係,使之重新達到平衡。

在這裏插入圖片描述

注意:每次調整的對象都是最小的不平衡子樹,即以插入路徑上離插入結點最近的平衡因子的絕對值大於1的結點作爲根的子樹。如上圖所示框的爲最小不平衡子樹。

平衡二叉樹的插入過程的前半部分與二叉排序樹相同,但在新結點插入後,若造成查找路徑上的某個結點不再平衡,則需要做出相應的調整。一般可將失去平衡後進行調整的規律歸納爲下列4種情況:

1, LL平衡旋轉(右單旋轉)

由於在結點A的左孩子(L)的左子樹(L)上插入了新的結點,A的平衡因子由1曾至2, 導致以A爲根的子樹失去平衡, 需要一次向右的旋轉操作。將A的左孩子B向右上旋轉代替A成爲根結點,將A結點向右下旋轉成爲B的右子樹的根結點,而B的原右子樹則作爲A結點的左子樹。

在這裏插入圖片描述

2, RR平衡旋轉(左單旋轉)

由於在結點A的右孩子®的右子樹®上插入了新結點,A的平衡因子由-1減至-2,導致以A爲根的子樹失去平衡,需要一次向左的旋轉操作。將A的右孩子B向左上旋轉替代A成爲根結點,將A結點向左下旋轉成爲B的左子樹的根結點,而B的原右子樹則作爲A結點的右子樹,

在這裏插入圖片描述

3, LR平衡旋轉(先左後右雙旋轉)

由於在A的左孩子(L)的右子樹®上插入新的結點,A的平衡因子由1曾至2,導致以A爲根的子樹失去平衡,需要進行二次旋轉操作,先左旋轉後右旋轉。先將A結點的左孩子B的右子樹的根結點C向左上旋轉提升到B結點的位置,然後再把該C結點向右上旋轉提升到A結點的位置。

在這裏插入圖片描述

4, RL平衡旋轉(先右後左雙旋轉)

由於在A的右孩子®的左子樹(L)上插入新的結點,A的平衡因子由-1減至-2,導致以A爲根的子樹失去平衡,需要進行二次旋轉操作,先右旋轉後左旋轉。先將A結點的右孩子B的左子樹的根結點C向右上旋轉提升到B結點的位置,然後再把該結點向左上旋轉提升到A結點的位置。

在這裏插入圖片描述

個人博客地址:https://chensongpoixs.github.io/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章