当前位置:   article > 正文

数据结构之二叉搜索树_最优二叉查找树

最优二叉查找树

1.二叉搜索树

1.1 二叉搜索树概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
·若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
·若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
·它的左右子树也分别为二叉搜索树

 通过观察可以发现:搜索二叉树走一遍中序遍历可以排成升序.

1.2 二叉搜索树操作

1. 二叉搜索树的查找

2. 二叉搜索树的插入

 插入的具体过程如下:
a. 树为空,则直接插入

b. 树不空,按二叉搜索树性质查找插入位置,插入新节点

3. 二叉搜索树的删除

删除节点的原则是:必须保持搜索二叉树的特性.

1.3 二叉搜索树的实现

  1. namespace K
  2. {
  3. template<class K>
  4. struct BSTreeNode//定义节点的结构体
  5. {
  6. BSTreeNode<K>* _left;//左孩子指针
  7. BSTreeNode<K>* _right;//右孩子指针
  8. K _key;//存的值
  9. BSTreeNode(const K& key)//节点的构造函数
  10. :_left(nullptr)
  11. ,_right(nullptr)
  12. ,_key(key)
  13. {}
  14. };
  15. template<class K>
  16. class BSTree
  17. {
  18. typedef BSTreeNode<K> Node;
  19. public:
  20. BSTree()//构造函数
  21. :_root(nullptr)
  22. {}
  23. BSTree(const BSTree<K>& t)
  24. {
  25. _root = Copy(t._root);//调用递归copy去拷贝构造
  26. }
  27. BSTree<K>& operator=(BSTree<K> t)//利用现代写法进行赋值拷贝
  28. {
  29. swap(_root, t._root);
  30. return *this;
  31. }
  32. ~BSTree()
  33. {
  34. Destory(_root);//调用Destory去析构
  35. _root = nullptr;//将root置空
  36. }
  37. bool Insert(const K& key)
  38. {
  39. if (_root == nullptr)//如果刚开始树为空树,那么直接给一个节点给root
  40. {
  41. _root = new Node(key);
  42. return true;
  43. }
  44. Node* cur = _root;//利用cur去循环迭代找到空位
  45. Node* parent = nullptr;//利用parent去记录cur的父亲节点,并且要注意这个刚开始给成nullptr
  46. while (cur)//利用循环去找适合key的空位,找到为空的位置就结束
  47. {
  48. if (key > cur->_key)//大于就去右子树
  49. {
  50. parent = cur;//进入语句先把父节点赋值
  51. cur = cur->_right;//然后再把cur迭代
  52. }
  53. else if (key < cur->_key)//小于就去左子树
  54. {
  55. parent = cur;
  56. cur = cur->_left;
  57. }
  58. else
  59. {
  60. return false;//如果找到与key值相等的节点那么就删除
  61. }
  62. }//出了while循环说明找到了合适的空位,此时可以开始插入新节点
  63. cur = new Node(key);//给cur申请一个节点
  64. if (key > parent->_key)//这里的if语句判断一定要注意,是用值判断而不是指针
  65. parent->_right = cur;
  66. else if (key < parent->_key)
  67. parent->_left = cur;
  68. return true;//插入成功返回
  69. }
  70. Node* Find(const K& key)
  71. {
  72. Node* cur = _root;
  73. while (cur)//利用循环去找
  74. {
  75. if (key > cur->_key)
  76. {
  77. cur = cur->_right;
  78. }
  79. else if (key < cur->_key)
  80. {
  81. cur = cur->_left;
  82. }
  83. else
  84. {
  85. return cur;
  86. }
  87. }
  88. return false;//出了循环还未找到就返回false
  89. }
  90. bool Erase(const K& key)
  91. {
  92. Node* cur = _root;
  93. Node* parent = nullptr;
  94. while (cur)
  95. {
  96. if (key > cur->_key)
  97. {
  98. parent = cur;
  99. cur = cur->_right;
  100. }
  101. else if (key < cur->_key)
  102. {
  103. parent = cur;
  104. cur = cur->_left;
  105. }//上面两段if语句都是去定位key的位置
  106. else//进入else语句则找到了需要删除的节点
  107. {
  108. if (cur->_left == nullptr)//如果需要删除的节点的左节点为空,即没有左孩子
  109. {
  110. if (cur == _root)//注:这种情况非常容易被忽略
  111. {
  112. _root = cur->_right;//如果cur就为根节点的话,也就是右单支的情况,那么把root赋值为cur的右节点
  113. }
  114. else//cur不为根节点的情况
  115. {
  116. if (cur == parent->_left)//需要判断cur为父亲的左孩子还是右孩子
  117. {
  118. parent->_left = cur->_right;//需要将cur的右孩子托孤给父亲
  119. }
  120. else if (cur == parent->_right)
  121. {
  122. parent->_right = cur->_right;
  123. }
  124. }
  125. delete cur;//最后删除cur节点
  126. }
  127. else if (cur->_right == nullptr)//如果需要删除的节点的右节点为空,即没有右孩子
  128. {
  129. if (cur == _root)//需要特别注意
  130. {
  131. _root = cur->_left;//如果cur就为根节点的话,也就是左单支的情况,那么把root赋值为cur的左节点
  132. }
  133. else//cur不为根节点的情况
  134. {
  135. if (cur == parent->_left)//需要判定的是cur节点为父节点的左孩子还是右孩子
  136. {
  137. parent->_left = cur->_left;//将cur节点的左孩子托孤给父亲
  138. }
  139. else if (cur == parent->_right)
  140. {
  141. parent->_right = cur->_left;
  142. }
  143. }
  144. delete cur;//最后删除cur节点
  145. }
  146. else
  147. {
  148. //这种情况最复杂:也就是需要删除的节点左孩子和右孩子都存在的情况
  149. //此时我们采用替代法删除
  150. Node* minRight = cur->_right;//去被删除的节点的右子树找最小节点,左子树找最大节点道理也是一样的
  151. Node* minParent = cur;//注意:这里一定不能给空节点,必须给cur节点
  152. //至于为什么不能给空节点,万一下面的while循环进不去,后面的if语句就会造成非法访问
  153. while (minRight->_left)//注意:括号里面是左节点不为空时为循环结束的条件,因为我们的目的是去找到最左节点,最左节点已经走到空了,就不要再进入循环了
  154. {
  155. minParent = minRight;
  156. minRight = minRight->_left;
  157. }
  158. cur->_key = minRight->_key;//将cur的值替换成右子树的最小节点值
  159. //走到这里就很容易知道最小节点的左子树一定为空了,但是右子树呢?
  160. //右子树如果不为空我们就需要进行托孤,这种托孤方式也就演变成了上面两种简单的方式
  161. //但是这一部很容易被遗忘:大家都会觉得minRight一定会是minParent的左孩子
  162. //这是错误的理解,这里一定要去判断minRight为minParent右孩子的情况,这里画图理解
  163. if (minRight == minParent->_left)//如果为父节点的左孩子,那么就把我的右孩子交给父节点的左孩子
  164. minParent->_left = minRight->_right;
  165. else if (minRight == minParent->_right)//如果为父节点的右孩子,那么就把我的右孩子交给父节点的右孩子
  166. minParent->_right = minRight->_right;
  167. delete minRight;
  168. }
  169. return true;//删除完节点过后返回true
  170. }
  171. }
  172. return false;//出了while循环还没有找到需要删除的节点返回false
  173. }
  174. bool InsertR(const K& key)//递归插入key
  175. {
  176. return _InsertR(_root, key);
  177. }
  178. Node* FindR(const K& key)//递归查找key
  179. {
  180. return _FindR(_root, key);
  181. }
  182. bool EraseR(const K& key)//递归删除key
  183. {
  184. return _EraseR(_root, key);
  185. }
  186. void Inorder()//中序遍历
  187. {
  188. _Inorder(_root);//把中序遍历的递归封装一层,传root指针,保护root不被改变
  189. cout << endl;
  190. }
  191. private:
  192. void Destory(Node* root)
  193. {
  194. if (root == nullptr)
  195. return;
  196. Destory(root->_left);
  197. Destory(root->_right);
  198. delete root;//利用后序遍历递归删除节点
  199. }
  200. Node* Copy(Node* root)
  201. {
  202. if (root == nullptr)
  203. return nullptr;
  204. Node* newroot = new Node(root->_key);//建立新节点
  205. newroot->_left = Copy(root->_left);//新节点的左右节点再去转换成子问题
  206. newroot->_right = Copy(root->_right);
  207. return newroot;//最后返回新节点
  208. }
  209. bool _InsertR(Node*& root, const K& key)//注意这里要用引用传参
  210. {
  211. if (root == nullptr)//root为空时new一个节点
  212. {
  213. root = new Node(key);
  214. return true;
  215. }
  216. if (key > root->_key)
  217. {
  218. return _InsertR(root->_right, key);
  219. }
  220. else if (key < root->_key)
  221. {
  222. return _InsertR(root->_left, key);
  223. }
  224. else
  225. {
  226. return false;
  227. }
  228. }
  229. Node* _FindR(Node* root, const K& key)
  230. {
  231. if (root == nullptr)
  232. {
  233. return nullptr;
  234. }
  235. if (key > root->_key)
  236. {
  237. return _FindR(root->_right, key);
  238. }
  239. else if (key < root->_key)
  240. {
  241. return _FindR(root->left, key);
  242. }
  243. else
  244. {
  245. return root;
  246. }
  247. }
  248. bool _EraseR(Node*& root, const K& key)//注意这里一定要使用引用传参
  249. {
  250. if (root == nullptr)//如果root为空则返回false
  251. {
  252. return false;
  253. }
  254. if (key > root->_key)
  255. {
  256. return _EraseR(root->_right, key);
  257. }
  258. else if (key < root->_key)
  259. {
  260. return _EraseR(root->_left, key);
  261. }
  262. else
  263. {
  264. //找到了该节点
  265. if (root->_left == nullptr)//左孩子为空,右孩子存在的情况
  266. {
  267. Node* del = root;//用del记录一下这个带删除的节点
  268. root = root->_right;//然后将root的右节点赋值给root
  269. //上面这一步非常巧,左边的root实际是上一层父节点的左孩子或右孩子的引用
  270. //这里就体现出了引用传参的作用
  271. delete del;//链接关系处理完过后,最后删除这个root节点
  272. }
  273. else if (root->_right == nullptr)
  274. {
  275. Node* del = root;
  276. root = root->_left;
  277. delete del;
  278. }
  279. else
  280. {
  281. Node* minRight = root->_right;
  282. while (minRight->_left)//递归去找root的右子树中的最左节点,也就是最小节点
  283. {
  284. minRight = minRight->_left;
  285. }
  286. K min = minRight->_key;//用变量min存一下这个节点
  287. //转换成在root的右子树删除min
  288. _EraseR(root->_right, min);//这里转换成子问题去删除min
  289. root->_key = min;//再将root的值替换成min
  290. }
  291. return true;
  292. }
  293. }
  294. void _Inorder(Node* root)
  295. {
  296. if (root == nullptr)
  297. return;
  298. _Inorder(root->_left);
  299. cout << root->_key << " ";
  300. _Inorder(root->_right);
  301. }
  302. Node* _root;
  303. };
  304. }

