【C++】-- 搜索二叉树(一)

简介: 【C++】-- 搜索二叉树

一、二叉搜索树概念

二叉搜索树也叫做二叉排序树,它要么是一棵空树,要么具有以下性质:

(1)若它的左子树不为空,则左子树上所有节点的值都小于根节点的值

(2)若它的右子树不为空,则右子树上所有节点的值都大于根节点的值

(3)它的左右子树也分别为二叉搜索树

如下就是二叉搜索树,对于任何一个节点,它的左子树的所有节点值都比它小,它的右子树的所有节点值都比它大:

int a[] = {9,6,15,4,13,5,1,20,8,27};

总结:在左子树值比根小,右子树值比根大。 当树走中序遍历时,序列都是有序的。

二叉搜索树的结构定义:

1. #include<iostream>
2. using namespace std;
3. 
4. //树的节点可以支持多种类型
5. template<class K>
6. 
7. //树节点结构
8. struct BSTreeNode
9. {
10.   BSTreeNode<K>* _left;//左指针
11.   BSTreeNode<K>* _right;//右指针
12.   K _key;//值
13. 
14.   //树节点构造函数
15.   BSTreeNode(const K& key)
16.     :_left(nullptr)
17.     ,_right(nullptr)
18.     ,_key(key)
19.   {}
20. };
21. 
22. template<class K>
23. class BStree//树结构
24. {
25.   typedef BSTreeNode<K> Node;
26. public:
27. 
28.   //构造函数只需要将根初始化为空就行了
29.   BSTree()
30.     :_root(nullptr)
31.   {}
32. 
33. private:
34.   Node* _root;//根
35. };

二、二叉搜索树操作(非递归)

1.二叉搜索树的查找(非递归)

二份查找借助排序查找,二叉搜索树借助结构

查找的时间复杂度,最坏查找高度次,就可以确认一个值在不在树中:

(1)当树接近完全二叉树或满二叉树 ,时间复杂度为O(N):

(2)查找的时间复杂度最坏为O(N),如下这种情况:

因此二叉搜索树在极端情况下没办法保证效率。

(1)查找

查找比较简单:

       ①key比当前节点值大,向左走

       ②key比当前节点值小,向右走

       ③key等于当前节点值,找到了

1.  //查找
2.  Node* Find(const K& key)
3.  {
4.    Node* cur = _root;
5.    if (cur->_key > key)//比当前节点小,就向左走
6.    {
7.      cur = cur->_left;
8.    }
9.    else if (cur->_key < key)//比当前节点大,就向右走
10.     {
11.       cur = cur->_right;
12.     }
13.     else
14.     {
15.       return cur;//找到了,返回
16.     }
17. 
18.     return nullptr;
19.   }

(2)中序遍历

由于根节点的访问限定符是私有的,那么在main函数中要终须遍历一棵树时,就无法将二叉搜索树的对象根节点传给中序遍历,因为类外面访问不到私有成员。

因此可以这样考虑:这个二叉搜索树对象是有根节点的,可以考虑在里面套一层,给套在里面的函数传参_root ,去递归调用自己:

1.  //内层函数使用_root
2. void _Inorder(Node* root)
3.  {
4.    if (root == nullptr)
5.    {
6.      return;
7.    }
8.    _Inorder(root->_left);//递归调用自己
9.    cout << root->_key << " ";
10.     _Inorder(root->_right);//递归调用自己
11.   }
12. 
13.   //先调不传参数的InOrder
14.   void InOrder()
15.   {
16.     //把_root传给子函数,让子函数去使用_root
17.     _InOrder(_root);
18.     cout << endl;
19.   }

2.二叉搜索树的插入(非递归)

插入节点分两步:

(1)找位置

       ①key比当前节点值大,向左走

       ②key比当前节点值小,向右走

       ③key等于当前节点值,该节点值已经存在,插入失败

(2)插入

       ①key比父亲节点值小就插入父亲左子树

       ②key比父亲节点值大就插入父亲右子树

由于插入后,要将节点链接到树中,因此要定义parent节点,用来链接新节点:

