赞
踩
目录
本文将深入探讨二叉搜索树这一重要的数据结构。二叉搜索树不仅是一个功能强大的数据结构,而且在实际应用中展现出了极高的实用性。它以其独特的组织方式,使得查找、插入和删除操作都能在平均对数到线性时间内完成,从而大大提高了数据处理的效率。为了更好地理解二叉搜索树的工作原理,我们使用C++语言实现了一个简单的二叉搜索树。
二叉搜索树(Binary Search Tree),也称二叉排序树,是一种特殊的二叉树。二叉搜索树可以为空树。如果不为空树,有以下的性质:
先自定义一个二叉树结点的类,该类将使用模版。一般来说,有两种类型的二叉搜索树。
下面的代码是两种模型自定义类的实现,把下面的代码放到BSTree.h文件下。分别封装到key和keyValue的命名空间中。我们先实现K模型的二叉树成员函数。再如法炮制实现第二种模型的成员函数。
- #pragma once
- #include <iostream>
- using namespace std;
-
- namespace key
- {
- template<class K>
- struct BSTNode
- {
- BSTNode(const K& key = K())
- :_key(key)
- , _left(nullptr)
- , _right(nullptr)
- {}
-
- K _key;
- BSTNode<K>* _left;
- BSTNode<K>* _right;
- };
-
- template<class K>
- class BSTree
- {
- typedef BSTNode<K> Node;
- //...
- private:
- Node* root;
- }
- }
-
- namespace keyValue
- {
- template<class K, class V>
- struct BSTNode
- {
- BSTNode(const K& key = K(), const V& value = V())
- :_key(key)
- ,_value(value)
- , _left(nullptr)
- , _right(nullptr)
- {}
-
- K _key;
- V _value;
- BSTNode<K, V>* _left;
- BSTNode<K, V>* _right;
- };
-
- template<class K, class V>
- class BSTree
- {
- typedef BSTNode<K, V> Node;
- //...
- private:
- Node* root;
- }
- }
二叉搜索树的查找操作比较简单。
- bool Find(const K& key)
- {
- Node* cur = _root;
- //如果为空,说明这个值不存在
- while (cur)
- {
- //比较关键值大小
- if (cur->_key < key)
- {
- cur = cur->right;
- }
- else if (cur->_key > key)
- {
- cur = cur->left;
- }
- else
- {
- return true;
- }
- }
-
- return false;
- }
int arr[] = {11, 7, 18, 9, 14, 4, 23, 8, 16, 10};
二叉树的插入操作其实步骤根查找类似,插入具体过程如下:
- bool Insert(const K& key)
- {
- if (_root == nullptr)
- {
- _root = new Node(key);
- }
-
- Node* parent = nullptr;
- Node* cur = _root;
- while (cur)
- {
- if (cur->_key < key)
- {
- parent = cur;
- cur = cur->_right;
- }
- else if (cur->_key > key)
- {
- parent = cur;
- cur = cur->_left;
- }
- else
- {
- return false;
- }
- }
-
- cur = new Node(key);
- if (parent->_key < key)
- {
- parent->_right = cur;
- }
- else
- {
- parent->_left = cur;
- }
- return true;
- }
二叉搜索树可以通过中序遍历得到有序的数据。在类中,定义一个中序遍历的子函数,再传入这棵树的根,进行遍历打印即可。
- class BSTree
- {
- //...
- public:
- //...
-
- void InOrder()
- {
- _InOrder(_root);
- cout << endl;
- }
-
- private:
- void _InOrder(Node* root)
- {
- if (root == nullptr)
- return;
-
- _InOrder(root->_left);
- cout << root->_key << " ";
- _InOrder(root->_right);
- }
-
- private:
- Node* _root = nullptr;
- };
我们写一个测试函数,测试一下插入函数。
- #include "BSTree.h"
-
- void Test1()
- {
- int arr[] = { 11, 7, 18, 9, 14, 4, 23, 8, 16, 10 };
- key::BSTree<int> tree;
-
- for (auto& e : arr)
- {
- tree.Insert(e);
- }
- tree.InOrder();
-
- tree.Insert(2);
- tree.Insert(13);
- tree.InOrder();
- }
运行结果如下:
二叉树的删除操作就有些麻烦。需要分几种情况:
待删除结点没有孩子结点,可以直接释放该节点,使其父亲结点指向空即可。
待删除结点只有一个孩子结点。如下图,14结点有一个右孩子,左孩子为空。先释放14结点,再将其父亲结点的左指针指向16结点即可。如果待删除结点有一个左孩子,操作也是类似。
不过这个有极端情况,如下面的第二张图,如果要删除的是根节点,并且根节点只有左孩子或者右孩子。此时,因为根结点没有父亲结点,所以直接释放根结点,让root指针指向他的孩子结点。
待删除结点有两个孩子结点的情况,就比较麻烦。如下图,思路是找到可以替换的结点,待删除结点的key值替换成该节点的key值,然后再释放这个替换结点,处理结点之间的连接关系。
- bool Erase(const K& key)
- {
- Node* parent = nullptr;
- Node* cur = _root;
- while (cur)
- { //查找待删除结点
- if (cur->_key < key)
- {
- parent = cur;
- cur = cur->_right;
- }
- else if (cur->_key > key)
- {
- parent = cur;
- cur = cur->_left;
- }
- else//删除操作
- {
- // 0-1孩子
- if (cur->_left == nullptr)
- {
- if (parent == nullptr)//删除根节点的情况
- {
- _root = cur->_right;
- }
- else
- {
- if (parent->_left == cur)
- parent->_left = cur->_right;
- else
- parent->_right = cur->_right;
- }
- delete cur;
- return true;
- }
- else if (cur->_right == nullptr)
- {
- if (parent == nullptr)//删除根节点的情况
- {
- _root = cur->_left;
- }
- else
- {
- if (parent->_left == cur)
- parent->_left = cur->_left;
- else
- parent->_right = cur->_left;
- }
- delete cur;
- return true;
- }
- else
- {
- //两个孩子的情况
- // 右子树的最小节点作为替代节点
- Node* rightMinP = cur;//不可为空,特殊情况会访问空指针
- Node* rightMin = cur->_right;
-
- while (rightMin->_left)
- {
- rightMinP = rightMin;
- rightMin = rightMin->_left;
- }
-
- cur->_key = rightMin->_key;
-
- //需要判断右子树最小节点是父亲结点的左孩子还是右孩子
- if (rightMinP->_left == rightMin)
- rightMinP->_left = rightMin->_right;
- else
- rightMinP->_right = rightMin->_right;
-
- delete rightMin;
- return true;
- }
- }
- }
- return false;
- }
我们写一个测试函数,测试一些删除函数的功能:
- void Test2()
- {
- int arr[] = { 11, 7, 18, 9, 14, 4, 23, 8, 16, 10 };
- key::BSTree<int> tree;
-
- for (auto& e : arr)
- {
- tree.Insert(e);
- }
- tree.InOrder();
-
- for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
- {
- printf("第%-2d次:", i + 1);
- tree.Erase(arr[i]);
- tree.InOrder();
- }
- }
运行结果如下:
上文有提到二叉搜索树有两种模型,其中在现实生活中比较常用的是KV模型。每个关键码key,都有对应的的值Value,即<Key,Value>键值对。
下面是KV模型的代码实现:
- namespace keyValue
- {
- template<class K, class V>
- struct BSTNode
- {
- BSTNode(const K& key = K(), const V& value = V())
- :_key(key)
- ,_value(value)
- , _left(nullptr)
- , _right(nullptr)
- {}
-
- K _key;
- V _value;
- BSTNode<K, V>* _left;
- BSTNode<K, V>* _right;
- };
-
- template<class K, class V>
- class BSTree
- {
- typedef BSTNode<K, V> Node;
-
- public:
- BSTree() = default;
-
- BSTree(const BSTree<K, V>& t)
- {
- _root = Copy(t._root);
- }
-
- ~BSTree()
- {
- Destroy(_root);
- _root = nullptr;
- }
-
- bool Insert(const K& key, const V& value)
- {
- if (_root == nullptr)
- {
- _root = new Node(key, value);
- }
-
- Node* parent = nullptr;
- Node* cur = _root;
- while (cur)
- {
- if (cur->_key < key)
- {
- parent = cur;
- cur = cur->_right;
- }
- else if (cur->_key > key)
- {
- parent = cur;
- cur = cur->_left;
- }
- else
- {
- return false;
- }
- }
-
- cur = new Node(key, value);
- if (parent->_key < key)
- {
- parent->_right = cur;
- }
- else
- {
- parent->_left = cur;
- }
- return true;
- }
-
- Node* Find(const K& key)
- {
- Node* cur = _root;
- while (cur)
- {
- if (cur->_key < key)
- {
- cur = cur->_right;
- }
- else if (cur->_key > key)
- {
- cur = cur->_left;
- }
- else
- {
- return cur;
- }
- }
- return nullptr;
- }
-
- bool Erase(const K& key)
- {
- Node* parent = nullptr;
- Node* cur = _root;
- while (cur)
- {
- if (cur->_key < key)
- {
- parent = cur;
- cur = cur->_right;
- }
- else if (cur->_key > key)
- {
- parent = cur;
- cur = cur->_left;
- }
- else
- {
- // 0-1孩子
- if (cur->_left == nullptr)
- {
- if (parent == nullptr)
- {
- _root = cur->_right;
- }
- else
- {
- if (parent->_left == cur)
- parent->_left = cur->_right;
- else
- parent->_right = cur->_right;
- }
- delete cur;
- return true;
- }
- else if (cur->_right == nullptr)
- {
- if (parent == nullptr)
- {
- _root = cur->_left;
- }
- else
- {
- if (parent->_left == cur)
- parent->_left = cur->_left;
- else
- parent->_right = cur->_left;
- }
- delete cur;
- return true;
- }
- else
- {
- // 两个孩子的情况
- // 右子树的最小节点作为替代节点
- Node* rightMinP = cur;
- Node* rightMin = cur->_right;
-
- while (rightMin->_left)
- {
- rightMinP = rightMin;
- rightMin = rightMin->_left;
- }
-
- cur->_key = rightMin->_key;
- cur->_value = rightMin->_value;
-
- if (rightMinP->_left == rightMin)
- rightMinP->_left = rightMin->_right;
- else
- rightMinP->_right = rightMin->_right;
-
- delete rightMin;
- return true;
- }
- }
- }
- return false;
- }
-
- void InOrder()
- {
- _InOrder(_root);
- cout << endl;
- }
-
- private:
- void _InOrder(Node* root)
- {
- if (root == nullptr)
- return;
-
- _InOrder(root->_left);
- cout << root->_key << ":" << root->_value << " ";
- _InOrder(root->_right);
- }
-
- void Destroy(Node* root)
- {
- if (root == nullptr)
- return;
-
- Destroy(root->_left);
- Destroy(root->_right);
- delete root;
- }
-
- Node* Copy(Node* root)
- {
- if (root == nullptr)
- return nullptr;
-
- Node* newRoot = new Node(root->_key, root->_value);
- newRoot->_left = Copy(root->_left);
- newRoot->_right = Copy(root->_right);
- }
-
- private:
- Node* _root = nullptr;
- };
- };
二叉搜索树KV模型的应用有许多
- void TestBsTree3()
- {
- keyValue::BSTree<string, string> dict;
- dict.Insert("left", "左边");
- dict.Insert("right", "右边");
- dict.Insert("apple", "苹果");
- dict.Insert("sort", "排序");
- dict.Insert("love", "爱");
-
- string str;
- while (cin >> str)
- {
- auto ret = dict.Find(str);
- if (ret)
- {
- cout << "->" << ret->_value << endl;
- }
- else
- {
- cout << "重新输入" << endl;
- }
- }
- }
运行结果如下:
这是统计词语出现次数
- void TestBSTree4()
- {
- // 统计物品出现的次数
- string arr[] = { "书本", "笔", "书本", "笔", "书本", "书本", "笔", "书本", "橡皮", "书本", "橡皮" };
- keyValue::BSTree<string, int> countTree;
-
- for (const auto& str : arr)
- {
- // 先查找物品在不在搜索树中
- // 1、不在,说明物品第一次出现,则插入<物品, 1>
- // 2、在,则查找到的节点中水果对应的次数++
-
- auto ret = countTree.Find(str);
- if (ret == NULL)
- countTree.Insert(str, 1);
- else
- ret->_value++;
-
- }
- countTree.InOrder();
- }
运行结果如下:
二叉搜索树的插入和删除操作,都需要先进行查找。查找操作一般最多查找这颗树的高度次,如果二叉搜索树是一个满二叉树或者完全二叉树,效率很高。可当二叉树退化成下图中右边这颗二叉树,基本上像链表的形态,那么查找的速度比原来慢了很多。
因此,在二叉搜索树的基础上,又出现了平衡二叉搜索树,如AVL树和红黑树,会调整二叉树成为接近满二叉树的形态。
通过本文的阐述,相信你应该对二叉搜索树的基本概念、特性以及操作方法已经有了一定的了解。不过想要掌握这个数据结构,还需要亲自上手编写一个二叉搜索树的代码。通过编码实践,才能更深刻体会到内部的工作机制。
创作不易,希望这篇文章能给你带来启发和帮助,如果喜欢这篇文章,请留下你的三连,你的支持的我最大的动力!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。