二叉查詢樹是一種樹資料結構,它與普通的二叉樹最大的不同就是二叉查詢樹滿足乙個性質:對於樹中的任意乙個節點,均有其左子樹中的所有節點的關鍵字值都不大於該節點的關鍵字值,其右子樹中的任意乙個節點的關鍵字值都不小於該節點的關鍵字值。
在二叉查詢樹上可以進行搜尋、取最小值、取最大值、取指定節點的前驅、取指定節點的後繼以及插入和刪除節點操作,因此二叉查詢樹和堆(大頂堆和小頂堆)一樣,也可以做優先佇列,都能夠在 o(lgn) 的時間內取得集合的最大值和最小值。乙個二叉查詢樹的期望高度為o(lgn),因此在二叉查詢樹上的基本操作都能在期望時間o(lgn)下實現,但是最壞情況下時間為o(n),也就是二叉排序樹極度不平衡,變成一條鏈。對二叉查詢樹的前序、中序和後序遍歷均是在 o(n) 的時間複雜度內實現。根據二叉查詢樹的性質,其中序遍歷的結果就是樹元素的按非遞減序輸出,因此研究二叉排序樹的中序遍歷及遍歷中元素的前驅和後繼是非常重要的。
使用二叉查詢樹也有乙個問題,隨著插入和刪除操作的不斷執行,樹的高度就會發生變化,不一定為o(lgn),這也是它的乙個缺陷,因此在做優先佇列時,雖然堆(看做完全二叉樹)和查詢樹都是利用二叉樹的結構來實現的,但是用堆較查詢樹多。
下面的**是包括了二叉樹的前序、中序和後序遍歷的遞迴和非遞迴演算法,以及二叉查詢樹的相關操作。
/** 演算法導論 第十二章 二叉查詢樹
*/#include #include using namespace std;
//二叉樹節點定義
typedef struct tnode
tnode, tree;
/* * 前序遍歷
* 遞迴
*/void preordertreewalkrecursion(tree* tree)}/*
* 前序遍歷二叉樹
* 非遞迴
*/void preordertreewalknorecursion(tree* tree)
stacktreestack;
treestack.push(tree);
while (! treestack.empty())
treestack.pop();
if (! treestack.empty())
}}/*
* 中序遍歷
* 遞迴
*/void inordertreewalkrecursion(tree* tree)}/*
* 中序遍歷
* 非遞迴 用棧
*/void inordertreewalknorecursionstack(tree* tree)
treestack.pop();
if (! treestack.empty())
}}/*
* 中序遍歷
* 非遞迴 無棧
*/void inordertreewalknorecursionnostack(tree* tree)
if (! pleft->right)
else
} //在向下階段直到沒有左孩子,在向上回溯階段表示左子樹已經遍歷完
//此時應該遍歷此節點,然後遍歷其右孩子
couttree = tree->right; }}
/* * 後序遍歷
* 遞迴
*/void postordertreewalkrecursion(tree* tree)}/*
* 後序遍歷
* 非遞迴
*/void postordertreewalknorecursion(tree* tree)
treestack.pop();
if (! treestack.empty())
else
} }}/*
* 二叉排序樹的查詢
* 遞迴
* 時間複雜度為樹的高度o(lgn)
*/tnode* treesearch(tree* tree, int k)
if (k < tree->key)
else }/*
* 二叉排序樹查詢
* 非遞迴
* 時間複雜度為o(lgn)
*/tnode* treesearchiterative(tree* tree, int k)
else
} return tree;}/*
* 查詢二叉排序樹中的最小元素
* 即為最左元素
* 時間複雜度為o(lgn)
*/tnode* treeminimum(tree* tree)
return tree;}/*
* 查詢二叉查詢樹中的最大元素
* 即為最右元素
* 時間複雜度為o(lgn)
*/tnode* treemaximum(tree* tree)
return tree;}/*
* 求二叉排序樹中指定節點node的中序遍歷後繼
* 如果node右子樹不為空,則後繼則為node右子樹中的最小節點
* 否則node右子樹為空,必須向上回溯找到第乙個節點:該節點為其父節點的左孩子
* 後繼即為其父節點,如果不存在這樣的節點,說明node為最右節點,後繼為空
* 時間複雜度為o(lgn)
*/tnode* treesuccessor(tnode* node)
tnode* temp = node->parent;
while (temp && node == temp->right)
return temp;}/*
* 求二叉排序樹中指定節點node的中序遍歷前驅
* 如果node左子樹不為空,則前驅則為node左子樹中的最大節點
* 否則node左子樹為空,必須向上回溯找到第乙個節點:該節點為其父節點的右孩子
* 前驅即為其父節點,如果不存在這樣的節點,說明node為最左節點,前驅為空
* 時間複雜度為o(lgn)
*/tnode* treepredecessor(tnode* node)
tnode* temp = node->parent;
while (temp && node == temp->left)
return temp;}/*
* 二叉查詢樹插入元素
* 新元素只能插入到二叉樹的葉子節點上
* 首先需要找到這個插入點
* 時間複雜度主要在搜尋插入位置上,為o(lgn)
*/void treeinsert(tree* &tree, tnode* node)
tnode* pos = null;
tnode* temp = tree;
while (temp)
else }
node->parent = pos;
if (! pos)
else if (node->key < pos->key) else }/*
* 二叉查詢樹刪除元素
* 刪除二叉查詢樹中的乙個元素必須繼續保持二叉查詢樹的性質,也就是保證中序遍歷的結果不變
* 根據被刪除節點分三種情況
* 1、刪除節點沒有孩子,這種情況下直接刪除該節點即可
* 2、刪除節點有乙個孩子,這種情況先將節點刪除,再將該節點的孩子填補上來即可
* 3、刪除節點有兩個孩子,這種情況相對麻煩一點,需要先找到刪除節點的後繼節點
* 然後將該刪除節點的資料值修改為後繼結點的值,然後將後繼節點刪除,由於後繼節點不可能有左孩子,否則刪除節點
* 的後繼就應該是後繼節點的左孩子,所以後繼節點的刪除很簡單
* 時間主要用在查詢刪除節點的後繼節點上,所以需要o(lgn)
*/tnode* treedelete(tree* &tree, tnode* node)
else
//找到替補節點
tnode* fillnode = null;//填補(到被刪除的位置)節點
if (delnode->left)
else
//將替補節點接到刪除節點的位置
//可能delnode沒有孩子
if (fillnode)
//delnode可能是根節點
if (! delnode->parent)
else if (delnode == delnode->parent->left) else
//如果實際刪除的是node的後繼節點,則複製資料
if (delnode != node)
return delnode;
}int main()
; int len = sizeof(arr) / sizeof(arr[0]);
tree* tree = null;
for (int i=0; ikey = arr[i];
node->left = node->parent = node->right = null;
treeinsert(tree, node);
} cout<<"前序遞迴遍歷二叉排序樹:"<<",中序後繼是:"<<(treesuccessor(pnode))->keypnode->left = pnode->parent = pnode->right = null;
treeinsert(tree, pnode);
cout<<"插入節點"<
《演算法導論》筆記 第12章 12 1 二叉查詢樹
二叉查詢樹性質 設x為二叉查詢樹中的乙個節點。如果y是x的左子樹中的乙個結點,則key x key y 如果y是x的右子樹中的乙個結點,則key x key y 如果x是一棵包含n個結點的子樹的根,則呼叫inorder tree walk x 過程的時間為 n void inodertreewalk...
演算法導論第12章 二叉搜尋樹
左子樹 根 右子樹 根據二叉樹的基本性質,向左子樹或右子樹遞迴即可 查詢結點x的後繼y分為兩種情況 右結點存在,即只需要找到右子樹中最小的元素就好了 右結點不存在,此時就要向親代查詢,直到找到右上方的親代,此時y是x的最底層祖先,且y的左孩子是x的乙個祖先。迭代演算法 類似於一根小棒在樹中移動,最終...
《演算法導論》第12章 二叉查詢樹 3 基數樹
基數樹與二叉查詢樹和trie樹很相似。它像bst一樣是二叉的,向左表示0而不是bst的小於,而向右則表示1而不是大於。它像trie一樣共享相同的結點來儲存字串中相同的字首,從而 節省了空間,但它不像trie那樣每個結點有很多孩子 可以是26個,表示a到z 它用來處理 只包含0和1的字串。基數樹和tr...