1.  //插入
2.  bool Insert(const K& key)
3.  {
4.    //1.树为空
5.    if (_root == nullptr)
6.    {
7.      _root = new Node(key);
8.      return true;
9.    }
10. 
11.     //2.树不为空 非递归方式,两步:(1)找位置  (2)插入节点
12.     //(1)找位置
13.     Node* parent = nullptr;
14.     Node* cur = _root;
15.     while (cur)
16.     {
17.       if (cur->_key < key)//比当前节点大,就往右走
18.       {
19.         parent = cur;//记录当前节点的父亲位置
20.         cur = cur->_right;
21.       }
22.       else if (cur->_key > key)//比当前节点小,就向左走
23.       {
24.         parent = cur;//记录当前节点的父亲位置
25.         cur = cur->_left;
26.       }
27.       else
28.       {
29.         return false;//树里面已经有这个节点了,就返回flase
30.       }
31.     }
32. 
33.     //(2)插入节点
34.     cur = new Node(key);
35.     if(parent->_key > key)//比父亲节点值小就连接在父亲左子树
36.     {
37.       parent->_left = cur;
38.     }
39.     else
40.     {
41.       parent->_right = cur;//比父亲节点值大就连接在父亲右子树
42.     }
43.     return true;
44.   }

3.二叉搜索树的删除(非递归)

(1)找位置

       ①key比当前节点值大,向左走

       ②key比当前节点值小,向右走

       ③key等于当前节点值,找到了,准备删除

(2)删除,有两种删除方法:非递归和递归

       非递归删除:

       ①该节点没有孩子,即该节点是叶子节点,删除节点后把父亲指向自己的指针置空

       ②该节点有一个孩子,就把该节点的孩子节点的链接给该节点的父亲,顶替自己的位置,①可以当成②的特殊情况

       ③该节点有两个孩子,找比它自己的左孩子大,比它自己的右孩子小的节点替换它(也就是拿它的左子树的最大节点或右子树的最小节点替换它),替换之后,该节点就只有一个孩子或没有孩子了,就变成①或②了。

1.  //删除
2.  bool Erase(const K& key)
3.  {
4.    Node* parent = nullptr;
5.    Node* cur = _root;
6.    while (cur)
7.    {
8.      if (cur->_key > key)//比当前节点小,就往左走
9.      {
10.         parent = cur;//记录父亲位置
11.         cur = cur->_left;
12.       }
13.       else if (cur->_key < key)//比当前节点大,就往右走
14.       {
15.         parent = cur;//记录父亲位置
16.         cur = cur->_right;
17.       }
18.       else//找到了,要删除
19.       {
20.         //1.删除的是叶子节点, 删除节点后把父亲指向自己的指针置空
21.         //2.删除的是有一个孩子的节点,就把它的孩子节点的链接给它的父亲,顶替自己的位置
22.         //3.删除的是有两个孩子的节点,找比它自己的左孩子大,比它自己的右孩子小的节点替换它,
23.         //也就是它的左子树的最大节点或右子树的最小节点替换它,它就只有一个孩子或没有孩子了
24. 
25.         //第1可以当成第2去处理,当前节点的父亲节点指向空就可以了
26. 
27.         if (cur->_left == nullptr)//cur左为空,就让父亲指向cur的右
28.         {
29.           //如果要删除根,直接让根的右孩子做根
30.           if (cur == _root)
31.           {
32.             _root = cur->_right;
33.           }
34. else
35.                     {
36.               if (parent->_left == cur)//当cur是父亲的左时,就让父亲的左指向cur的右
37.               {
38.                 parent->_left = cur->_right;
39.               }
40.               else//当cur是父亲的右时,就让父亲的右指向cur的右
41.               {
42.                 parent->_right = cur->_right;
43.               }
44.                     }
45. 
46.           delete cur;
47.         }
48.         else if (cur->_right == nullptr)//cur右为空,就让父亲指向cur的左
49.         {
50.           //如果要删除根,直接让根的右孩子做根
51.           if (cur == _root)
52.           {
53.             _root = cur->_right;
54.           }
55.           else
56.           {
57.             if (parent->_left == cur)//当cur是父亲的左时,就让父亲的左指向cur的左
58.             {
59.               parent->_left = cur->_left;
60.             }
61.             else//当cur是父亲的右时,就让父亲的右指向cur的左
62.             {
63.               parent->_right = cur->_left;
64.             }
65.           }
66. 
67.           delete cur;//删除
68.         }
69.         else//左右都不为空,替换法删除
70.         {
71.           //找右子树最左节点  当右子树的左孩子不为空时就继续遍历
72.           Node* minRight = cur->_right;
73.           Node* minParent = cur;//这里不要初始化成null,否则左为空时,minParent->_left就会崩掉
74. 
75.           //当左不为空时,就一直向左走,直到找到右子树最左节点
76.           while (minRight->_left)
77.           {
78.             minParent = minRight;
79.             minRight = minRight->_left;
80.           }
81. 
82.           //保存替换节点的值
83.           cur->_key = minRight->_key;
84. 
85.           //删除替换节点
86.           if (minParent->_left == minRight)//如果右子树最左节点是minParent的左,那就让minParent的左指向右子树最左节点的右
87.           {
88.             minParent->_left = minRight->_right;
89.           }
90.           else//如果右子树最左节点是minParent的右,那就让minParent的右指向右子树最左节点的右
91.           {
92.             minParent->_right = minRight->_right;
93.           }
94.           delete minRight;//删除
95.         }
96. 
97.         return true;
98.       }
99. 
100.    }
101.    return false;//cur不存在,直接返回
102.  }