二叉树实现的代码测试:

  1. void TestBSTree1()
  2. {
  3. K::BSTree<int> t;
  4. int a[] = { 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 };
  5. for (auto e : a)
  6. {
  7. t.InsertR(e);
  8. }
  9. t.Inorder();
  10. t.EraseR(5);
  11. t.Inorder();
  12. t.EraseR(8);
  13. t.Inorder();
  14. t.EraseR(7);
  15. t.Inorder();
  16. t.EraseR(5);
  17. t.Inorder();
  18. // 测试时,把树中的节点删除干净,确保没问题
  19. for (auto e : a)
  20. {
  21. t.EraseR(e);
  22. t.Inorder();
  23. }
  24. t.Inorder();
  25. }
  26. void TestBSTree2()
  27. {
  28. K::BSTree<int> t;
  29. int a[] = { 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 };
  30. for (auto e : a)
  31. {
  32. t.InsertR(e);
  33. }
  34. t.Inorder();
  35. K::BSTree<int> copy = t;
  36. copy.Inorder();
  37. }

1.4 二叉搜索树的应用

1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。核心是想要体现在不在的问题。
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
·以单词集合中的每个单词作为key,构建一棵二叉搜索树
·在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
2. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见。核心是想要通过一个值去找另一个值。

比如:英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英
文单词与其对应的中文<word, chinese>就构成一种键值对;再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。
比如:实现一个简单的英汉词典dict,可以通过英文找到与其对应的中文,具体实现方式如下:

