二叉排序樹(BST,Binary Sort Tree)具有這樣的性質:對於二叉樹中的任意節點,如果它有左子樹或右子樹,則該節點的資料成員大於左子樹所有節點的資料成員,且小於右子樹所有節點的資料成員。排序二叉樹的中序遍曆結果是從小到大排列的。
二叉排序樹的尋找和插入比較好理解,主要來看一下刪除時的情況。
如果需要尋找並刪除8-6-8中的37, 51, 73,93這些在二叉排序樹中是葉子的結點,那是很容易的,畢竟刪除它們對整棵樹來說,其他結點的結構並未受到影響。
對於要刪除的結點只有左子樹或只有右子樹的情況,相對也比較好解決。那就是結點刪除後,將它的左子樹或右子樹整個移動到刪除結點的位置即可,可以理解為獨子繼承父業。比8-6-9,就是先刪除35和99兩結點,再刪除58結點的變化圖,最終,整個結構還是一個二叉排序樹。
但是對於要刪除的結點既有左子樹又有右子樹的情況怎麼辦呢?比8-6-10中的47結點若要刪除了,它的兩兒子和子孫們怎麼辦呢?
前人總結的比較好的方法就是,找到需要刪除的結點p的直接前驅(或直接後繼)s,用s來替換結點p,然後再刪除此結點s,8-6-12所示。
下面來看代碼:(參考《linux c 編程一站式學習》
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
|
/************************************************************************* > File Name: binarysearchtree.h > Author: Simba > Mail: dameng34@163.com > Created Time: Sat 29 Dec 2012 06:05:55 PM CST ************************************************************************/#ifndef BST_H #define BST_H typedef struct node *link; struct node { unsigned char item; link left, right; }; link search(link t, unsigned char key); link insert(link t, unsigned char key); link delete(link t, unsigned char key); void print_tree(link t); #endif |
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
|
|
/************************************************************************* > File Name: binarysearchtree.c > Author: Simba > Mail: dameng34@163.com > Created Time: Sat 29 Dec 2012 06:08:08 PM CST ************************************************************************/#include<stdio.h> #include<stdlib.h> #include "binarysearchtree.h" static link make_node(unsigned char item) { link p = malloc(sizeof(*p)); p->item = item; p->left = p->right = NULL; return p; } static void free_node(link p) { free(p); } link search(link t, unsigned char key) { if (!t) return NULL; if (t->item > key) return search(t->left, key); if (t->item < key) return search(t->right, key); /* if (t->item == key) */ return t; } link insert(link t, unsigned char key) { if (!t) return make_node(key); if (t->item > key) /* insert to left subtree */ t->left = insert(t->left, key); else /* if (t->item <= key), insert to right subtree */ t->right = insert(t->right, key); return t; } link delete(link t, unsigned char key) { link p; if (!t) return NULL; if (t->item > key) /* delete from left subtree */ t->left = delete(t->left, key); else if (t->item < key) /* delete from right subtree */ t->right = delete(t->right, key); else /* if (t->item == key) */ { if (t->left == NULL && t->right == NULL) { /* 如果此時(遞迴)找到的節點已經是沒有子樹的樹葉則直接釋放空間 共置t為NULL並由return返回,(即將上一層指向此節點的指標置為NULL),防止野指標 */ /* if t is a leaf node */ free_node(t); t = NULL; } else if (t->left) /* if t has left subtree */ { /* replace t with the rightmost node in left subtree */ /* 如果想要刪除的節點有左子樹,則找到左子樹下邊最靠右的節點(已無右子樹) */ for (p = t->left; p->right; p = p->right); t->item = p->item; /* 將左子樹下最靠右的節點值賦予想要刪除的節點 */ t->left = delete(t->left, t->item); /* 以左子樹的根指標作為根節點,遞迴找到最靠右節點 若此時節點已無子樹則滿足地一個if條件,直接釋放空間 若還有左子樹,則按相同原則遞迴進行換值後刪除下一個最靠右節點 */ } /* 當找到要刪除的節點時,優先考慮將左子樹下最靠右節點的值換上來 * 如果沒有左子樹,則將右子樹下最靠左節點的值換上來 */ else /* if t has right subtree */ { /* replace t with the leftmost node in right subtree */ for (p = t->right; p->left; p = p->left); t->item = p->item; t->right = delete(t->right, t->item); } } return t; } void print_tree(link t) { if (t) { printf("("); printf("%d", t->item); print_tree(t->left); print_tree(t->right); printf(")"); } else printf("()"); } |
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
|
|
/************************************************************************* > File Name: main2.c > Author: Simba > Mail: dameng34@163.com > Created Time: Sat 29 Dec 2012 06:22:57 PM CST ************************************************************************/#include<stdio.h> #include<stdlib.h> #include<time.h> #include "binarysearchtree.h" #define RANGE 100 #define N 6 void print_item(link p) { printf("%d", p->item); } int main(void) { int i, key; link root = NULL; srand(time(NULL)); for (i = 0; i < N; i++) { root = insert(root, rand() % RANGE); /* 第一次迴圈root從NULL變成根節點值,接下去 的迴圈雖然迭代root,但在插入節點過程中root的值始終不變 */ printf("root = 0x%x\n", (unsigned int)root); } printf("\t\\tree"); print_tree(root); printf("\n\n"); while (root) { key = rand() % RANGE; if (search(root, key)) { printf("delete %d in tree\n", key); root = delete(root, key); /* root雖然迭代,但返回的仍是先前的值,即根節點的值保持不變 直到全部節點被刪除,root變成NULL即0x0 */ printf("root = 0x%x\n", (unsigned int)root); printf("\t\\tree"); print_tree(root); /* 傳遞給函數的一直是根節點的值,直到樹清空,root變成NULL */ printf("\n\n"); } } return 0; } |
輸出為:
如果我們使用了The Tree Preprocessor,可以將以括弧展示的排序二叉樹轉換成樹形展示,如
以前此工具可以在 http://www.essex.ac.uk/linguistics/clmt/latex4ling/trees/tree/ 下載,現已找不到連結,我將其上傳到csdn,需要的可以去下載。
http://download.csdn.net/detail/simba888888/5334093
最後提一下,我們希望構建出來的二叉排序樹是比較平衡的,即其深度與完全二叉樹相同,那麼尋找的時間複雜度研究度O(logn),近似於折半尋找,
但如果出現構造的樹嚴重不平衡,如完全是左斜樹或者右斜樹,那麼尋找時間複雜度為O(n),近似於順序尋找。那如何讓二叉排序樹平衡呢?
事實上還有一種平衡二叉樹(AVL樹),也是一種二叉排序樹,其中每個結點的左子樹和右子樹的高度差至多等於1。