二、二叉搜索树的应用
1.K模型
K 模型: K 模型即只有 key 作为关键码,结构中只需要存储 Key 即可,关键码即为需要搜索到的值 。
比如: 给一个单词 word ,判断该单词是否拼写正确 ,具体方式如下:
以词库中所有单词集合中的每个单词作为 key ,构建一棵二叉搜索树
在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
2.KV模型
KV模型:每一个关键码key,都有与之对应的值Value,即的键值对。该种方式在现实生活中非常常见:
比如 英汉词典就是英文与中文的对应关系 ,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文 就构成一种键值对;
再比如 统计单词次数 ,统计成功后,给定单词就可快速找到其出现的次数, 单词与其出现次数就是 就构成一种键值对。
对于KV模型我们给出以下两个例子:
1.输入单词,查找单词对应的中文翻译:
namespace key_value { template <class K,class V> struct TreeNode { TreeNode<K,V>* left; TreeNode<K,V>* right; K _key; V _value; TreeNode(const K& key,const V& value) :left(nullptr) , right(nullptr) , _key(key) ,_value(value) { } }; template <class K,class V> class BSTree { typedef TreeNode<K,V> Node; public: BSTree() :_root(nullptr) { } BSTree(const BSTree<K,V>& t) { _root = copy(t._root); } BSTree<K,V>& operator=(BSTree<K,V> t) { std::swap(_root, t._root); return *this; } ~BSTree() { Destruct(_root); } bool insert(const K& val, const V& value) { if (_root == nullptr) { _root = new Node(val,value); return true; } Node* cur = _root; Node* parent = nullptr; while (cur) { if (cur->_key < val) { parent = cur; cur = cur->right; } else if (cur->_key > val) { parent = cur; cur = cur->left; } else { return false; } } if (parent->_key < val) { parent->right = new Node(val,value); } else { parent->left = new Node(val,value); } return true; } bool erase(const K& val) { assert(_root != nullptr); Node* cur = _root; Node* parent = nullptr; while (cur) { if (cur->_key < val) { parent = cur; cur = cur->right; } else if (cur->_key > val) { parent = cur; cur = cur->left; } else { //找到要删除的节点 if (cur->left == nullptr) { if (parent == nullptr) { _root = cur->right; return true; } 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; return true; } if (parent->left == cur) { parent->left = cur->left; } else { parent->right = cur->left; } delete cur; return true; } else { //要删除的节点有两个子树,需要右树的最小节点或者左树的最大节点托管 Node* minRight = cur->right; Node* PrevParent = cur; while (minRight->left) { PrevParent = minRight; minRight = minRight->left; } cur->_key = minRight->_key; if (PrevParent->left == minRight) { PrevParent->left = minRight->right; } else { PrevParent->right = minRight->right; } delete minRight; return true; } } } return false; } Node* find(const K& val) { Node* tmp = _find(_root, val); return tmp; } void Inorder() { _Inorder(_root); } protected: Node* copy(Node* root) { if (root == nullptr) { return nullptr; } Node* newroot = new Node(root->_key); newroot->left = copy(root->left); newroot->right = copy(root->right); return newroot; } void Destruct(Node*& root) { if (root == nullptr) { return; } Destruct(root->left); Destruct(root->right); delete root; root = nullptr; } Node* _find(Node* root, const K& val) { Node* cur = root; while (cur) { if (cur->_key < val) { cur = cur->right; } else if (cur->_key > val) { cur = cur->left; } else { return cur; } } return nullptr; } void _Inorder(Node* root) { if (root == nullptr) { return; } _Inorder(root->left); cout << root->_key << ":"<<root->_value<<" "; _Inorder(root->right); } private: Node* _root = nullptr; }; }
void test5() { key_value::BSTree<string, string> dict; dict.insert("sort", "排序"); dict.insert("insert", "插入"); dict.insert("left", "左边"); dict.insert("right", "右边"); dict.insert("erase", "删除"); string str; while (cin >> str) { auto ret = dict.find(str); if (ret) { cout << ":"<<ret->_value << endl; } else { cout << "找不到此单词" << endl; } } }
我们看看上述代码的结果:
KV的实现只需要添加一个模板参数,修改遍历中序打印函数和插入函数,插入的时候我们需要将新参数也插入,我们在节点中初始化的时候记得初始化新的参数即可。
简单的中文互译我们就搞定了,再看看统计次数的例子 。
2.统计水果出现的次数:
void test6() { string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" }; key_value::BSTree<string, int> ct; for (auto& ch : arr) { auto ret = ct.find(ch); if (ret) { ret->_value++; } else { ct.insert(ch, 1); } } ct.Inorder(); }
统计次数很简单,当数组中的水果在树中存在我们就让水果的次数++,如果不存在我们就将这个水果插入数中,最后打印出来的顺序是按照ascll码进行比较的,因为中文的底层是ascll。
总结
二叉搜索树的性能分析:
插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有 n 个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二
叉搜索树的深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树。
最优情况下,二叉搜索树为完全二叉树 ( 或者接近完全二叉树 ) ,其平均比较次数为:O(log N)
最差情况下,二叉搜索树退化为单支树 ( 或者类似单支 ) ,其平均比较次数为:O(N)