1. 程式人生 > >二叉排序樹(查詢、插入、刪除)

二叉排序樹(查詢、插入、刪除)

二叉排序樹,又稱為二叉查詢樹。它或者是一顆空樹,或者具有下列性質的二叉樹。

  • 若它的左子樹不空,則左子樹上所有節點的值均小於它的根節點的值;

  • 若它的右子樹不空,則右子樹上所有節點的值均大於它的根節點的值;

  • 它的左、右子樹也分別為二叉排序樹。

構造一顆二叉排序樹的目的,其實並不是為了排序,而是為了提高查詢和插入刪除關鍵字的速度。不管怎麼說,在一個有序資料集上的查詢,速度總是要快於無序資料集的,而二叉排序樹這種非線性的結構,也有利於插入和刪除的實現。” 


通俗的講,二叉排序樹的本質就是一顆二叉樹,只是關鍵字的排序比較有規律,能夠利用二叉樹的遞迴特性進行很方便的操作。在對於二叉排序樹的基本操作中,包括:根據資料集構建二叉排序樹(沒有要查詢的關鍵字,就插入)、查詢、刪除。其中,刪除操作時最麻煩的,插入和查詢的思路很像,下面詳解。


1、二叉排序樹的查詢操作

首先定義一個二叉樹的結構。