递归删除:

相对于非递归,只需要修改找到了要修改的代码:找到了后不需要管cur到底左为空、右为空、还是左右都不为空

① 找要删除节点的右子树的最小节点并把它的值保存起来

② 删除右子树的最小节点

③ 把要删除的节点值替换成右子树的最小节点值

1. else//左右都不为空,替换法删除
2.                 {
3.          //找右子树最小节点
4.          Node* minRight = cur->_right;
5.          while (minRight->_left)
6.          {
7.            minRight = minRight->_left;
8.          }
9. 
10.           //用min保存右子树最小节点的值
11.           K min = minRight->_key;
12. 
13.           //递归调用自己去替换删除节点,一定会走到左为空的情况处理
14.           this->Erase(min);
15. 
16.           //删除完毕替换节点之后,把cur的值替换成min
17.           cur->_key = min;
18.         }


相关文章
|
6月前
|
C++
二叉树进阶面试题(精华总结)【C++版本】
二叉树进阶面试题(精华总结)【C++版本】
|
6月前
|
存储 编译器 数据库
【C/C++ 数据结构 】线索二叉树全解析:从数学原理到C++实现
【C/C++ 数据结构 】线索二叉树全解析:从数学原理到C++实现
137 0
|
6月前
|
算法 C++ 开发者
【C/C++ 数据结构 】二叉树基本性质:对于任何一颗二叉树T,若其终端结点为n0 ,那么度数为2的结点数为n2。则n0=n2+1...
【C/C++ 数据结构 】二叉树基本性质:对于任何一颗二叉树T,若其终端结点为n0 ,那么度数为2的结点数为n2。则n0=n2+1...
77 0
|
1月前
|
存储 算法 C++
【搜索算法】 跳马问题(C/C++)
【搜索算法】 跳马问题(C/C++)
|
1月前
|
人工智能 算法 Java
【搜索算法】数字游戏(C/C++)
【搜索算法】数字游戏(C/C++)
|
4月前
|
存储 C++
【C++】二叉树进阶之二叉搜索树(下)
【C++】二叉树进阶之二叉搜索树(下)
32 4
|
4月前
|
Java 编译器 C++
【C++】二叉树进阶之二叉搜索树(上)
【C++】二叉树进阶之二叉搜索树(上)
36 3
|
4月前
|
算法 C++
【C++高阶】高效搜索的秘密:深入解析搜索二叉树
【C++高阶】高效搜索的秘密:深入解析搜索二叉树
42 2
|
6月前
|
存储 C++
【C++】二叉树进阶 -- 详解
【C++】二叉树进阶 -- 详解
|
6月前
|
存储 算法 数据管理
C++中利用随机策略优化二叉树操作效率的实现方法
C++中利用随机策略优化二叉树操作效率的实现方法
116 1