二叉排序樹的創建


二叉查找樹(Binary Search Tree)又叫二叉排序樹(Binary Sort Tree),它是一種數據結構,支持多種動態集合操作,如 Search、Insert、Delete、Minimum 和 Maximum 等。

二叉查找樹要么是一棵空樹,要么是一棵具有如下性質的非空二叉樹:

  1. 若左子樹非空,則左子樹上的所有結點的關鍵字值均小於根結點的關鍵字值。

  2. 若右子樹非空,則右子樹上的所有結點的關鍵字值均大於根結點的關鍵字值。

  3. 左、右子樹本身也分別是一棵二叉查找樹(二叉排序樹)。

可以看出,二叉查找樹是一個遞歸的數據結構,且對二叉查找樹進行中序遍歷,可以得到一個遞增的有序序列。

首先,我們來定義一下 BST 的結點結構體,結點中除了 key 域,還包含域 left, right 和 parent,它們分別指向結點的左兒子、右兒子和父節點:

typedef struct Node 
{
	int key;
	Node* left;
	Node* right;
	Node* parent;
} *BSTree;

  

一、BST的插入與構造

二叉查找樹作為一種動態結構,其特點是樹的結構通常不是一次生成的,而是在查找過程中,當樹中不存在結點的關鍵字等於給定值時再進行插入。

由於二叉查找樹是遞歸定義的,插入結點的過程是:若原二叉查找樹為空,則直接插入;否則,若關鍵字 k 小於根結點關鍵字,則插入到左子樹中,若關鍵字 k 大於根結點關鍵字,則插入到右子樹中。

/**
 * 插入:將關鍵字k插入到二叉查找樹
 */
int BST_Insert(BSTree &T, int k)
{
	if(T == NULL)
	{
		BSTree T = new BstNode;
		T->key = k;
		T->left = NULL;
		T->right = NULL;
		return 1;  // 返回1表示成功
	}
	else if(k == T->key)
		return 0;  // 樹中存在相同關鍵字
	else if(k < T->key)
		return BST_Insert(T->left, k);
	else
		return BST_Insert(T->right, k);
}

  構造一棵二叉查找樹就是依次輸入數據元素,並將它們插入到二叉排序樹中的適當位置。具體過程是:每讀入一個元素,就建立一個新結點;若二叉查找樹為空,則新結點作為根結點;若二叉查找樹非空,則將新結點的值與根結點的值比較,如果小於根結點的值,則插入到左子樹中,否則插入到右子樹中。

/**
 * 構造:用數組arr[]創建二叉查找樹
 */
void Create_BST(BSTree &T, int arr[], int n)
{
	T = NULL;  // 初始時為空樹
	for(int i=0; i<n; ++i)
		BST_Insert(T, arr[i]);
}

  注意,插入的新結點一定是某個葉結點。另外,插入操作既可以遞歸實現,也可以使用非遞歸(迭代)實現。通常來說非遞歸的效率會更高。

/**
 * 非遞歸插入:將關鍵字k插入到二叉查找樹
 */
int BST_Insert_NonRecur(BSTree &T, int k)
{
	Node* pre = NULL;  // 記錄上一個結點
	Node* t = T;
	while(t != NULL)
	{
		pre = t;
		if(k < t->key)
			t = t->left;
		else if(k > t->key)
			t = t->right;
		else
			return 0;
	}

	Node* node = (Node*)malloc(sizeof(Node));
	node->key = k;
	node->left = NULL;
	node->right = NULL;
	node->parent = pre;

	if(pre == NULL)
		T = node;
	else
	{
		if(k < pre->key)
			pre->left = node;
		else
			pre->right = node;
	}
	return 1;
}

  

二、BST的查找

對於二叉查找樹,最常見的操作就是查找樹中的某個關鍵字。除了Search操作外,二叉查找樹還能支持如 Minimum(最小值)、Maximum(最大值)、Predecessor(前驅)、Successor(后繼)等查詢。對於高度為 h 的樹,這些操作都可以在 Θ(h) 時間內完成。

1. 查找

BST 的查找是從根結點開始,若二叉樹非空,將給定值與根結點的關鍵字比較,若相等,則查找成功;若不等,則當給定值小於根結點關鍵字時,在根結點的左子樹中查找,否則在根結點的右子樹中查找。顯然,這是一個遞歸的過程。

/**
 * 遞歸查找:返回指向包含關鍵字k的結點的指針
 */
Node* BST_Search(BSTree T, int k)
{
	if(T == NULL || k == T->key)
		return T;
	if(k < T->key)
		return BST_Search(T->left, k);
	else
		return BST_Search(T->right, k);
}

  也可以使用非遞歸的實現:

/**
 * 非遞歸查找:返回指向包含關鍵字k的結點的指針
 */
Node* BST_Search_NonRecur(BSTree T, int k)
{
	while(T != NULL && k != T->key)
	{
		if(k < T->key)
			T = T->left;
		else
			T = T->right;
	}
	return T;
}

  

2. 最大值與最小值

由二叉查找樹的性質可知,最左下結點即為關鍵字最小的結點,最右下結點即為關鍵字最大的結點。此過程無需比較,只需要沿着最左和最右的路徑查找下去,直到遇到 NULL 為止。

/**
 * 最小值:查找二叉查找樹中關鍵字最小的結點
 */
Node* BST_Minimum(BSTree T)
{
	while(T->left != NULL)
		T = T->left;
	return T;
}

/**
 * 最大值:查找二叉查找樹中關鍵字最大的結點
 */
Node* BST_Maximum(BSTree T)
{
	while(T->right != NULL)
		T = T->right;
	return T;
}

  

3. 前驅與后繼

給定一個二叉查找樹的結點,求出它在中序遍歷中的前驅與后繼。如果所有的關鍵字均不相同,則某結點 x 的后繼是:

  • 若結點 x 的右子樹不為空,則 x 的后繼就是它的右子樹中關鍵字值最小的結點;

  • 若結點 x 的右子樹為空,為了找到其后繼,從結點 x 開始向上查找,直到遇到一個祖先結點 y,它的左兒子也是結點 x 的祖先,則結點 y 就是結點 x 的后繼。如下圖

  •  

 

/**
 * 后繼:查找給定結點在中序遍歷中的后繼結點
 */
Node* BST_Successor(Node* node)
{
	if(node->right != NULL)
		return BST_Minimum(node->right);
	Node* p = node->parent;
	while(p!=NULL && p->right == node)
	{
		node = p;
		p = p->parent;
	}
	return p;
}

  

求前驅(predecessor)的過程對稱,對於某個結點 x ,它的前驅是:

  • 若結點 x 的左子樹不為空,則 x 的前驅是它的左子樹中關鍵字值最大的結點;

  • 若結點 x 的左子樹為空,為了找到其前驅,從結點 x 開始向上查找,直到遇到一個祖先結點 y,它的右兒子也是結點 x 的祖先,則結點 y 就是結點 x 的前驅。

    /**
     * 前驅:查找給定結點在中序遍歷中的前驅結點
     */
    Node* BST_Predecessor(Node* node)
    {
    	if(node->left != NULL)
    		return BST_Maximum(node->left);
    	Node* p = node->parent;
    	while(p!=NULL && p->left == node)
    	{
    		node = p;
    		p = p->parent;
    	}
    	return p;
    }
    

      

    之所以在這里討論如何求中序序列的后繼,主要是為了后面講刪除操作做鋪墊。

    三、BST的刪除

    二叉查找樹的刪除操作是相對復雜一點,它要按 3 種情況來處理:

  • 若被刪除結點 z 是葉子結點,則直接刪除,不會破壞二叉排序樹的性質;

  • 若結點 z 只有左子樹或只有右子樹,則讓 z 的子樹成為 z 父結點的子樹,替代 z 的位置;

  • 若結點 z 既有左子樹,又有右子樹,則用 z 的后繼(Successor)代替 z,然后從二叉查找樹中刪除這個后繼,這樣就轉換成了第一或第二種情況。

void BST_Delete(BSTree &T,Node* z)
{
	if(z->left == NULL && z->right == NULL)
	{
		if(z->parent != NULL)
		{
			if(z->parent->left == z)
				z->parent->left = NULL;
			else
				z->parent->right = NULL;
		}
		else
		{
			T = NULL;  // 只剩一個結點的情況
		}
		free(z);
	}
	else if(z->left != NULL && z->right == NULL)
	{
		z->left->parent = z->parent;
		if(z->parent != NULL)
		{
			if(z->parent->left == z)
				z->parent->left = z->left;
			else
				z->parent->right = z->left;
		}
		else
		{
			T = z->left;  // 刪除左斜單支樹的根結點
		}
		free(z);
	}
	else if(z->left == NULL && z->right != NULL)
	{
		z->right->parent = z->parent;
		if(z->parent != NULL)
		{
			if(z->parent->left == z)
				z->parent->left = z->right;
			else
				z->parent->right = z->right;
		}
		else
		{
			T = z->right;  // 刪除右斜單支樹的根結點
		}
		free(z);
	}
	else
	{
		Node* s = BST_Successor(z);
		z->key = s->key;   // s的關鍵字替換z的關鍵字
		BST_Delete(T, s);  // 轉換為第一或第二種情況
	}
}

  對於一個高度為 h 的二叉查找樹來說,刪除操作和插入操作一樣,都可以在 Θ(h) 時間內完成。

四、隨機構造的二叉查找樹

二叉查找樹可以實現任何一種基本的動態集合操作,且各基本操作的運行時間都是 Θ(h)。當樹的高度較低時,這些操作執行的較快;但是,當樹的高度較高時,性能會變差。比如,如果各元素是按嚴格增長的順序插入的,那么構造出來的樹就是一個高度為 n-1 的鏈。 為了盡量減少這種最壞情況的出現,我們可以隨機地構造二叉查找樹,即隨機地將各關鍵字插入一棵初始為空的樹來構造 BST。

//#include <cstdlib>
//#include <ctime>
/**
 * 隨機構造二叉查找樹
 */
void Create_BST(BSTree &T, int arr[], int n)
{
	T = NULL;  
	// 隨機遍歷數組,進行插入操作
	srand(time(NULL));
	for(int i=n-1; i>=0; --i)
	{
		int j = rand() % (i+1);
		BST_Insert(T, arr[j]);
		swap(arr[j], arr[i]);
	}
}

  


免責聲明!

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



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