/* 二叉排序樹的節點結構定義 */
typedef struct BiTNode
{
	int data;
	struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;

查詢操作思路:

    先查詢其根節點,如果根節點的資料與key值相等,則返回該根節點,並且返回TRUE;

    否則, 如果key值大於根節點,則查詢其右子樹;

                如果小於根節點,則查詢其左子樹。

程式碼如下:

int SearchBST( BiTree T, int key, BiTree f, BiTree *p )
{
	/* 遞迴查詢二叉排序樹T中是否存在key */
	/* 指標f指向T的雙親,其初始呼叫值為NULL */
	/* 若查詢成功,則指標p指向該資料元素節點,並返回TRUE */
	/* 否則指標p指向查詢路徑上訪問的最後一個節點並返回FALSE */
	if( !T )
	{	
		*p = f;		//這是f唯一被用到的位置。
		return FALSE;	
	}
	else
	{
		if( key == T->data )
		{	*p = T;		return TRUE; }
		else if( key > T->data )
			return SearchBST( T->rchild, key, T, p );		/* 在右子樹繼續查詢 */
		else	
			return SearchBST( T->lchild, key, T, p );		/* 在左子樹繼續查詢 */
		}
}

int SearchBST2( BiTree T, int key, BiTree f, BiTree *p )
{
	/*非遞迴*/
	BiTree s;
	if( !T )
	{	*p = f;		return FALSE;	}
	else
	{
		while( T )
		{
			if( key == T->data )
			{	*p = T;		return TRUE;	}
			if( key > T->data )
			{	s = T;	T = T->rchild;		}
			else
			{	s = T;	T = T->lchild;		}
		}
		*p = s;
		return FALSE;
	}
}


2、二叉排序樹的插入操作

程式碼如下:

int InsertBST1( BiTree *T, int key )
{
	/* 當二叉排序樹T中不存在關鍵字等於key的資料元素時 */
	/* 插入key並返回TRUE,否則返回FALSE */
	/* 呼叫查詢函式SearchBST,非遞迴 */
	BiTree p, s;
	if( !SearchBST2( *T, key, NULL, &p ) )
	{
		s = (BiTree)malloc(sizeof(BiTNode));
		s->data = key;
		s->lchild = s->rchild = NULL;
		if( !p )
			*T = s;				/* 插入s為根節點,此前樹為空樹 */
		else if( key > p->data )
			p->rchild = s;		/* 插入s為右孩子 */
		else
			p->lchild = s;		/* 插入s為左孩子 */
		return TRUE;
	}
	return FALSE;
}

int InsertBST2( BiTree *T, int key )
{
	/* 當二叉排序樹T中不存在關鍵字等於key的資料元素時 */
	/* 插入key並返回TRUE,否則返回FALSE */
	/* 未呼叫查詢函式,遞迴插入 */
	if( !(*T) )									/* 樹為空, */
	{
		(*T) = (BiTree)malloc(sizeof(BiTNode));	/* 這個位置要留心,要重新分配空間,*T為空,說明未曾分配空間 */
		(*T)->data = key;
		(*T)->lchild = (*T)->rchild = NULL;
		return TRUE;
	}
	if( key == (*T)->data )
		return FALSE;
	if( key > (*T)->data )		
		return InsertBST2( &((*T)->rchild), key );		/* 插入右孩子 */
	else
		return InsertBST2( &((*T)->lchild), key );		/* 插入左孩子 */
}

3、二叉樹的刪除操作(相對複雜一些)

    刪除節點有三種情況分析:

        a. 葉子節點;(直接刪除即可)




b. 僅有左或右子樹的節點;(上移子樹即可)


 c. 左右子樹都有的節點。( 用刪除節點的直接前驅或者直接後繼來替換當前節點,調整直接前驅或者直接後繼的位置)


程式碼如下:

int DeleteBST(BiTree *T, int key)
{
	/* 若二叉排序樹T中存在關鍵字等於key的資料元素時,則刪除該資料元素節點 */
	/* 並返回TRUE;否則返回FALSE */
	if( !(*T))
		return FALSE;	/* 不存在關鍵字等於key的資料元素 */
	else
	{
		if( key == (*T)->data )
			Delete(T);
		else if( key < (*T)->data)
			return DeleteBST(&(*T)->lchild, key);
		else
			return DeleteBST(&(*T)->rchild, key);
	}
}

int Delete(BiTree *p)
{
	/* 從二叉排序樹中刪除節點p, 並重接它的左或右子樹 */
	BiTree q, s;
	if(  !(*p)->lchild && !(*p)->rchild )	/* p為葉子節點 */
		*p = NULL;
	else if( !(*p)->lchild )	/* 左子樹為空,重接右子樹 */
	{
		q = *p;	
		*p = (*p)->rchild;
		free(q);
	}
	else if( !(*p)->rchild )	/* 右子樹為空,重接左子樹 */
	{
		q = *p;
		*p = (*p)->lchild;		
		free(q);
	}
	else						/* 左右子樹均不為空 */
	{
		q = *p;
		s = (*p)->lchild;
		while(s->rchild)		/* 轉左,然後向右走到盡頭*/
		{
			q = s;
			s = s->rchild;
		}
		(*p)->data = s->data;
		if( q != *p )				/* 判斷是否執行上述while迴圈 */
			q->rchild = s->lchild;	/* 執行上述while迴圈,重接右子樹 */	
		else
			q->lchild = s->lchild;	/* 未執行上述while迴圈,重接左子樹 */
		free(s);
	}
	return TRUE;
}

總結:二叉樹以鏈式方式儲存,保持了連結儲存結構在執行插入或刪除操作時不用移動元素的優點,只要找到合適的插入和刪除位置後,僅需要修改連結指標節課。插入刪除的時間效能比較好。而丟與二拆排序樹的查詢,走的就是從根節點到要查詢的節點的路徑,其比較次數等於給定值的節點在二叉排序樹的層數。極端情況,最少為1次,即根節點就是要找的節點,最多也不會超過樹的深度。也就是說,二叉排序樹的查詢效能取決於二叉排序樹的形狀。可問題就在於,二叉排序樹的形狀是不確定的。

例如{62,88,58,47,35,73,51,99,37,93}這樣的陣列,我們可以構建一顆正常的二叉排序樹。但是如果陣列元素的次序是從小到大有序,如{35,37,47,51,58,62,73,88,93,99},則二拆排序樹就成了極端的單支樹,注意它依然是一顆二叉排序樹。同樣是查詢節點99,左圖只需要兩次比較,而右圖就需要10次比較才可以得到結果,而這差異很大。


也就是說,我們希望二叉排序樹是比較平衡的,即其深度與完全二叉樹相同。

這樣就延續到了另一篇部落格中要講解的平衡二叉樹。

附加:完整程式碼

#include<stdio.h>
#include<stdlib.h>
#define TRUE 1
#define FALSE 0

/* 二叉排序樹的節點結構定義 */
typedef struct BiTNode
{
	int data;
	struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;


int SearchBST( BiTree T, int key, BiTree f, BiTree *p )
{
	/* 遞迴查詢二叉排序樹T中是否存在key */
	/* 指標f指向T的雙親,其初始呼叫值為NULL */
	/* 若查詢成功,則指標p指向該資料元素節點,並返回TRUE */
	/* 否則指標p指向查詢路徑上訪問的最後一個節點並返回FALSE */
	if( !T )
	{	
		*p = f;		//這是f唯一被用到的位置。
		return FALSE;	
	}
	else
	{
		if( key == T->data )
		{	*p = T;		return TRUE; }
		else if( key > T->data )
			return SearchBST( T->rchild, key, T, p );		/* 在右子樹繼續查詢 */
		else	
			return SearchBST( T->lchild, key, T, p );		/* 在左子樹繼續查詢 */
		}
}

int SearchBST2( BiTree T, int key, BiTree f, BiTree *p )
{
	/*非遞迴*/
	BiTree s;
	if( !T )
	{	*p = f;		return FALSE;	}
	else
	{
		while( T )
		{
			if( key == T->data )
			{	*p = T;		return TRUE;	}
			if( key > T->data )
			{	s = T;	T = T->rchild;		}
			else
			{	s = T;	T = T->lchild;		}
		}
		*p = s;
		return FALSE;
	}
}


int InsertBST1( BiTree *T, int key )
{
	/* 當二叉排序樹T中不存在關鍵字等於key的資料元素時 */
	/* 插入key並返回TRUE,否則返回FALSE */
	/* 呼叫查詢函式SearchBST,非遞迴 */
	BiTree p, s;
	if( !SearchBST2( *T, key, NULL, &p ) )
	{
		s = (BiTree)malloc(sizeof(BiTNode));
		s->data = key;
		s->lchild = s->rchild = NULL;
		if( !p )
			*T = s;				/* 插入s為根節點,此前樹為空樹 */
		else if( key > p->data )
			p->rchild = s;		/* 插入s為右孩子 */
		else
			p->lchild = s;		/* 插入s為左孩子 */
		return TRUE;
	}
	return FALSE;
}

int InsertBST2( BiTree *T, int key )
{
	/* 當二叉排序樹T中不存在關鍵字等於key的資料元素時 */
	/* 插入key並返回TRUE,否則返回FALSE */
	/* 未呼叫查詢函式,遞迴插入 */
	if( !(*T) )									/* 樹為空, */
	{
		(*T) = (BiTree)malloc(sizeof(BiTNode));	/* 這個位置要留心,要重新分配空間,*T為空,說明未曾分配空間 */
		(*T)->data = key;
		(*T)->lchild = (*T)->rchild = NULL;
		return TRUE;
	}
	if( key == (*T)->data )
		return FALSE;
	if( key > (*T)->data )		
		return InsertBST2( &((*T)->rchild), key );		/* 插入右孩子 */
	else
		return InsertBST2( &((*T)->lchild), key );		/* 插入左孩子 */
}


void order(BiTree t)//中序輸出  
{  
    if(t == NULL)  
        return ;  
    order(t->lchild);  
    printf("%d ", t->data);  
    order(t->rchild);  
} 



int DeleteBST(BiTree *T, int key)
{
	/* 若二叉排序樹T中存在關鍵字等於key的資料元素時,則刪除該資料元素節點 */
	/* 並返回TRUE;否則返回FALSE */
	if( !(*T))
		return FALSE;	/* 不存在關鍵字等於key的資料元素 */
	else
	{
		if( key == (*T)->data )
			Delete(T);
		else if( key < (*T)->data)
			return DeleteBST(&(*T)->lchild, key);
		else
			return DeleteBST(&(*T)->rchild, key);
	}
}

int Delete(BiTree *p)
{
	/* 從二叉排序樹中刪除節點p, 並重接它的左或右子樹 */
	BiTree q, s;
	if(  !(*p)->lchild && !(*p)->rchild )	/* p為葉子節點 */
		*p = NULL;
	else if( !(*p)->lchild )	/* 左子樹為空,重接右子樹 */
	{
		q = *p;	
		*p = (*p)->rchild;
		free(q);
	}
	else if( !(*p)->rchild )	/* 右子樹為空,重接左子樹 */
	{
		q = *p;
		*p = (*p)->lchild;		/* 不太理解 */
		free(q);
	}
	else						/* 左右子樹均不為空 */
	{
		q = *p;
		s = (*p)->lchild;
		while(s->rchild)		/* 轉左,然後向右走到盡頭*/
		{
			q = s;
			s = s->rchild;
		}
		(*p)->data = s->data;
		if( q != *p )				/* 判斷是否執行上述while迴圈 */
			q->rchild = s->lchild;	/* 執行上述while迴圈,重接右子樹 */	
		else
			q->lchild = s->lchild;	/* 未執行上述while迴圈,重接左子樹 */
		free(s);
	}
	return TRUE;
}
void main()
{
	int i;
	int a[10] = {62,88,58,47,35,73,51,99,37,93};
	BiTree T = NULL;
	for( i = 0; i < 10; i++ )
		InsertBST1(&T, a[i]);
	printf("中序遍歷二叉排序樹:\n");
	order(T);
	printf("\n");
	printf("刪除58後,中序遍歷二叉排序樹:\n");
	DeleteBST(&T,58);
	order(T);
	printf("\n");
}