·<单词,中文含义>为键值对构造二叉搜索树,注意:二叉搜索树需要比较,键值对比较时只比较Key

·查询英文单词时,只需给出英文单词,就可快速找到与其对应的key

KV模型的代码:

  1. namespace KV
  2. {
  3. template<class K, class V>
  4. struct BSTreeNode
  5. {
  6. BSTreeNode<K, V>* _left;
  7. BSTreeNode<K, V>* _right;
  8. K _key;
  9. V _value;
  10. BSTreeNode(const K& key, const V& value)
  11. : _left(nullptr)
  12. , _right(nullptr)
  13. , _key(key)
  14. , _value(value)
  15. {}
  16. };
  17. template<class K, class V>
  18. class BSTree
  19. {
  20. typedef BSTreeNode<K, V> Node;
  21. private:
  22. // 如果树中已经存在key,返回false
  23. bool _InsertR(Node*& root, const K& key, const V& value)
  24. {
  25. if (root == NULL) // 插入
  26. {
  27. root = new Node(key, value);
  28. return true;
  29. }
  30. if (root->_key < key)
  31. {
  32. return _InsertR(root->_right, key, value);
  33. }
  34. else if (root->_key > key)
  35. {
  36. return _InsertR(root->_left, key, value);
  37. }
  38. else
  39. {
  40. return false;
  41. }
  42. }
  43. Node* _FindR(Node* root, const K& key)
  44. {
  45. if (root == nullptr)
  46. {
  47. return nullptr;
  48. }
  49. if (root->_key < key)
  50. {
  51. return _FindR(root->_right, key);
  52. }
  53. else if (root->_key > key)
  54. {
  55. return _FindR(root->_left, key);
  56. }
  57. else
  58. {
  59. return root;
  60. }
  61. }
  62. // 如果树中不存在key,返回false
  63. // 存在,删除后,返回true
  64. bool _EraseR(Node*& root, const K& key)
  65. {
  66. if (root == NULL)
  67. {
  68. return false;
  69. }
  70. if (root->_key < key)
  71. {
  72. return _EraseR(root->_right, key);
  73. }
  74. else if (root->_key > key)
  75. {
  76. return _EraseR(root->_left, key);
  77. }
  78. else
  79. {
  80. // 找到了,root就是要删除的节点
  81. if (root->_left == nullptr)
  82. {
  83. Node* del = root;
  84. root = root->_right;
  85. delete del;
  86. }
  87. else if (root->_right == nullptr)
  88. {
  89. Node* del = root;
  90. root = root->_left;
  91. delete del;
  92. }
  93. else
  94. {
  95. Node* minRight = root->_right;
  96. while (minRight->_left)
  97. {
  98. minRight = minRight->_left;
  99. }
  100. K kmin = minRight->_key;
  101. V vMin = minRight->_value;
  102. // 转换成在root的右子树删除min
  103. _EraseR(root->_right, kmin);
  104. root->_key = kmin;
  105. root->_value = vMin;
  106. }
  107. return true;
  108. }
  109. }
  110. void _Destory(Node* root)
  111. {
  112. if (root == NULL)
  113. {
  114. return;
  115. }
  116. _Destory(root->_left);
  117. _Destory(root->_right);
  118. delete root;
  119. }
  120. Node* _Copy(Node* root)
  121. {
  122. if (root == nullptr)
  123. {
  124. return nullptr;
  125. }
  126. Node* copyNode = new Node(root->_key, root->_value);
  127. copyNode->_left = _Copy(root->_left);
  128. copyNode->_right = _Copy(root->_right);
  129. return copyNode;
  130. }
  131. public:
  132. BSTree()
  133. :_root(nullptr)
  134. {}
  135. BSTree(const BSTree<K, V>& t)
  136. {
  137. _root = _Copy(t._root);
  138. }
  139. // t1 = t2
  140. BSTree<K, V>& operator=(BSTree<K, V> t)
  141. {
  142. swap(_root, t._root);
  143. return *this;
  144. }
  145. ~BSTree()
  146. {
  147. _Destory(_root);
  148. _root = nullptr;
  149. }
  150. bool InsertR(const K& key, const V& value)
  151. {
  152. return _InsertR(_root, key, value);
  153. }
  154. Node* FindR(const K& key)
  155. {
  156. return _FindR(_root, key);
  157. }
  158. bool EraseR(const K& key)
  159. {
  160. return _EraseR(_root, key);
  161. }
  162. void _InOrder(Node* root)
  163. {
  164. if (root == nullptr)
  165. return;
  166. _InOrder(root->_left);
  167. cout << root->_key << ":" << root->_value << endl;
  168. _InOrder(root->_right);
  169. }
  170. void InOrder()
  171. {
  172. _InOrder(_root);
  173. cout << endl;
  174. }
  175. private:
  176. Node* _root;
  177. };
  178. }

