數據結構與算法二 - 平衡二叉樹


 

 

1 引言

  二叉樹是數據結構中的重點與難點,也是應用較為廣泛的一類數據結構。二叉樹的基礎知識在之前的數據結構與算法——二叉樹基礎中已經詳細介紹。本篇文章將着重介紹兩類二叉樹,二叉搜索樹和平衡二叉樹。

 

2 二叉搜索樹

2.1 定義

  二叉搜索樹又稱二叉查找樹,亦稱為二叉排序樹。設x為二叉查找樹中的一個節點,x節點包含關鍵字key,節點x的key值記為key[x]。如果y是x的左子樹中的一個節點,則key[y] <= key[x];如果y是x的右子樹的一個節點,則key[y] >= key[x]。

2.2 性質

  (1)若左子樹不空,則左子樹上所有節點的值均小於它的根節點的值;
  (2)若右子樹不空,則右子樹上所有節點的值均大於它的根節點的值;
  (3)左、右子樹也分別為二叉搜索樹;

  例如:圖2.2.1所示的二叉樹為一棵二叉搜索樹。
圖2.2.1

圖2.2.1

  例如:圖2.2.2所示不是一棵二叉搜索樹,因為節點40的左孩子節點值為44,不滿足二叉搜索樹的定義。
圖2.2.2

圖2.2.2

2.3 節點結構

  二叉樹的節點結構通常包含三部分,其中有:左孩子的指針,右孩子指針以及數據域。節點的圖示如下:

代碼定義: 

struct BSTNode
{
	int key; //節點數據
	struct BSTNode *lchild, *rchild; /* 左右孩子指針 */
};

2.4 創建二叉搜索樹

  現有序列:A = {61, 87, 59, 47, 35, 73, 51, 98, 37, 93}。根據此序列構造二叉搜索樹過程如下:

  (1)i = 0,A[0] = 61,節點61作為根節點;
  (2)i = 1,A[1] = 87,87 > 61,且節點61右孩子為空,故81為61節點的右孩子;
  (3)i = 2,A[2] = 59,59 < 61,且節點61左孩子為空,故59為61節點的左孩子;
  (4)i = 3,A[3] = 47,47 < 59,且節點59左孩子為空,故47為59節點的左孩子;
  (5)i = 4,A[4] = 35,35 < 47,且節點47左孩子為空,故35為47節點的左孩子;
  (6)i = 5,A[5] = 73,73 < 87,且節點87左孩子為空,故73為87節點的左孩子;
  (7)i = 6,A[6] = 51,47 < 51,且節點47右孩子為空,故51為47節點的右孩子;
  (8)i = 7,A[7] = 98,98 < 87,且節點87右孩子為空,故98為87節點的右孩子;
  (9)i = 8,A[8] = 93,93 < 98,且節點98左孩子為空,故93為98節點的左孩子;創建完畢后如圖2.4中的二叉搜索樹:

圖2.4

圖2.4

2.5 查找

  查找流程:
  (1)如果樹是空的,則查找結束,無匹配。
  (2)如果被查找的值和節點的值相等,查找成功。
  (3)如果被查找的值小於節點的值,遞歸查找左子樹,
  (4)如果被查找的值大於節點的值,遞歸查找右子樹,

  查找代碼: 

bool searchBST(BSTNode* T, int key, BSTNode* f, BSTNode **p)
{
	if (!T) /* 查找不成功 */
	{
	*p = f;
	return false;
	}
	else if (key == T->key) /* 查找成功 */
	{
	*p = T;
	return true;
	}
	else if (key < T->key)
	return searchBST(T->lchild, key, T, p); /* 在左子樹中繼續查找 */
	else
	return searchBST(T->rchild, key, T, p); /* 在右子樹中繼續查找 */
}

  使用二叉搜索樹可以提高查找效率,其平均時間復雜度為O(log2n)。

2.6 插入

  插入流程:
  (1)先檢測該元素是否在樹中已經存在。如果已經存在,則不進行插入;
  (2)若元素不存在,則進行查找過程,並將元素插入在查找結束的位置。

  圖解過程:

代碼實現:

void insertBST(BSTNode **T,int key) //此處使用二重指針是因為要修改指針的指針
{
	BSTNode *s;
	if(*T==NULL) //到達查找結束位置,再次位置插入元素
	{
	s = (BSTNode*)malloc(sizeof(BSTNode));
	s->key = key;
	s->lchild = NULL;
	s->rchild = NULL;
	*T=s;
	}
	else if(key<(*T)->key)//要插入的值大於當前節點,往左子樹搜
	{
	insertBST(&((*T)->lchild),key);
	}
	else if(key>(*T)->key)//大於當前節點,往右子樹搜
	{
	insertBST(&((*T)->rchild),key);
	}
}

2.7 刪除

  1) 刪除節點為葉子節點
  刪除葉子節點的方式最為簡單,只需查找到該節點,直接刪除即可。例如刪除圖2.4中的葉子節點37、節點51、節點60、節點73和節點93的方式是相同的。

  2) 刪除的節點只有左子樹
  刪除的節點若只有左子樹,將節點的左子樹替代該節點位置。例如:刪除圖2.4中的98節點:

  3)刪除的節點只有右子樹
  刪除的節點若只有右子樹,將節點的右子樹替代該節點位置。這種情況與刪除左子樹處理方式類似,不再贅述。

  4)刪除的節點既有左子樹又有右子樹。
  若刪除的節點既有左子樹又有右子樹,這種節點刪除過程相對復雜。其流程如下:
  (1)遍歷待刪除節點的左子樹,找到其左子樹中的最大節點,即刪除節點的前驅節點;
  (2)將最大節點代替被刪除節點;
  (3)刪除左子樹中的最大節點;
  (4)左子樹中待刪除最大節點一定為葉子節點或者僅有左子樹。按照之前情形刪除即可。

  注:同樣可以使用刪除節點的右子樹中最小節點,即后繼節點代替刪除節點,此流程與使用前驅節點類似。

  刪除代碼:

/* 從二叉排序樹中刪除節點p,並重接它的左或右子樹。 */
bool deleteBSTNode(BSTNode* p)
{
	BSTNode* q,s;
	if((*p)->rchild==NULL) //右子樹空則只需重接它的左子樹(待刪節點是葉子也走此分支)
	{
	q=*p;
	*p=(*p)->lchild;
	free(q);
	}
	else if((*p)->lchild==NULL) //左子樹為空,只需重接它的右子樹
	{
	q=*p;
	*p=(*p)->rchild;
	free(q);
	}
	else //左右子樹均不空
	{
	q=*p;
	s=(*p)->lchild;
	while(s->rchild) // 轉到左子樹,然后向右到盡頭(找待刪節點的前驅) */
	{
	q=s;
	s=s->rchild;
	}
	(*p)->key=s->key; //s指向被刪節點的直接前驅(將被刪節點前驅的值取代被刪節點的值)
	if(q!=*p)
	q->rchild=s->lchild; //重接q的右子樹
	else
	q->lchild=s->lchild; //重接q的左子樹
	free(s);
	}
	return TRUE;
}

3 平衡二叉樹

3.1 定義

  二叉搜索樹一定程度上可以提高搜索效率,但是當原序列有序,例如序列A = {1,2,3,4,5,6},構造二叉搜索樹如圖3.1。依據此序列構造的二叉搜索樹為右斜樹,同時二叉樹退化成單鏈表,搜索效率降低為O(n)。

圖3.1

圖3.1

 

  在此二叉搜索樹中查找元素6需要查找6次。二叉搜索樹的查找效率取決於樹的高度,因此保持樹的高度最小,即可保證樹的查找效率。同樣的序列A,改為圖3.2方式存儲,查找元素6時只需比較3次,查找效率提升一倍。

  可以看出當節點數目一定,保持樹的左右兩端保持平衡,樹的查找效率最高。這種左右子樹的高度相差不超過1的樹為平衡二叉樹。

非平衡二叉樹

非平衡二叉樹

 

平衡二叉樹

平衡二叉樹

3.2 平衡因子

  定義:某節點的左子樹與右子樹的高度(深度)差即為該節點的平衡因子(BF,Balance Factor),平衡二叉樹中不存在平衡因子大於1的節點。在一棵平衡二叉樹中,節點的平衡因子只能取-1、1或者0。

3.3 節點結構

  定義平衡二叉樹的節點結構:  

typedef struct AVLNode *Tree;
typedef int ElementType;
struct AVLNode
{
	int depth; //深度,這里計算每個結點的深度,通過深度的比較可得出是否平衡
	Tree parent; //該結點的父節點,方便操作
	ElementType val; //結點值
	Tree lchild;
	Tree rchild;
	AVLNode(int val=0) //默認構造函數
	{
		parent=NULL;
		depth=0;
		lchild=rchild=NULL;
		this->val=val;
	}
};

對於給定結點數為n的AVL樹,最大高度為O(log2n)。

3.4 左旋與右旋

  1) 左旋
  如圖3.4.1所示的平衡二叉樹

  如在此平衡二叉樹插入節點62,樹結構變為:

  可以得出40節點的左子樹高度為1,右子樹高度為3,此時平衡因子為-2,樹失去平衡。為保證樹的平衡,此時需要對節點40做出旋轉,因為右子樹高度高於左子樹,對節點進行左旋操作,流程如下:
  (1)節點的右孩子替代此節點位置
  (2)右孩子的左子樹變為該節點的右子樹
  (3)節點本身變為右孩子的左子樹

  圖解過程:


  2)右旋
  右旋操作與左旋類似,操作流程為:
  (1)節點的左孩子代表此節點
  (2)節點的左孩子的右子樹變為節點的左子樹
  (3)將此節點作為左孩子節點的右子樹。

  圖解過程:

3.5 插入

  假設一顆 AVL 樹的某個節點為A,有四種操作會使 A 的左右子樹高度差大於 1,從而破壞了原有 AVL 樹的平衡性。平衡二叉樹插入節點的情況分為以下四種:

  1) A的左孩子的左子樹插入節點(LL)

  例如:圖3.5.1所示的平衡二叉樹:
圖3.5.1

圖3.5.1 

  節點A的左孩子為B,B的左子樹為D,無論在節點D的左子樹或者右子樹中插入F均會導致節點A失衡。因此需要對節點A進行旋轉操作。A的平衡因子為2,值為正,因此對A進行右旋操作。

  圖解過程:

代碼實現:

//LL型調整函數
//返回:新父節點
Tree LL_rotate(Tree node)
{
	//node為離操作結點最近的失衡的結點
	Tree parent=NULL,son;
	//獲取失衡結點的父節點
	parent=node->parent;
	//獲取失衡結點的左孩子
	son=node->lchild;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM