接下来我们来开始使用C++来详细讲解数据结构的一些高阶的知识点
本期讲解的是二叉搜索树,对于初阶二叉树有所遗忘的同学可以看到这里:
讲解二叉搜索树主要是为了后面的map和set做铺垫,废话不多说我们直接上干货:
目录
二叉搜索树又称二叉排序树(BST, Binary Search Tree),它可以是一棵空树,或者是具有以下性质的二叉树:
若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树
例如:
我们可以发现的一点:无论是什么样的二叉搜索树,使用中序遍历,遍历出的值都是升序排列的
下面又到了我们最激动人心的代码实现环节,本次代码实现我们还是要基于链式二叉树的实现:
- template<class K>
- struct BSTreeNode//节点
- {
- BSTreeNode
* _lchild; - BSTreeNode
* _rchild; - K _key;
-
- BSTreeNode(const K& key)
- :_lchild(nullptr),
- _rchild(nullptr),
- _key(key)
- {}
- };
我们可以根据二叉搜索树的规律来向其中插入数据,但是插入数据时需要注意一点:要记录插入节点的上一个父节点,将插入的节点连接上二叉树:
- template<class K>
- class BSTree
- {
- typedef BSTreeNode
Node; - public:
- bool Insert(const K& key)
- {
- if (_root == nullptr)
- {
- _root = new Node(key);
- _root->_key = key;
- return true;
- }
- Node* cur = _root;//使用cur遍历二叉树找到合适的插入位置
- Node* parent = cur;//记录cur的父节点
- while (cur)
- {
- parent = cur;
- if (key < cur->_key)
- {
- cur = cur->_lchild;
- }
- else if (key > cur->_key)
- {
- cur = cur->_rchild;
- }
- else
- {
- return false;//这里创建的二叉搜索树不允许出现值的冗余
- }
- }
- cur = new Node(key);//创建
- //将创建的节点链接到二叉树上
- if (key < parent->_key)
- {
- parent->_lchild = cur;
- }
- else
- {
- parent->_rchild = cur;
- }
- return true;
- }
-
- private:
- Node* _root = nullptr;//根节点
- };
递归的效率并没有循环高,那为什么要说一下插入数据的非递归实现呢
主要是非递归的数据插入的传值方法值得一说:
- template<class K>
- class BSTree
- {
- typedef BSTreeNode
Node; - public:
- bool InsertR(const K& key)//插入数据(递归)
- {
- return _InsertR(_root, key);
- }
-
- bool _InsertR(Node*& root,const K& key)//这里使用指针的引用用来直接修改其父节点指针的指向
- {
- if (root == nullptr)
- {
- root = new Node(key);
- return true;
- }
- if (root->_key > key)
- {
- _InsertR(root->_lchild, key);
- }
- else if (root->_key < key)
- {
- _InsertR(root->_rchild, key);
- }
- else
- {
- return false;
- }
- }
- private:
- Node* _root = nullptr;//根节点
- };
我们可以看到在递归时,传入的形参类型为Node*&,这样可以直接在其函数内部习惯其父节点孩子指针的指向
那为什么要写两个插入函数呢?因为如果我们直接使用_InsertR函数,无法直接使用对象对_InsertR函数进行传参
因为二叉搜索树的性质,这里我们采用中序遍历:
- template<class K>
- class BSTree
- {
- typedef BSTreeNode
Node; - public:
- bool Insert(const K& key)插入数据
- {
- ....
- }
-
- void InOrder()//中序遍历
- {
- _InOrder(_root);
- cout << endl;
- }
-
- void _InOrder(Node* root)
- {
- if (root == NULL)//如果是空树就直接结束
- {
- return;
- }
- _InOrder(root->_lchild);//先递归遍历其左子树
- cout << root->_key << " ";//再遍历其根节点
- _InOrder(root->_rchild);//最后递归遍历其右子树
- }
-
- private:
- Node* _root = nullptr;//根节点
- };
那为什么要写两个中序遍历函数呢?因为如果我们直接使用_InOrder函数,无法直接使用对象对_InOrder函数进行传参
- template<class K>
- class BSTree
- {
- typedef BSTreeNode
Node; - public:
- bool Insert(const K& key)//插入数据
- {
- ......
- }
-
- void InOrder()//中序遍历
- {
- ......
- }
-
- void _InOrder(Node* root)
- {
- ......
- }
-
- bool Find(const K& key)//查找数据
- {
- Node* cur = _root;
- while (cur)
- {
- if (cur->_key < key)
- {
- cur = cur->_rchild;
- }
- else if (cur->_key > key)
- {
- cur = cur->_lchild;
- }
- else
- {
- return true;
- }
- }
- return false;
- }
-
- private:
- Node* _root = nullptr;//根节点
- };
对于在二叉搜索树中删除数据,我们就要好好说道说道了
下面我们有这样的一个二叉搜索树:
现在我们要删除其叶子节点,这很容易,直接删除完,再将其父节点对应的孩子指针置空即可
那我们要删只有一个孩子节点的数据呢?例如14和10
这个稍微麻烦一点,删除完该节点后将其孩子节点托付给其父节点即可:
那要删带有两个孩子节点的数据呢?例如3、6、8:
对于这种情况我们可以选择其节点下的左子树的最大节点(左子树的最右节点),或者右子树的最小节点(右子树的最左节点)来替代要删除的节点:
综上所述,要删除的结点可能分下面四种情况:
a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点
看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:
情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除
情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除
情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题--替换法删除
思路我们有了,下面用代码来实现一下:
- template<class K>
- class BSTree
- {
- typedef BSTreeNode
Node; - public:
- bool Insert(const K& key)//插入数据
- {
- ...
- }
-
- void InOrder()//中序遍历
- {
- ...
- }
-
- void _InOrder(Node* root)
- {
- ...
- }
-
- bool Find(const K& key)//查找数据
- {
- ...
- }
-
- bool Erase(const K& key)
- {
- Node* cur = _root;//使用cur遍历二叉树找到要删除元素的位置
- Node* parent = cur;//记录cur的父节点
- while (cur)//寻找需要删除的节点
- {
- if (cur->_key < key)
- {
- parent = cur;
- cur = cur->_rchild;
- }
- else if (cur->_key > key)
- {
- parent = cur;
- cur = cur->_lchild;
- }
- else//找到了,开始删除
- {
- if (cur->_lchild == cur->_rchild && cur->_lchild == nullptr)//删除的节点为叶子节点
- {
- if (parent == cur)//删除的是根节点
- {
- _root = nullptr;//更新根节点
- }
- //将其父节点指向的自身的指针置空
- else if (parent->_lchild == cur)
- {
- parent->_lchild = nullptr;
- }
- else
- {
- parent->_rchild = nullptr;
- }
- //释放节点空间
- delete cur;
- cur = nullptr;
- }
- else if (cur->_rchild == nullptr)//删除的节点右孩子为空,左孩子不为空
- {
- if (parent == cur)//删除的是根节点
- {
- _root = cur->_lchild;//更新根节点
- }
- //将删除节点的左孩子交给其父节点
- else if (parent->_lchild == cur)
- {
- parent->_lchild = cur->_lchild;
- }
- else
- {
- parent->_rchild = cur->_lchild;
- }
- //释放节点空间
- delete cur;
- cur = nullptr;
- }
- else if (cur->_lchild == nullptr)//删除的节点左孩子为空,右孩子不为空
- {
- if (parent == cur)//删除的是根节点
- {
- _root = cur->_rchild;//更新根节点
- }
- //将删除节点的右孩子交给其父节点
- else if (parent->_lchild == cur)
- {
- parent->_lchild = cur->_rchild;
- }
- else
- {
- parent->_rchild = cur->_rchild;
- }
- //释放节点空间
- delete cur;
- cur = nullptr;
- }
- else//删除的节点左右孩子都不为空,要找到删除节点的左子树的最大节点或右子树的最小节点可将其替换
- {
- //这里找要删除节点的左子树的最大节点(右子树的最小节点也可)
- Node* maxleft = cur->_lchild; // 记录左子树的最大节点
- Node* pmaxleft = cur;//记录左子树的最大节点的父节点
- while (maxleft->_rchild)//查找左子树的最大节点
- {
- pmaxleft = maxleft;
- maxleft = maxleft->_rchild;
- }
- //将找到的节点替换要删除的节点
- cur->_key = maxleft->_key;
- if (pmaxleft->_lchild == maxleft)
- {
- pmaxleft->_lchild = maxleft->_lchild;
- }
- else
- {
- pmaxleft->_rchild = maxleft->_lchild;
- }
- //释放节点空间
- delete maxleft;
- maxleft = nullptr;
- }
- return true;
- }
- }
- return false;//没找到要删除的节点
- }
- private:
- Node* _root = nullptr;//根节点
- };
- template<class K>
- class BSTree
- {
- typedef BSTreeNode
Node; - public:
- bool EraseR(const K& key)//递归删除数据
- {
- return _EraseR(_root, key);
- }
-
- bool _EraseR(Node*& root, const K& key)
- {
- if (root == nullptr)
- {
- return false;
- }
- else if (root->_key < key)
- {
- _EraseR(root->_rchild, key);
- }
- else if (root->_key > key)
- {
- _EraseR(root->_lchild, key);
- }
- else
- {
- Node* del = root;
- if (root->_lchild == root->_rchild && root->_lchild == nullptr)//删除的节点为叶子节点
- {
- //释放节点空间
- delete root;
- //将其父节点指向的自身的指针置空
- root = nullptr;
- }
- else if (root->_rchild == nullptr)//删除的节点右孩子为空,左孩子不为空
- {
- //将删除节点的左孩子交给其父节点
- root = root->_lchild;
- //释放节点空间
- delete del;
- del = nullptr;
- }
- else if (root->_lchild == nullptr)//删除的节点左孩子为空,右孩子不为空
- {
- //将删除节点的右孩子交给其父节点
- root = root->_rchild;
- //释放节点空间
- delete del;
- del = nullptr;
- }
- else//删除的节点左右孩子都不为空,要找到删除节点的左子树的最大节点或右子树的最小节点将其替换
- {
- //这里找要删除节点的右子树的最小节点(左子树的最大节点也可)
- Node* minright = del->_rchild; // 记录右子树的最小节点
- while (minright->_lchild)//查找右子树的最小节点
- {
- minright = minright->_lchild;
- }
- root->_key = minright->_key;//将找到的节点替换要删除的节点
- return _EraseR(root->_rchild, root->_key);//继续递归删除其右子树中用来替换的节点
- }
- return true;
- }
- }
-
- private:
- Node* _root = nullptr;//根节点
- };
- class BSTree
- {
- typedef BSTreeNode
Node; -
- public:
- ~BSTree()//析构
- {
- Destory(_root);
- }
- void Destory(Node*& root)//销毁二叉搜索树
- {
- if (root == nullptr)
- return;
- Destory(root->_lchild);
- Destory(root->_rchild);
- delete root;
- root = nullptr;
- }
- private:
- Node* _root = nullptr;//根节点
- };
- template<class K>
- class BSTree
- {
- typedef BSTreeNode
Node; -
- public:
-
- BSTree(const BSTree
& t)//拷贝构造 - {
- _root = Copy(t._root);
- }
-
- Node* Copy(const Node* node)//拷贝
- {
- if (node == nullptr)
- {
- return nullptr;
- }
- Node* newnode = new Node(node->_key);
- newnode->_lchild = Copy(node->_lchild);
- newnode->_rchild = Copy(node->_rchild);
- return newnode;
- }
-
- private:
- Node* _root = nullptr;//根节点
- };
- template<class K>
- class BSTree
- {
- typedef BSTreeNode
Node; - public:
- BSTree& operator=(BSTree
t)//赋值重载 - {
- std::swap(_root, t._root);
- return *this;
- }
- private:
- Node* _root = nullptr;//根节点
- };
- #include
- using namespace std;
-
- template<class K>
- struct BSTreeNode//节点
- {
- BSTreeNode
* _lchild; - BSTreeNode
* _rchild; - K _key;
-
- BSTreeNode(const K& key)
- :_lchild(nullptr),
- _rchild(nullptr),
- _key(key)
- {}
- };
-
- template<class K>
- class BSTree
- {
- typedef BSTreeNode
Node; -
- public:
- BSTree() = default;//强制生成默认拷贝函数
-
- BSTree(const BSTree
& t)//拷贝构造函数 - {
- _root = Copy(t._root);
- }
-
- ~BSTree()//析构
- {
- Destory(_root);
- }
-
- bool Insert(const K& key)//插入数据
- {
- if (_root == nullptr)//如果根节点为空就直接插入
- {
- _root = new Node(key);
- _root->_key = key;
- return true;
- }
- Node* cur = _root;//使用cur遍历二叉树找到合适的插入位置
- Node* parent = cur;//记录cur的父节点
- while (cur)
- {
- parent = cur;
- if (key < cur->_key)
- {
- cur = cur->_lchild;
- }
- else if (key > cur->_key)
- {
- cur = cur->_rchild;
- }
- else
- {
- return false;//这里创建的二叉搜索树不允许出现值的冗余
- }
- }
- cur = new Node(key);//创建
- //将创建的节点链接到二叉树上
- if (key < parent->_key)
- {
- parent->_lchild = cur;
- }
- else
- {
- parent->_rchild = cur;
- }
- return true;
- }
-
- bool InsertR(const K& key)//插入数据(递归)
- {
- return _InsertR(_root, key);
- }
-
- void InOrder()//中序遍历
- {
- _InOrder(_root);
- cout << endl;
- }
-
- bool Find(const K& key)//查找数据
- {
- Node* cur = _root;
- while (cur)
- {
- if (cur->_key < key)
- {
- cur = cur->_rchild;
- }
- else if (cur->_key > key)
- {
- cur = cur->_lchild;
- }
- else
- {
- return true;
- }
- }
- return false;
- }
-
- bool Erase(const K& key)
- {
- Node* cur = _root;//使用cur遍历二叉树找到要删除元素的位置
- Node* parent = cur;//记录cur的父节点
- while (cur)//寻找需要删除的节点
- {
- if (cur->_key < key)
- {
- parent = cur;
- cur = cur->_rchild;
- }
- else if (cur->_key > key)
- {
- parent = cur;
- cur = cur->_lchild;
- }
- else//找到了,开始删除
- {
- if (cur->_lchild == cur->_rchild && cur->_lchild == nullptr)//删除的节点为叶子节点
- {
- if (parent == cur)//删除的是根节点
- {
- _root = nullptr;//更新根节点
- }
- //将其父节点指向的自身的指针置空
- else if (parent->_lchild == cur)
- {
- parent->_lchild = nullptr;
- }
- else
- {
- parent->_rchild = nullptr;
- }
- //释放节点空间
- delete cur;
- cur = nullptr;
- }
- else if (cur->_rchild == nullptr)//删除的节点右孩子为空,左孩子不为空
- {
- if (parent == cur)//删除的是根节点
- {
- _root = cur->_lchild;//更新根节点
- }
- //将删除节点的左孩子交给其父节点
- else if (parent->_lchild == cur)
- {
- parent->_lchild = cur->_lchild;
- }
- else
- {
- parent->_rchild = cur->_lchild;
- }
- //释放节点空间
- delete cur;
- cur = nullptr;
- }
- else if (cur->_lchild == nullptr)//删除的节点左孩子为空,右孩子不为空
- {
- if (parent == cur)//删除的是根节点
- {
- _root = cur->_rchild;//更新根节点
- }
- //将删除节点的右孩子交给其父节点
- else if (parent->_lchild == cur)
- {
- parent->_lchild = cur->_rchild;
- }
- else
- {
- parent->_rchild = cur->_rchild;
- }
- //释放节点空间
- delete cur;
- cur = nullptr;
- }
- else//删除的节点左右孩子都不为空,要找到删除节点的左子树的最大节点或右子树的最小节点可将其替换
- {
- //这里找要删除节点的左子树的最大节点(右子树的最小节点也可)
- Node* maxleft = cur->_lchild; // 记录左子树的最大节点
- Node* pmaxleft = cur;//记录左子树的最大节点的父节点
- while (maxleft->_rchild)//查找左子树的最大节点
- {
- pmaxleft = maxleft;
- maxleft = maxleft->_rchild;
- }
- //将找到的节点替换要删除的节点
- cur->_key = maxleft->_key;
- if (pmaxleft->_lchild == maxleft)
- {
- pmaxleft->_lchild = maxleft->_lchild;
- }
- else
- {
- pmaxleft->_rchild = maxleft->_lchild;
- }
- //释放节点空间
- delete maxleft;
- maxleft = nullptr;
- }
- return true;
- }
- }
- return false;//没找到要删除的节点
- }
-
-
- bool EraseR(const K& key)//递归删除数据
- {
- return _EraseR(_root, key);
- }
-
- BSTree& operator=(BSTree
t)//赋值重载 - {
- std::swap(_root, t._root);
- return *this;
- }
-
- protected:
- Node* Copy(const Node* node)//拷贝
- {
- if (node == nullptr)
- {
- return nullptr;
- }
- Node* newnode = new Node(node->_key);
- newnode->_lchild = Copy(node->_lchild);
- newnode->_rchild = Copy(node->_rchild);
- return newnode;
- }
-
- void Destory(Node*& root)//销毁二叉搜索树
- {
- if (root == nullptr)
- return;
- Destory(root->_lchild);
- Destory(root->_rchild);
- delete root;
- root = nullptr;
- }
-
- bool _InsertR(Node*& root,const K& key)//这里使用指针的引用用来直接修改其父节点指针的指向
- {
- if (root == nullptr)
- {
- root = new Node(key);
- return true;
- }
- if (root->_key > key)
- {
- _InsertR(root->_lchild, key);
- }
- else if (root->_key < key)
- {
- _InsertR(root->_rchild, key);
- }
- else
- {
- return false;
- }
- }
-
- void _InOrder(Node* root)
- {
- if (root == NULL)//如果是空树就直接结束
- {
- return;
- }
- _InOrder(root->_lchild);//先递归遍历其左子树
- cout << root->_key << " ";//再遍历其根节点
- _InOrder(root->_rchild);//最后递归遍历其右子树
- }
-
- bool _EraseR(Node*& root, const K& key)
- {
- if (root == nullptr)
- {
- return false;
- }
- else if (root->_key < key)
- {
- _EraseR(root->_rchild, key);
- }
- else if (root->_key > key)
- {
- _EraseR(root->_lchild, key);
- }
- else
- {
- Node* del = root;
- if (root->_lchild == root->_rchild && root->_lchild == nullptr)//删除的节点为叶子节点
- {
- //释放节点空间
- delete root;
- //将其父节点指向的自身的指针置空
- root = nullptr;
- }
- else if (root->_rchild == nullptr)//删除的节点右孩子为空,左孩子不为空
- {
- //将删除节点的左孩子交给其父节点
- root = root->_lchild;
- //释放节点空间
- delete del;
- del = nullptr;
- }
- else if (root->_lchild == nullptr)//删除的节点左孩子为空,右孩子不为空
- {
- //将删除节点的右孩子交给其父节点
- root = root->_rchild;
- //释放节点空间
- delete del;
- del = nullptr;
- }
- else//删除的节点左右孩子都不为空,要找到删除节点的左子树的最大节点或右子树的最小节点将其替换
- {
- //这里找要删除节点的右子树的最小节点(左子树的最大节点也可)
- Node* minright = del->_rchild; // 记录右子树的最小节点
- while (minright->_lchild)//查找右子树的最小节点
- {
- minright = minright->_lchild;
- }
- root->_key = minright->_key;//将找到的节点替换要删除的节点
- return _EraseR(root->_rchild, root->_key);//继续递归删除其右子树中用来替换的节点
- }
- return true;
- }
- }
-
- private:
- Node* _root = nullptr;//根节点
- };
K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下: 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树,在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
KV模型:每一个关键码key,都有与之对应的值Value,即的键值对。
该种方式在现实生活中十分常见: 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文就构成一种键值对; 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是就构成一种键值对
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二 叉搜索树的深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
所以最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:O(㏒⑵N)
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:O(N)
那如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插 入关键码,二叉搜索树的性能都能达到最优?
那么我们后面就要学习的AVL树和红黑树了。
本期博客到这里就结束了,代码量较大,还请各位仔细分析呀
我们下期见~