二叉排序樹(又叫二叉搜索、查找樹) 是一種特殊的二叉樹,定義以下:數據結構
二分查找也稱爲折半查找,要求原線性表有序,它是一種效率很高的查找方法。若是在須要進行頻繁修改的表中採用二分查找,其效率也是很是低下的,由於順序表的修改操做效率低。若是隻考慮頻繁的修改,咱們能夠採用鏈表。然而,鏈表的查找效率又很是低。綜合這兩種數據結構的優點,二叉查找樹(Binary Sort/Search Tree)登場了。函數
給定一串數\((65,32,87,46,71,98,39)\),使用插入構造法,步驟以下:ui
typedef struct BTNode { char data; struct BTNode* Left; struct BTNode* Right; }*BTree; typedef BTNode BSTNode, *BSTree;
若是是普通二叉樹的查找操做,由於沒法肯定二叉樹的具體特性,所以只能對其左右子樹都進行遍歷。但二叉排序樹的特性,則會有一條肯定的路線。spa
//找不到返回NULL,找到返回該節點。 //非遞歸 BSTNode* BSTreeFind(BSTree t, int x) { if (!t)return NULL; if (t->data == x) return t; if (x < t->data) return BSTreeFind(t->Left, x); if (x > t->data) return BSTreeFind(t->Right, x); } //非遞歸 BSTNode* BSTFind(BSTree T,int x) { BSTree p = T; while (p) { if (x == p->data) return p; p = x > p->data ? p->Right : p->Left; } return NULL; }
對於一顆二叉排序樹來講,若是查找某個元素成功,說明該結點存在;若是查找失敗,則查找失敗的地方必定就是該結點應該插入的地方。指針
BSTree InsertBStree(BSTree BST, int x) { if (BST == NULL) { BST = new BSTNode; BST->data = x; return BST; } if (x < BST->data) BST->Left = InsertBStree(BST->Left, x); else BST->Right = InsertBStree(BST->Right, x); return BST; }
創建一顆二叉排序樹,就是前後插入\(n\)個結點的過程,與通常二叉樹的創建是徹底同樣的。code
BSTree BuildBSTree(int* a, int length) { BSTree BST = NULL; for (int i = 0; i < length; i++) BST = InsertBStree(BST, a[i]); return BST; }
二叉查找樹的刪除操做通常有兩種常見作法,複雜度都是\(O(h)\)或\(O(log(n))\),其中 \(h\) 爲樹的高度,\(n\) 爲結點個數。blog
以下圖的二叉排序樹,若是要刪除結點 \(5\),則有兩種辦法,一種辦法是以樹中比 \(5\) 小的最大結點(結點 \(4\) )覆蓋結點 \(5\) ,另外一種是用樹中比 \(5\) 大的最小結點(結點 \(6\) )覆蓋結點 \(5\)。排序
在二叉排序樹中把比該結點權值小的最大結點稱爲該結點的前驅,把比該結點權值大的最小結點稱爲該結點的後繼。結點的前驅是左子樹的最右結點,結點的後繼是右子樹的最左結點。用下面兩個函數用來尋找以root
爲根的樹中最大、最小權值的結點,後面會使用到。遞歸
//得到以root爲根結點的樹中的最大值結點 int GetMax(BSTree root) { while (root->Right != NULL) root = root->Right; return root->data; } //得到以root爲根結點的樹中的最小值 int GetMin(BSTree root) { while (root->Left != NULL) { root = root->Left; } return root->data; }
假設決定使用結點\(N\)的前驅 \(P\) 來替換 \(N\) ,因而就把問題轉換爲,先用 \(P\) 的值去覆蓋 \(N\) 的權值,再刪除結點\(P\),因而刪除操做的實現以下:(寫法1好理解,寫法2更簡潔)class
使用 BSTree root
,若是 root
爲要刪除的結點, \(P\) 爲父節點,刪除狀況有如下3種:
1. root
結點是葉子,將 \(P\) 節點的 left
或 right
指針域置爲NULL
。
2. root
結點只有左子樹,將 root
節點的 left
從新到結點 \(P\) 上。和刪除單鏈表節點相似。(只有右子樹時狀況類似。第一種狀況,寫的時候能夠和第二種合併起來)
3. root
結點既有左子樹,也有右子樹,尋找結點的前驅 pre
(或者後繼),用 pre
的值去覆蓋 root
的權值,再刪除結點 pre
,而這個 pre
的刪除必定屬於狀況 1 或者 2 。
這個寫法更適合JAVA,代碼裏的那些 delete
還能夠省去,可是必需要注意的是:函數的返回值不是多餘的,刪除只有一個(或沒有)子樹的根節點的時候,必須用到這個返回值。
實現方法1:
//刪除結點左子樹的最大值結點 BSTree DelMax(BSTree root) { if (root->Right == NULL) { BSTNode *t= root->Left; delete root; return t; } root->Right = DelMax(root->Right); return root; } BSTree BSTDel(BSTree root,int x) { if (root == NULL) return NULL; if (root->data == x) { //狀況1和2,被刪除的結點只有左子樹或右子樹,或沒有子樹 if (! root->Right || !root->Left) { BSTNode *t = !root->Right ? root->Left : root->Right; delete root; return t; } //刪除既有左子樹,也有右子樹的結點 else{ root->data = GetMax(root->Left); root->Left = DelMax(root->Left); } } else if (x > root->data) root->Right = BSTDel(root->Right, x); else root->Left = BSTDel(root->Left, x); return root; }
實現方法2:
//刪除子樹中的最大值 void DelMax(BSTree &root,int &value) { if (!root)return; if (root->Right == NULL) { BSTNode *t= root; value = root->data; root = root->Left; delete t; } else DelMax(root->Right, value); } void BSTDel(BSTree &root,int x) { if (root == NULL) return; BSTree p = root; if (p->data == x) { if (! p->Right || !p->Left) { root = !p->Right ? p->Left : p->Right; delete p; } else //由於使用的方法是值替換,並無把原來的root覆蓋掉,因此delete p不能像和下面方法2同樣寫在整個大if裏 DelMax(root->Left, root->data); } else if (x > p->data) BSTDel(p->Right, x); else BSTDel(p->Left, x); }
使用 BSTree *root
,和上面的BSTree &root
是等價的,若是 root
爲要刪除的結點,刪除狀況也是如下3種:
1. 當前 root
結點是葉子,將 root
置空。
2. root
結點只有左子樹,將 root
置爲 root->Right
(只有右子樹時狀況類似。第一種狀況,寫的時候能夠和第二種合併起來)
3. 被刪除的結點既有左子樹,也有右子樹,尋找結點的前驅 pre
(或者後繼),用 root
的指針域覆蓋 pre
的指針域,再把結點 root
刪除。(形象化來講就是刪除 root
,把 pre
移動到 root
的位置)
void BSTDel(BSTree *root, int x){ if (!*root) return; BSTree p = *root; if (p->data == x) { if (!p->Right || !p->Left) *root = !p->Right ? p->Left : p->Right; else{ BSTNode *parent = p->Left, *q = p->Left; if (!q->Right) q->Right = p->Right; else { while (q->Right) { parent = q; q = q->Right; } parent->Right = q->Left; q->Left = p->Left; q->Right = p->Right; } *root = q; } delete p; } else if (x > p->data) //向右找 BSTDel(&(p->Right), x); else if (x < p->data) //向左找 BSTDel(&(p->Left), x); }
二叉排序樹最基本的性質:二叉排序樹的中序序列是有序的。
若是合理調整二叉排序樹的形態,使得樹上的每一個結點都儘可能有兩個子結點,這樣整個二叉樹的高度就會大約在\(log(n)\) 左右,其中 \(n\) 爲結點個數。實現這個要求的一種樹就是下一篇平衡二叉樹(AVL Tree)。