🏆个人主页:企鹅不叫的博客
? 🌈专栏
?? 博主码云gitee链接:代码仓库地址
?若有帮助可以【关注+点赞+收藏】,大家一起进步!
💙系列文章💙
【初阶与进阶C++详解】第一篇:C++入门知识必备
【初阶与进阶C++详解】第二篇:C&&C++互相调用(创建静态库)并保护加密源文件
【初阶与进阶C++详解】第三篇:类和对象上(类和this指针)
【初阶与进阶C++详解】第四篇:类和对象中(类的六个默认成员函数)
【初阶与进阶C++详解】第五篇:类和对象下(构造+static+友元+内部类
【初阶与进阶C++详解】第六篇:C&C++内存管理(动态内存分布+内存管理+new&delete)
【初阶与进阶C++详解】第七篇:模板初阶(泛型编程+函数模板+类模板+模板特化+模板分离编译)
【初阶与进阶C++详解】第八篇:string类(标准库string类+string类模拟实现)
【初阶与进阶C++详解】第九篇:vector(vector接口介绍+vector模拟实现+vector迭代器区间构造/拷贝构造/赋值)
【初阶与进阶C++详解】第十篇:list(list接口介绍和使用+list模拟实现+反向迭代器和迭代器适配)
【初阶与进阶C++详解】第十一篇:stack+queue+priority_queue+deque(仿函数)
【初阶与进阶C++详解】第十二篇:模板进阶(函数模板特化+类模板特化+模板分离编译)
【初阶与进阶C++详解】第十三篇:继承(菱形继承+菱形虚拟继承+组合)
【初阶与进阶C++详解】第十四篇:多态(虚函数+重写(覆盖)+抽象类+单继承和多继承)
💎一、二叉搜索树概念
二叉搜索树中序遍历是有序的
💎二、二叉搜索树操作实现
🏆1.基本框架
构建一个树的节点结构
template<class K>
struct BSTreeNode
{
public:
BSTreeNode* _left;
BSTreeNode* _right;
K _key;
BSTreeNode(K key)
:_left(nullptr),
_right(nullptr),
_key(key)
{}
};
定义一个根节点,默认值是空
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
private:
Node* _root=nullptr;
};
🏆2.插入
步骤
-
首先判断root是否为空,如果为空直接插入数据即可,然后结束 -
如果root非空则定义==cur(当前节点)和parent(父亲节点)==两个指针遍历,要插入的值比当前节点值小,cur则往左走,要插入的值比当前节点大,cur则往右走,如果等于就返回flase,表示树已经存在这个数据,不用插入,找到正确位置后创建新节点插入即可 -
返回值使用bool用来判断是否成功插入 bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(key);
if (key > parent->_key) {
parent->_right = cur;
}
else {
parent->_left = cur;
}
return true;
}
递归版本: bool InsertR(const K& key)
{
return _InsertR(_root, key);
}
bool _InsertR(Node*& root, const K& key)
{
if (root == nullptr) {
Node* newNode = new Node(key);
root = newNode;
return true;
}
if (key > root->_key) {
_InsertR(root->_right, key);
}
else if (key < root->_key) {
_InsertR(root->_left, key);
}
else {
return false;
}
}
🏆3.中序遍历打印
在类外面无法访问到私有成员root ,无法直接给该函数传参,可以把这个函数定义为private 成员
void InOrder()
{
_InOrder(_root);
}
void _InOrder(Node* root)
{
if (root == nullptr)
return ;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
🏆4.查找
步骤:
-
如果查找值key比当前节点的值小,就往左子树走 -
如果查找值key比当前节点的值大,就往右子树走 -
如果查找值key和当前节点的值相等,就返回当前节点的指针
bool Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (key > cur->_key)
{
cur = cur->_right;
}
else if (key < cur->_key)
{
cur = cur->_left;
}
else
{
return true;
}
}
return false;
}
递归版本: bool FindR(const K& key)
{
return _FindR(_root, key);
}
bool _FindR(Node* root, const K& key)
{
if (root == nullptr) {
return false;
}
if (key > root->_key) {
_FindR(root->_right, key);
}
else if (key < root->_key) {
_FindR(root->_left, key);
}
else {
return true;
}
}
🏆5.删除(重点)
情况:
- 要删除的节点无孩子节点,比如删除节点1时,直接删除
- 要删除的节点只有左孩子节点,比如节点2,删除节点2的左孩子并且指向左孩子的右边
- 要删除的节点只有右孩子节点,比如节点7,删除节点7的右孩子并且指向右孩子的做左边
- 要删除的节点有左孩子和右孩子,比如节点3,在右子树中找到最小/在左子树中找到最大,将它的值填补到被删除的节点中
bool Erase(const K& key)
{
if (_root == nullptr)
return false;
Node* prev = nullptr;
Node* cur = _root;
while (cur)
{
if (key > cur->_key)
{
prev = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
prev = cur;
cur = cur->_left;
}
else
{
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = _root->_right;
}
else
{
if (prev->_left == cur)
{
prev->_left = cur->_right;
}
else {
prev->_right = cur->_right;
}
}
delete cur;
}
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = _root->_left;
}
else
{
if (prev->_left == cur)
{
prev->_left = cur->_left;
}
else {
prev->_right = cur->_left;
}
}
delete cur;
}
else
{
Node* minParent = cur;
Node* minRight = cur->_right;
while (minRight->_left)
{
minParent = minRight;
minRight = minRight->_left;
}
swap(minRight->_key, cur->_key);
if (minParent->_left == minRight)
{
minParent->_left = minRight->_right;
}
else
{
minParent->_right = minRight->_right;
}
delete minRight;
}
return true;
}
}
return false;
}
递归版本:
bool _EraseR(Node*& root, const K& key)
{
if (root == nullptr) {
return false;
}
if (key > root->_key) {
_EraseR(root->_right, key);
}
else if (key < root->_key) {
_EraseR(root->_left, key);
}
else
{
Node* del = root;
if (root->_left == nullptr)
{
root = root->_right;
}
else if (root->_right == nullptr)
{
root = root->_left;
}
else
{
Node* minRight = root->_right;
while (minRight->_left)
{
minRight = minRight->_left;
}
swap(minRight->_key, root->_key);
return _EraseR(root->_right, key);
}
delete del;
return true;
}
}
💎三、二叉搜索树应用
-
K模型: K模型只有key值,节点只存储key值。这里主要应用就是查找判断某个元素在不在。 -
KV模型: KV模型每个key值都对应着一个value,主要应用就是通过key找value,将<key, value>绑定
-
排序依据key来排序,而不是value -
key不可以修改,但是value可以修改 -
在保存键值关系的同时,去重+排序 下面是修改代码(只用修改查找和插入即可),和具体应用 template <class K, class V>
struct BSTNode
{
BSTNode<K, V>* _left;
BSTNode<K, V>* _right;
K _key;
V _value;
BSTNode(const K& key, const V& value)
:_left(nullptr)
, _right(nullptr)
, _key(key)
,_value(value)
{}
};
template <class K, class V>
class BSTree
{
typedef BSTNode<K, V> Node;
public:
~BSTree()
{
Node* cur = _root;
while (cur)
{
Erase(cur->_key);
cur = _root;
}
}
Node* Find(const K& key)
{
if (_root == nullptr)
return nullptr;
Node* cur = _root;
while (cur)
{
if (key < cur->_key)
cur = cur->_left;
else if (key > cur->_key)
cur = cur->_right;
else
return cur;
}
return nullptr;
}
bool Insert(const K& key, const V& value)
{
if (_root == nullptr)
{
_root = new Node(key, value);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
parent = cur;
if (key < cur->_key)
cur = cur->_left;
else if (key > cur->_key)
cur = cur->_right;
else
return false;
}
cur = new Node(key, value);
if (cur->_key < parent->_key)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
return true;
}
bool Erase(const K& key)
{
if (_root == nullptr)
return false;
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else
{
if (cur->_left == nullptr)
{
if (_root == cur)
{
_root = _root->_right;
}
else
{
if (parent->_left == cur)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
}
delete cur;
cur = nullptr;
}
else if (cur->_right == nullptr)
{
if (_root == cur)
{
_root = _root->_left;
}
else
{
if (parent->_left == cur)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
}
delete cur;
cur = nullptr;
}
else
{
Node* rightMinParent = cur;
Node* rightMin = cur->_right;
while (rightMin->_left)
{
rightMinParent = rightMin;
rightMin = rightMin->_left;
}
cur->_key = rightMin->_key;
if (rightMinParent->_left == rightMin)
rightMinParent->_left = rightMin->_right;
else
rightMinParent->_right = rightMin->_right;
delete rightMin;
rightMin = nullptr;
}
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 << endl;
_InOrder(root->_right);
}
private:
Node* _root = nullptr;
};
void TestBSTree_KV1()
{
BSTree<string, string> dict;
dict.Insert("苹果", "apple");
dict.Insert("排序", "sort");
dict.Insert("培养", "cultivate");
dict.Insert("通过", "pass");
dict.Insert("apple", "苹果");
dict.Insert("sort", "排序");
dict.Insert("cultivate", "培养");
dict.Insert("pass", "通过");
string str;
while (cin >> str)
{
aotu ret = dict.Find(str);
if (ret)
{
cout << ret->_value << endl;
}
else
{
cout << "本字典无此词" << endl;
}
}
void TestBSTree_KV2()
{
BSTree<string, int> countTree;
string strArr[] = { "香蕉","水蜜桃","西瓜","苹果","香蕉" ,"西瓜","香蕉" ,"苹果","西瓜","苹果","苹果","香蕉" ,"水蜜桃" };
for (auto e : strArr)
{
BSTNode<string, int>* ret = countTree.Find(e);
if (ret == nullptr)
{
countTree.Insert(e, 1);
}
else
{
ret->_value++;
}
}
countTree.InOrder();
}
💎四、二叉搜索树新能分析
理想情况情况(完全二叉树),二叉搜索树的插入和删除的效率都是O(logN),极端情况(一条链)会导致效率变成O(N)。
💎五、面试题
🏆1.根据二叉树创建字符串
传送门
输入:root = [1,2,3,4] 输出:“1(2(4))(3)” 解释:初步转化后得到 “1(2(4)())(3()())” ,但省略所有不必要的空括号对后,字符串应该是"1(2(4))(3)" 。
思路:
? 最后是否加括号有三种情况,左右两边都是空,那么不要加括号,左边是空右边不是空,保留括号,右边是空左边不是空,不要加括号,然后递归循环即可
class Solution {
public:
string tree2str(TreeNode* root) {
if(root == nullptr)
return "";
string str;
str += to_string(root->val);
if(root->left)
{
str += '(';
str += tree2str(root->left);
str += ')';
}
else if(root->right)
{
str += "()";
}
if(root->right)
{
str += '(';
str += tree2str(root->right);
str += ')';
}
return str;
}
};
🏆2.二叉树的层序遍历
传送门
输入:root = [3,9,20,null,null,15,7] 输出:[[3],[9,20],[15,7]]
思路:
? 利用变量levelsize控制一层变量,然后将每一层变量放到队列q中,如果q为空则表示遍历完了
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> vv;
if(root == nullptr)
return vv;
queue<TreeNode*> q;
int levelsize = 1;
q.push(root);
while(!q.empty())
{
vector<int> v;
while(levelsize--)
{
TreeNode* front = q.front();
q.pop();
v.push_back(front->val);
if(front->left)
q.push(front->left);
if(front->right)
q.push(front->right);
}
vv.push_back(v);
levelsize = q.size();
}
return vv;
}
};
🏆3.二叉树的层序遍历 II
传送门
输入:root = [3,9,20,null,null,15,7] 输出:[[15,7],[9,20],[3]]
思路同第二题,最后逆置即可
class Solution {
public:
vector<vector<int>> levelOrderBottom(TreeNode* root) {
vector<vector<int>> vv;
if(root == nullptr)
return vv;
queue<TreeNode*> q;
int levelsize = 1;
q.push(root);
while(!q.empty())
{
vector<int> v;
while(levelsize--)
{
TreeNode* front = q.front();
q.pop();
v.push_back(front->val);
if(front->left)
q.push(front->left);
if(front->right)
q.push(front->right);
}
vv.push_back(v);
levelsize = q.size();
}
reverse(vv.begin(), vv.end());
return vv;
}
};
🏆4.二叉树的最近公共祖先
传送门
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 输出:3 解释:节点 5 和节点 1 的最近公共祖先是节点 3 。
思路:
? 当一个节点是祖先的时候,一种情况是,pq两个点都在祖先的左右两边,另一种情况是pq两点中有一个点是祖先,如果pq两点中有一个是祖先的话,那么返回root即可,如果当前root节点不是pq的话,那么就从root的左右子树中找pq,此时我们定义一个子树中寻找节点的函数,用来寻找root子树中是否存在pq,如果pq存在root左右两边的话,那么返回root祖先,如果pq存在root同一边的话,那么将root移动到一边寻找即可
class Solution {
public:
bool IsInBinry(TreeNode* root, TreeNode* x)
{
if(root == nullptr)
return false;
if(root == x)
return true;
return IsInBinry(root->left, x)
||IsInBinry(root->right, x);
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root == nullptr)
return root;
if(p == root || q == root)
return root;
bool qInleft = IsInBinry(root->left, q);
bool qInright = IsInBinry(root->right, q);
bool pInleft = IsInBinry(root->left, p);
bool pInright = IsInBinry(root->right, p);;
if((qInleft && pInright)||(qInright && pInleft))
return root;
else if(qInleft && pInleft)
return lowestCommonAncestor(root->left, p, q);
else
return lowestCommonAncestor(root->right, p, q);
}
};
🏆5.二叉搜索树与双向链表
传送门
思路:
? 通过中序遍历,将节点左子树的右边指向节点,利用一前一后指针将每一个系欸但双向
class Solution {
public:
void InOrder(Node* cur, Node*& prev)
{
if(cur == nullptr) return;
InOrder(cur->left, prev);
cur->left = prev;
if(prev)
prev->right = cur;
prev = cur;
InOrder(cur->right, prev);
}
Node* treeToDoublyList(Node* root) {
if(root == nullptr)
return nullptr;
Node* prev = nullptr;
InOrder(root, prev);
Node* head = root;
while(head->left)
{
head = head->left;
}
return head;
}
};
🏆6.从前序与中序遍历序列构造二叉树
传送门
输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7] 输出: [3,9,20,null,null,15,7]
思路:
? 首先通过前序遍历找到root然后通过中序遍历确定root位置,那么根据中序遍历root位置可知,在root左边是左树,右边是右树,将中序区间划分为[begini, rooti-1]rooti[rooti+1, endi],然后将root的左右树分别递归划分
class Solution {
public:
TreeNode* _buildTree(vector<int>& preorder, vector<int>& inorder, int& prei, int begini, int endi)
{
if(begini>endi)
{
return nullptr;
}
TreeNode* root = new TreeNode(preorder[prei]);
++prei;
int rooti = begini;
while(rooti<=endi)
{
if(root->val == inorder[rooti])
break;
else
rooti++;
}
root->left = _buildTree(preorder, inorder, prei, begini, rooti-1);
root->right = _buildTree(preorder, inorder, prei, rooti+1, endi);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int prei = 0, begini = 0, endi = inorder.size()-1;
return _buildTree(preorder, inorder, prei, begini, endi);
}
};
🏆7.从中序与后序遍历序列构造二叉树
传送门
思路:
思路和前序中序遍历差不多,首先从后序遍历的最后开始,找到根节点,然后从中序遍历中找到[begini, rooti-1]rooti[rooti+1, endi]区间,然后将每个区间递归遍历,注意的是,递归顺序是后序中序遍历时,递归是先右再左
class Solution {
public:
TreeNode* _buildTree(vector<int>& inorder, vector<int>& postorder, int& nexti, int begini, int endi)
{
if(begini>endi)
{
return nullptr;
}
TreeNode* root = new TreeNode(postorder[nexti]);
nexti--;
int rooti = begini;
while(rooti<endi)
{
if(root->val == inorder[rooti])
break;
else
rooti++;
}
root->right = _buildTree(inorder, postorder, nexti, rooti+1, endi);
root->left = _buildTree(inorder, postorder, nexti, begini, rooti-1);
return root;
}
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
int nexti = postorder.size()-1, begini = 0, endi = inorder.size()-1;
return _buildTree(inorder, postorder, nexti, begini, endi);
}
};
🏆8.二叉树的前序遍历
传送门
输入:root = [1,null,2,3] 输出:[1,2,3]
思路:
? 前序遍历是根左右,所以我们要优先·访问根节点,然后递归访问左节点和右节点,其他中序遍历和后续遍历差不多
class Solution {
public:
void _preorderTraversal(TreeNode* root, vector<int>& v)
{
if(root == nullptr)return ;
v.push_back(root->val);
_preorderTraversal(root->left, v);
_preorderTraversal(root->right, v);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> v;
_preorderTraversal(root, v);
return v;
}
};
|