什么是查找?
就是给定一个特殊的元素和一个集合,确定在这个集合中是否包含这个元素,并找出该元素对应位置的过程称之为“查找”。
查找的分类
静态查找:在查找的过程中集合不发生变化
动态查找:在查找的过程中向这个集合中频繁地添加、删除元素。
什么是二叉树?
如图所示:每个节点最多只有两个分支,这就是我们所说的“二叉”,由于形状看起来像一颗倒放的树,所以称之为“二叉树”。
圆圈代表结点,而的顶层节点也叫根结点。一个结点的左边线所连接的结点叫“左孩子”,同理,右边的叫右孩子,那这个结点也就叫父亲结点。
下面说明几个概念:
- 结点所包含的链接可以指向空(NULL)或其他结点
- 二叉树中,只能有一个父亲结点指向自己(根结点除外)
- 每个结点最多只能有左右两个链接,分别指向自己的左孩子和右孩子
什么是二叉查找树?
如图所示:S、E、A、C等等,这些都叫做键,我们就用键来指代这个结点。而图上R旁边的9就是这个结点的值。
下面说一下二叉查找树满足的特性: - 若根结点的左子树非空,则左子树所有结点的值小于根结点
- 若根结点的右子树非空,则右子树所有结点的值大于根结点
- 每一颗二叉查找树的左右子树也分别是一颗二叉查找树
在二叉查找树中查找一个键的递归算法思想如下:
- 如果树为空,则查找未命中
- 如果被查找的键和根结点的键相等,则查找命中
- 如果树不为空而且查找未命中,我们就递归地在适当的子树中继续查找(如果被查找的键较小就选择左子树,反之选择右子树)
基本数据结构
// define data type for the key
typedef int keyType;
// define binary search tree data structure
struct BinaryTreeNode {
keyType key;
BinaryTreeNode* left; // left child
BinaryTreeNode* right; // right child
};
// alias for the tree
typedef BinaryTreeNode bstree;
// alias for the tree node
typedef BinaryTreeNode bstnode;
从二叉查找树中查找key
// search BST by the given key
bstnode* search_by_key(bstree* tree, keyType key) {
bstnode* node = tree;
int found = 0;
while (NULL != node) {
if (key == node->key) {
found = 1;
break;
}
node = (key > node->key) ? node->right : node->left;
}
return found ? node : NULL;
}
向二叉查找树中插入key
// insert a key to BST
int insert_key(bstree* &tree, keyType key) {
if (NULL == tree) {
tree = (bstnode*) malloc(sizeof(bstnode));
tree->key = key;
return 1;
}
int found = 0;
bstnode* curr = tree;
bstnode* prev = NULL;
while (NULL != curr) {
// if already exists
if (key == curr->key) {
found = 1;
break;
}
prev = curr;
curr = (key > curr->key) ? curr->right : curr->left;
}
if (!found && NULL == curr) {
curr = (bstnode*) malloc(sizeof(bstnode));
curr->key = key;
((key > prev->key) ? prev->right : prev->left) = curr;
return 1;
}
return 0;
}
从二叉查找树中删除key
// delete a key from the BST
int delete_key(bstree* &tree, keyType key) {
// 设定临时树根,其key假设为无穷大,防止出现删除root的情况
// 主要作用是让原来树的root可以像其他节点一样参与运算
bstnode* head = (bstnode*) malloc(sizeof(bstnode));
head->left = tree;
bstnode *curr = tree, *prev = head;
bstnode *t1 = NULL, *t2 = NULL;
int found = 0;
while (NULL != curr) {
if (key == curr->key) {
found = 1;
break;
}
prev = curr;
curr = ((key > curr->key) ? curr->right : curr->left);
}
if (found) {
// delete the node with the given key
if (NULL == curr->left) { // when the left child of the node is NULL
((curr == prev->left) ? prev->left : prev->right) = curr->right;
free(curr);
} else if (NULL == curr->right) { // when the right child of the node is NULL
((curr == prev->left) ? prev->left : prev->right) = curr->left;
free(curr);
} else { // when the node has two children
// 按照二叉查找树的特性,保持中序遍历顺序即可
// 即用该节点的中序前序后者后继替代该节点即可
t1 = curr->left;
while (NULL != t1->right) {
t2 = t1;
t1 = t1->right;
}
curr->key = t1->key;
((NULL == t2) ? curr->left : t2->right) = t1->left;
free(t1);
}
}
// 还原树根
tree = head->left;
free(head);
return found;
}
删除操作需要注意:
- 若删除的元素是树根,则需要确保删除之后还能构建成新的树;
- 按照查找二叉树的特性,按照中序遍历则必定有序,所以在删除某个节点之后可以寻找其中序前去或者后继来替代该节点。
主函数
int main() {
cout << "插入构建二叉查找树:12 15 5 3 12 34" << endl;
bstree * bst = NULL;
insert_key(bst, 12);
insert_key(bst, 15);
insert_key(bst, 5);
insert_key(bst, 3);
insert_key(bst, 12);
insert_key(bst, 34);
cout << endl;
cout << "遍历二叉查找树 :" << endl << "前序\t : ";
rpre_order(bst);
cout << endl << "中序\t : ";
rin_order(bst);
cout << endl;
cout << endl;
cout << "删除元素 12 : ";
int r = delete_key(bst, 12);
if (r) {
cout << "Success";
} else {
cout << "Failed";
}
cout << endl;
cout << "删除元素 55 : ";
r = delete_key(bst, 55);
if (r) {
cout << "Success";
} else {
cout << "Failed";
}
cout << endl;
cout << endl;
cout << "刪除之后 :" << endl << "前序\t : ";
rpre_order(bst);
cout << endl << "中序\t : ";
rin_order(bst);
cout << endl;
//
destory_btree(bst);
return 0;
}
总结
总的来说,二叉查找树是一种将链表插入的灵活性和有序数组查找的高效性结合起来的符号表实现。同时,我们使用二叉查找树算法时,其运行时间取决于树的形状,而树的形状有取决于键被插入的先后顺序,所以我们列出以下三种情况:
有图可知,在有N个结点的二叉查找树中,最好的情况就是完全平衡,所以查找命中平均所需的比较次数为2lnN。
参考链接:https://blog.youkuaiyun.com/whucyl/article/details/17100225