KV模型代码测试:
 

  1. void TestBSTree3()
  2. {
  3. KV::BSTree<string, string> dict;
  4. dict.InsertR("string", "字符串");
  5. dict.InsertR("tree", "树");
  6. dict.InsertR("left", "左边、剩余");
  7. dict.InsertR("right", "右边");
  8. dict.InsertR("sort", "排序");
  9. // ...插入词库中所有单词
  10. string str;
  11. while (cin >> str)
  12. {
  13. KV::BSTreeNode<string, string>* ret = dict.FindR(str);
  14. if (ret == nullptr)
  15. {
  16. cout << "单词拼写错误,词库中没有这个单词:" << str << endl;
  17. }
  18. else
  19. {
  20. cout << str << "中文翻译:" << ret->_value << endl;
  21. }
  22. }
  23. }
  24. void TestBSTree4()
  25. {
  26. // 统计水果出现的次数
  27. string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
  28. KV::BSTree<string, int> countTree;
  29. for (const auto& str : arr)
  30. {
  31. // 先查找水果在不在搜索树中
  32. // 1、不在,说明水果第一次出现,则插入<水果, 1>
  33. //KV::BSTreeNode<string, int>* ret = countTree.FindR(str);
  34. auto ret = countTree.FindR(str);
  35. if (ret == NULL)
  36. {
  37. countTree.InsertR(str, 1);
  38. }
  39. else
  40. {
  41. ret->_value++;
  42. }
  43. }
  44. countTree.InOrder();
  45. }

1.5 二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树

最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:log2N
最差情况下,二叉搜索树退化为单支树,其平均比较次数为: N/2

问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码,都可以是二叉搜索树的性能最佳?

所以实际中搜索二叉树,极端情况下没有办法保证效率,所以后面会对搜索二叉树进一步扩展延申:AVLTree,红黑树.他们对搜索二叉树,左右高度提出了要求,非常接近完全二叉树,他们的效率可以达到O(logN)

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号