1. 程式人生 > >詳解二叉排序樹

詳解二叉排序樹

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

二叉排序樹的定義

二叉排序樹右稱二叉查詢樹。或者為空樹,或者是具有以下性質:

(1)若它的左子樹不為空,則左子樹所有節點的值小於根結點,

(2)若它的右子樹不為空,則根結點的值小於所有右子樹結點的值

(3)它的左右子樹葉分別為二叉排序樹

總結起來就是根據結點的值有:左子樹<根結點<右子樹

如下圖就是一棵二叉排序樹


它的中序遍歷:1219232834363842536590,剛好是排好序的。

二叉排序樹的查詢分析

二叉排序樹有一些基本的應用,可以用於查詢,查詢的長度與樹的深度有關。其平均查詢長度為logN(以2位底的對數,N為結點個數)。

一個序列的排序二叉樹有多種構造情況。下面考慮兩種極端情況:深度最小和深度最大的二叉排序樹。

關鍵字序列(45,24,53,12,37,93),假設6個記錄的查詢概率相等都為1/6


容易看出這是一棵完全二叉樹,該數的深度(最大層次)為3,它的平均查詢長度為:

ASL = 1/61+2+2+3+3+3= 14/6

再來看該關鍵字序列構成深度最大的二叉排序樹

 

這是一棵單支樹,其深度為6,它的平均查詢長度為:

ASL = 1/61+2+3+4+5+6= 21/6

在隨機情況下,該數的平均查詢長度總是介於這兩種情況之間,14/6  < log6 < 21/6。二叉排序樹的平均查詢長度和

logN是等數量級的。

二叉排序樹的構造過程

以上述完全叉樹序列為例,構造該二叉排序數的過程如下圖:

 

容易看出,每次插入新的結點都是二叉排序樹上的葉子結點,則在插入時,不必移動其他結點,只需改動某個結點的指標,由空變為非空即可。

結點的插入操作與二叉排序樹的定義緊密相關,即左<<右,新插入一個關鍵字時,從根結點開始比較,直到找到合適的插入位置為止。還有一種情況就是一個序列中可能有兩個相同的關鍵字,對於這種情況,向樹中插入關鍵字時遇到相同關鍵字時,什麼都不做,不進行重複插入操作。

二叉排序樹的刪除過程

刪除二叉排序樹中任意一個結點,需要分成以下三種情況討論:

(1)刪除的是葉子結點

這種情況最簡單,由於刪除葉子結點不會破壞整棵樹的結構,只需要修改其雙親結點的指標即可。

(2)刪除的結點只有左子樹或只有右子樹。

這種情況只需要將其左子樹或右子樹往上推,替代要刪除結點的位置,顯然,作此修改也不會破壞二叉排序樹的結構。

(3)刪除的結點左右子樹都不為空

這種情況稍微複雜一點,為了不保持二叉排序樹的結構,有兩種思路,一種就是從要刪除結點的左子樹中選取最大的結點進行替代之,另一種就是從要刪除的結點的右子樹中選取最小的結點替代之。

下面考慮第三種情況


fpqs分別為指向結點FPQS的指標,假如現要刪除結點P,它的左右子樹都不為空。

其一就是尋找P的左子樹中的最大結點,代替之。

易知左子樹中最大結點為S,將P結點用S結點代替,還需處理S結點的左孩子K,由於S只有左子樹K,在刪除結點S之後,只要令KS的雙親Q的右子樹即可。

其二就是尋找P結點的右子樹中最小的結點,代替之。

找到P後,第一步就是往右走一步到R,若R的左子樹不為空,則最小的結點在R的左子樹中,只需一直往左走即p=p->Left,指定p->Left為空(右子樹最小結點,其左子樹定為空

,則找到最小結點,將其代替要刪除的結點P,並將最小結點的右子樹代替該最小結點即可。如下圖,Z為要刪除結點中右子樹中最小結點,將PZ代替後,Z的位置再由Y代替

 

R的左子樹為空,那麼最小結點就是R,只需將R往上推代替P


具體實現:

/*BinarySearchTree.cpp*/
#include<stdio.h>
#include<stdlib.h>

typedef int ElementType;
typedef struct TreeNode{
	ElementType Element;
	struct TreeNode *Left;
	struct TreeNode *Right;
}TreeNode,*SearchTree;

SearchTree Insert(SearchTree T,ElementType e)
{
	//二叉排序樹的插入過程
	if(!T)
	{
		//未找到值為e的結點,就新生成一個結點
		T = (TreeNode*)malloc(sizeof(TreeNode));
		T->Element = e;
		T->Left = NULL;
		T->Right = NULL;
	}
	
	else if(e<T->Element)		//在左子樹中插入
		T->Left = Insert(T->Left,e);
	else if(e>T->Element)		//在右子樹中插入
		T->Right = Insert(T->Right,e);
	else		//e == T->Element do nothing
	{
	}
	return T;
}


int Delete(SearchTree &p)
{
	//從二叉排序樹中刪除結點p,並重接它的左子樹或右子樹
	TreeNode *q,*s;
	if(!p->Right)		//右子樹為空,重接左子樹
	{
		q = p; 
		p = p->Left;
		free(q);
	}
	else if(!p->Left)	//左子樹為空,重接右子樹
	{
		q = p;
		p = p->Right;
		free(q);
	}
	else 				//左右子樹都不為空
	{
		q = p;
		s = p->Left;
		while(s->Right)
		{
			q = s;				//q是s的前驅結點
			s = s->Right;			//往右走到盡頭
		}
		p->Element = s->Element;
		if(q != p)
			q->Right = s->Left;
		else 			//q = p;
			q->Left = s->Left;
		free(s);
	}
	return 1;
}

int DeleteBST(SearchTree &T,ElementType x)//考慮這裡為什麼用引用?
{
	//在排序二叉樹T中刪除關鍵字等於x的結點
	if(!T)						//不存在關鍵字等於x的資料元素
		return -1;
	else
	{
		if(x == T->Element)	//查詢成功
			return Delete(T);
		else if(x < T->Element)
			return DeleteBST(T->Left,x);		//在左子樹中繼續查詢
		else 
			return DeleteBST(T->Right,x);		//在右子樹中繼續查詢
	}
	return 1;
}


void InOderTravesal(SearchTree T)
{
	//中序遍歷
	if(T)
	{
		InOderTravesal(T->Left);
		printf("%d ",T->Element);
		InOderTravesal(T->Right);
	}
}

void removeTree(SearchTree T)
{
	//銷燬二叉樹
	if(T)
	{
		removeTree(T->Left);
		removeTree(T->Right);
		free(T);
		T=NULL;
	}
}

int main()
{
	SearchTree T=NULL;
	int a[7] = {45,24,53,45,12,24,90},i;
	
	for(i=0; i<7; ++i)
		T=Insert(T,a[i]);
	
	InOderTravesal(T);
	printf("\n");
	
	DeleteBST(T,24);
	InOderTravesal(T);

	removeTree(T);
	return 0;
}