一、什么是二叉查找樹
二叉查找樹(Binary Search Tree)是一種特殊的二叉樹,對於一個二叉查找樹,樹中的每個結點X,它的左子樹中所有關鍵字的值都小於X的關鍵字值;而它的右子樹中所有關鍵字的值大於X的關鍵字值。這意味着,該樹的所有元素可以使用一種統一的方式進行排序,因此,二叉查找樹又稱為二叉排序樹。下圖即為一個二叉查找樹:
二、如何在 BST 中查找一個結點
二叉查找樹很適合進行查找操作。以上圖中的二叉查找樹為例:如果需要在該 BST 中查找值 10,先從根結點開始進行比較,10小於13,根據 BST 的性質,可以知道如果該 BST 中有結點值為10的話,那么該結點必然位於根結點的左子樹中;再從結點 5 開始,可知 10 在結點 5 的右子樹;再從結點 8 開始,10 在結點 8 的右子樹,最終找到元素 10。
用算法來描述,在一個 二叉查找樹 T(T 為二叉樹的根結點)中查找元素 n:
1)若 T 為空,則 n 不在 BST 中;
2)判斷 n 與根結點值(T->val)的關系;
3)如果 n 等於 T->val,則找到了元素 n;
4)如果 n 大於 T->val,則去根結點的右子樹 (T->right) 繼續查找。將 T->right 作為 T,回到第 1 步;
5)如果 n 小於 T->val,則去根結點的左子樹 (T->left ) 繼續查找。將 T->left 作為 T,回到第1步。
算法實現如下:
typedef int ElementType; typedef struct BinaryTreeNode_ { ElementType val; struct BinaryTreeNode_ *left; struct BinaryTreeNode_ *right; }BinaryTreeNode; typedef BinaryTreeNode *BiTreeNode; // 查找一個結點 BiTreeNode FindNode(BiTreeNode BiTree,ElementType n) { if (BiTree == NULL) // 如果頭結點為空,表明沒有找到元素 n return NULL; if (BiTree->val < n) // 結點值小於查找元素值,說明元素在結點的右邊 return FindNode(BiTree->right, n); // 遞歸,去右子樹繼續查找 else if (BiTree->val > n) return FindNode(BiTree->left, n); // 遞歸,去左子樹繼續查找 else return BiTree; }
該查找算法的時間復雜度為 O(log2N)。
三、向 BST 中插入一個結點
向 BST 中插入一個結點 n 時,若 BST 中已經存在與該結點值相等的結點,則判斷結點已存在,不做任何操作;如果 BST 中不存在該結點,則將該結點將作為一個新的葉子結點插入到 BST 中去(新結點總是 BST 的葉子結點),且需要保證插入后的樹是一個二叉查找樹。
可以以類似查找算法的方式來遍歷二叉樹,從而找到插入的位置:
用算法來描述,向一個二叉查找樹 T(T 為二叉樹的根結點)中插入一個結點,節點元素為 n:
1)若 T 為空,則將新結點插入到 T 所在的位置;
2)若 T 不為空,且 T->val 小於 n,則表明應該把 n 插入到 T 的右子樹。將 T->right 作為 T,回到第 1 步;
3)若 T 不為空,且 T->val 大於 n,則表明應該把 n 插入到 T 的左子樹。將 T->left 作為 T,回到第 1 步。
4)若 T->val 等於 n,則直接返回 T。
// 插入一個結點 BiTreeNode InsertNode(BiTreeNode BiTree, ElementType n) { if (BiTree == NULL) { // 樹為空,或已到達葉子結點 // 為新結點分配內存 BiTreeNode newNode = (BiTreeNode)malloc(sizeof(BinaryTreeNode)); if (newNode == NULL) { perror("malloc failed!\n"); exit(EXIT_FAILURE); } newNode->val = n; newNode->left = NULL; newNode->right = NULL; BiTree = newNode; } else if (BiTree->val < n) { // 插入值大於當前結點的值,則將元素插入到結點的右子樹 BiTree->right = InsertNode(BiTree->right, n); } else if (BiTree->val > n) { // 插入值小於當前結點的值,則將元素插入到結點的左子樹 BiTree->left = InsertNode(BiTree->left, n); } return BiTree; }
四、遍歷 BST
在上一篇博客中已經介紹了二叉樹的遍歷方法,主要有三種:先序遍歷、后序遍歷和中序遍歷。為了更好的體現二叉查找樹的特性,我們使用中序遍歷來遍歷它:
// 中序遍歷 BST void TraverseTree(BiTreeNode BiTree) { if (BiTree != NULL) { TraverseTree(BiTree->left); cout << BiTree->val << endl; TraverseTree(BiTree->right); } }
五、刪除一個結點
刪除操作比較復雜,分為三種情況:
1)被刪除結點為葉子結點:
可以直接刪除該結點,如圖:
2)被刪除結點有一個左孩子或一個右孩子:
將孩子結點設為該結點的父結點的孩子后,即可刪除該結點:
如圖,結點 2 是結點 5 的左孩子,他有一個右孩子 4。刪除結點 2 后,其右孩子 4 替代原來的結點 2 成為結點 5 的左孩子。
如圖,結點 16 是結點 18 的左孩子,他有一個左孩子 15。刪除結點 16 后,其左孩子 15 替代原來的結點 16 成為結點 18 的左孩子。
3)被刪除結點有兩個孩子結點:
這種情況比較復雜,一般的刪除策略是:用被刪除結點的右子樹中的最小結點替代被刪除結點,並遞歸地刪除這個最小數據結點:
// 刪除一個結點 BiTreeNode DeleteNode(BiTreeNode BiTree, ElementType n) { BiTreeNode tmpNode = (BiTreeNode)malloc(sizeof(BinaryTreeNode)); if (tmpNode == NULL) { perror("malloc failed!\n"); exit(EXIT_FAILURE); } // 先找到要刪除的結點在二叉樹中的位置 if (BiTree == NULL) { // 沒有找到元素 perror("can`t find element\n"); return NULL; } if (BiTree->val < n) { // 元素值大於結點元素值,則去結點右子樹尋找 BiTree->right = DeleteNode(BiTree->right, n); return BiTree; } else if (BiTree->val > n) { // 元素值小於結點元素值,則去結點左子樹尋找 BiTree->left = DeleteNode(BiTree->left, n); return BiTree; } // 找到結點 else { tmpNode = BiTree; if (BiTree->right == NULL) { // 沒有右子樹,則直接返回左子樹 BiTree = BiTree->left; return BiTree; } else if (BiTree->left == NULL) { // 沒有左子樹,則直接返回右子樹 BiTree = BiTree->right; return BiTree; } // 有兩個孩子結點 tmpNode = FindMin(BiTree->right); // 尋找右子樹最小結點 tmpNode->right = DeleteMin(BiTree->right); // 刪除右子樹的最小結點 tmpNode->left = BiTree->left; // 左子樹保持不變 return tmpNode; } } // 查找最左葉子結點(最小的結點) BiTreeNode FindMin(BiTreeNode BiTree) { if (BiTree == NULL) return NULL; if (BiTree->left == NULL) // 結點沒有左子樹,即為最左葉子結點 return BiTree; else // 有左子樹,則繼續在左子樹中查找 return FindMin(BiTree->left); } // 刪除最小結點 BiTreeNode DeleteMin(BiTreeNode BiTree) { if (BiTree->left == NULL) return BiTree->right; else { BiTree->left = DeleteMin(BiTree->left); return BiTree; } }
參考資料:
《數據結構與算法分析 C 語言描述》