【C++进阶】二叉树进阶,2024年最新这可能是目前最全的

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注大数据)
img

正文

		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}
		else
		{
			if (root->_key < key)
			{
				return \_InsertR(root->_right, key);
			}
			else if (root->_key > key)
			{
				return \_InsertR(root->_left, key);
			}
			else
			{
				return false;
			}
		}
	}
	void \_InOrder(Node\* root)
	{
		if (root == nullptr)
		{
			return;
		}
		\_InOrder(root->_left);
		cout << root->_key << " ";
		\_InOrder(root->_right);
	}
private:
	Node\* _root = nullptr;
};

}




---


#### ⭐️ 1.3.2 main.cpp



#include “BinarySearchTree.hpp”

void TestBSTree1()
{
int a[] = { 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 };
KEY::BSTree t;
for (auto e : a)
{
t.Insert(e);
}
t.InOrder();

t.Erase(7);
t.InOrder();

t.Erase(5);
t.InOrder();

}
void TestBSTree2()
{
int a[] = { 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 };
KEY::BSTree t;
for (auto e : a)
{
t.Insert(e);
}
t.InOrder();

//挨个删除
for (auto e : a)
{
    t.Erase(e);
    t.InOrder();
}
//先删除左树,再删除根
/\*t.Erase(0);

t.Erase(1);
t.Erase(2);
t.Erase(3);
t.Erase(4);
t.Erase(5);
t.InOrder();*/
}
void TestBSTree3()
{
int a[] = { 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 };
KEY::BSTree t;
for (auto e : a)
{
t.InsertR(e);
}
t.InOrder();

//挨个删除
for (auto e : a)
{
    t.Erase(e);
    t.InOrder();
}
//先删除左树,再删除根
/\*t.Erase(0);

t.Erase(1);
t.Erase(2);
t.Erase(3);
t.Erase(4);
t.Erase(5);
t.InOrder();*/
}
void TestBSTree4()
{
int a[] = { 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 };
KEY::BSTree t;
for (auto e : a)
{
t.Insert(e);
}
t.InOrder();

KEY::BSTree<int> copy = t;
copy.InOrder();

KEY::BSTree<int> assign;
assign = t;
assign.InOrder();

}
int main()
{
TestBSTree1();
cout << “-------------------” << endl;
TestBSTree2();
cout << “-------------------” << endl;
TestBSTree3();
cout << “-------------------” << endl;
TestBSTree4();

return 0;

}


![请添加图片描述](https://img-blog.csdnimg.cn/675cf37957614e69bd7c7c7175be2c4a.png)




---


### 🌟 1.4 二叉搜索树的应用


1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。


比如:**给一个单词word,判断该单词是否拼写正确**,具体方式如下:


* 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
* 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。


2. **KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对**。该种方式在现实生活中非常常见:


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


#### ⭐️ 1.4.1 \_BinarySearchTree.hpp



#pragma once
#include
#include
using namespace std;

namespace KEY
{
//…
}
namespace KEY_VALUE
{
template<class K, class V>
struct BSTreeNode
{
BSTreeNode<K, V>* _left;
BSTreeNode<K, V>* _right;
K _key;
V _value;

    BSTreeNode(const K& key, const V& value)
        : \_left(nullptr)
        , \_right(nullptr)
        , \_key(key)
        , \_value(value)
    {}
};
template<class K, class V>
class BSTree
{
    typedef BSTreeNode<K, V> Node;
public:
    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)
        {
            if (cur->_key < key)//往右子树查找
            {
                parent = cur;
                cur = cur->_right;
            }
            else if (cur->_key > key)//往左子树查找
            {
                parent = cur;
                cur = cur->_left;
            }
            else
            {
                return false;//默认不支持冗余
            }
        }
        //new节点,这里需要在BSTreeNode补构造函数
        cur = new Node(key, value);
        if (parent->_key < cur->_key)//新节点链接到父的左还是右,还需要再比一次
        {
            parent->_right = cur;//关联
        }
        else
        {
            parent->_left = cur;//关联
        }
        return true;
    }
    Node\* Find(const K& key)
    {
        Node\* cur = _root;
        while (cur)
        {
            if (cur->_key < key)
            {
                cur = cur->_right;
            }
            else if (cur->_key > key)
            {
                cur = cur->_left;
            }
            else
            {
                return cur;
            }
        }
        return nullptr;
    }
    bool Erase(const K& key)
    {
        Node\* parent = nullptr;
        Node\* cur = _root;
        while (cur)
        {
            if (cur->_key < key)
            {
                parent = cur;
                cur = cur->_right;
            }
            else if (cur->_key > key)
            {
                parent = cur;
                cur = cur->_left;
            }
            else
            {
                //删除
                if (cur->_left == nullptr)//ab
                {
                    //没有父亲
                    if (cur == _root)
                    {
                        _root = cur->_right;//右作根
                    }
                    else
                    {
                        //确定目标位置父亲的左还是右和目标位置的孩子关联(目标位置的左右孩子在外层if已经确定了)
                        if (cur == parent->_left)
                        {
                            parent->_left = cur->_right;
                        }
                        else
                        {
                            parent->_right = cur->_right;
                        }
                    }
                    delete cur;
                }
                else if (cur->_right == nullptr)//ac
                {
                    //没有父亲
                    if (cur == _root)
                    {
                        _root = cur->_left;
                    }
                    else
                    {
                        //确定目标位置父亲的左还是右和目标位置的孩子关联(目标位置的左右孩子在外层if已经确定了)
                        if (cur == parent->_left)
                        {
                            parent->_left = cur->_left;
                        }
                        else
                        {
                            parent->_right = cur->_left;
                        }
                    }
                    delete cur;
                }
                else//d
                {
                    //找右树的最小节点去替代删除
                    Node\* minRightParent = cur;
                    //cur->right一定不为空
                    Node\* minRight = cur->_right;
                    //最小节点
                    while (minRight->_left)
                    {
                        minRightParent = minRight;
                        minRight = minRight->_left;
                    }
                    //替代
                    cur->_key = minRight->_key;
                    //minRight的左一定为空,但右不一定为空,minRightParent->\_left不一定是minRight
                    if (minRight == minRightParent->_left)//删除5,找6
                    {
                        minRightParent->_left = minRight->_right;
                    }
                    else//删除7,找8
                    {
                        minRightParent->_right = minRight->_right;
                    }
                    delete minRight;
                }
                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;
};

}




---


#### ⭐️ 1.4.2 main.cpp



#include"_BinarySearchTree.hpp"

void TestBSTree1()
{
KEY_VALUE::BSTree<string, string> dict;
dict.Insert(“sort”, “排序”);
dict.Insert(“insert”, “插入”);
dict.Insert(“tree”, “树”);
dict.Insert(“left”, “左边”);
dict.Insert(“right”, “右边”);
//…
string str;
while(cin >> str)
{
if(str == “Q”)
{
break;
}
else
{
auto ret = dict.Find(str);
if(ret == nullptr)
{
cout << “拼写错误,请检查你的单词” << endl;
}
else
{
cout << ret->_key << “->” << ret->_value << endl;
}
}
}
}
void TestBSTree2()
{
string str[] = {“sort”, “sort”, “tree”, “insert”, “sort”, “tree”, “sort”, “test”, “sort” };
KEY_VALUE::BSTree<string, int> countTree;
for(auto& e : str)
{
auto ret = countTree.Find(e);
if(ret == nullptr)
{
countTree.Insert(e, 1);//第一次出现
}
else
{
ret->_value++;//非第一次出现
}
}
countTree.InOrder();
}
int main()
{
TestBSTree1();
TestBSTree2();

return 0;

}


![请添加图片描述](https://img-blog.csdnimg.cn/ca2927a011cf4a2f9ecc8d3f471e83c5.png)




---


### 🌟 1.5 二叉搜索树的性能分析


插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。


对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。


但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:


![在这里插入图片描述](https://img-blog.csdnimg.cn/fbb1620767ba4227bbaecfe527386fb5.png)


* 最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树)
* 最差情况下,二叉搜索树退化为单支树(或者类似单支)


问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?那么我们学习的AVL树和红黑树就可以上场了。




---


## 💫 2. 二叉树OJ题


### ✨ 第一题


链接:[根据二叉树创建字符串]( )


![在这里插入图片描述](https://img-blog.csdnimg.cn/750cb60584344fb6a0f6b868bc0f3b68.png#pic_center)  
 ![请添加图片描述](https://img-blog.csdnimg.cn/2c8f7a2f1d124ddaafdc690dd8754bb1.png)


**解题思路**


我们可以使用递归的方法得到二叉树的前序遍历,并在递归时加上额外的括号。


会有以下 4 种情况:


* 如果当前节点有两个孩子,那我们在递归时,需要在两个孩子的结果外都加上一层括号;
* 如果当前节点没有孩子,那我们不需要在节点后面加上任何括号;


![请添加图片描述](https://img-blog.csdnimg.cn/2f6d9a748c6441c0821b47a8ba908a0f.png)  
 如果当前节点只有左孩子,那我们在递归时,只需要在左孩子的结果外加上一层括号,而不需要给右孩子加上任何括号;


![请添加图片描述](https://img-blog.csdnimg.cn/5761c125ac3149fdac174e0a99628ed5.png)


如果当前节点只有右孩子,那我们在递归时,需要先加上一层空的括号 ‘()’\text{`()'}‘()’ 表示左孩子为空,再对右孩子进行递归,并在结果外加上一层括号。


![请添加图片描述](https://img-blog.csdnimg.cn/06a77a06a65d49d29117fe1fabc1f3a5.png)




---


**代码演示**



class Solution {
public:
string tree2str(TreeNode* root) {
if(root == nullptr)
return “”;

    if(root -> left == nullptr && root -> right == nullptr)
        return to\_string(root -> val);

    if(root -> right == nullptr)
        return to\_string(root -> val) + '(' + tree2str(root -> left) + ')';

    return to\_string(root -> val) + '(' + tree2str(root -> left) + ')' + '(' + tree2str(root -> right) + ')';
}

};




---


### ✨ 第二题


链接:[二叉树的层序遍历1]( )


![在这里插入图片描述](https://img-blog.csdnimg.cn/fa66c45418c64fdb924aa30f0cf35b8e.png#pic_center)


**解题思路**


按层打印: 题目要求的二叉树的 从上至下 打印(即按层打印),又称为二叉树的 广度优先搜索(BFS)。BFS 通常借助 队列 的先入先出特性来实现。 II. 每层打印到一行: 将本层全部节点打印到一行,并将下一层全部节点加入队列,以此类推,即可分为多行打印。


算法流程


* 特例处理: 当根节点为空,则返回空列表 [] ;
* 初始化: 打印结果列表 res = [] ,包含根节点的队列 queue = [root] ;
* BFS 循环: 当队列 queue 为空时跳出;
* 大小:计算队列的大小也就是当前根结点有多少孩子结点;
* 新建一个临时列表 tmp ,用于存储当前层打印结果;
* 当前层打印循环: 循环次数为当前层节点数(即队列 queue 长度);
* 出队: 队首元素出队,记为 node;
* 打印: 将 node.val 添加至 tmp 尾部;
* 添加子节点: 若 node 的左(右)子节点不为空,则将左(右)子节点加入队列 queue ;
* 将当前层结果 tmp 添加入 res 。
* 返回值: 返回打印结果列表 res 即可。


**代码演示:**



class Solution {
public:
vector<vector> levelOrder(TreeNode* root) {
vector<vector> res;
queue<TreeNode*> q;

    if(root != nullptr)
        q.push(root);

    //开始遍历
    while(!q.empty())
    {
        int size = q.size();
        vector<int> vec;

        for(int i = 0;i < size;i++)
        {
            TreeNode\* node = q.front();//第一个节点
            q.pop();

            vec.push\_back(node -> val);
            if(node -> left != nullptr)
                q.push(node -> left);
            if(node -> right != nullptr)
                q.push(node -> right);
        }
        //保存数据
        res.push\_back(vec);
    }
    return res;
}

};




---


### ✨ 第三题


链接:[二叉树的层序遍历2]( )


![请添加图片描述](https://img-blog.csdnimg.cn/c4c93d9a31bf493da2e1b81ec4cbc631.png)


**解题思路**


很简单,正着遍历一遍然后reveres反过来就可以了


**代码演示**



class Solution {
public:
vector<vector> levelOrderBottom(TreeNode* root) {
vector<vector> res;
queue<TreeNode*> q;

    if(root != nullptr)
        q.push(root);

    //开始遍历
    while(!q.empty())
    {
        int size = q.size();
        vector<int> vec;

        for(int i = 0;i < size;i++)
        {
            TreeNode\* node = q.front();//第一个节点
            q.pop();

            vec.push\_back(node -> val);
            if(node -> left != nullptr)
                q.push(node -> left);
            if(node -> right != nullptr)
                q.push(node -> right);
        }
        //保存数据
        res.push\_back(vec);
    }
    reverse(res.begin(),res.end());//反转

    return res;
}

};




---


### ✨ 第四题


链接:[二叉树最近的公共祖先]( )


![请添加图片描述](https://img-blog.csdnimg.cn/ddc663fda5254d519aff30ccd6c1f586.png)


![请添加图片描述](https://img-blog.csdnimg.cn/d0efee5e7e254a398a324cc5d7e3e5af.png)


**解题思路**


根据定义,若 rootrootroot 是 p,qp, qp,q 的 最近公共祖先 ,则只可能为以下情况之一:


* p 和 q 在 roott 的子树中,且分列 root 的 异侧(即分别在左、右子树中);
* p=root ,且 q 在 root 的左或右子树中;
* q=root ,且 p 在 root 的左或右子树中;


**代码演示**



class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root == nullptr || root == p || root == q)
return root;

    TreeNode \*left = lowestCommonAncestor(root -> left ,p ,q);
    TreeNode \*right = lowestCommonAncestor(root -> right ,p ,q);
    
    if(left == nullptr)
        return right;
    if(right == nullptr)
        return left;
    return root;
}

};




---


### ✨ 第五题


链接:[二叉搜索树和双向链表]( )


![请添加图片描述](https://img-blog.csdnimg.cn/f68f073da2cb44bdab2f9174584d3690.png)


![请添加图片描述](https://img-blog.csdnimg.cn/d245619237dd403b96577ac338388067.png)


**解题思路**


二叉搜索树最左端的元素一定最小,最右端的元素一定最大,符合“左中右”的特性,因此二叉搜索树的中序遍历就是一个递增序列,我们只要对它中序遍历就可以组装称为递增双向链表。


**代码演示**



class Solution {
public:
TreeNode* head = nullptr;
TreeNode* prev = nullptr;
TreeNode* Convert(TreeNode* pRootOfTree) {
if(pRootOfTree == nullptr)
return nullptr;
Convert(pRootOfTree -> left);

	if(prev == nullptr)
	{
		head = pRootOfTree;
		prev = pRootOfTree;
	}
	else
	{
		prev -> right = pRootOfTree;
		pRootOfTree -> left = prev;
		prev = pRootOfTree;
	}
	Convert(pRootOfTree -> right);
	return head;
}

};




---


### ✨ 第六题


链接:[从前序与中序遍历序列构造二叉树]( )


![请添加图片描述](https://img-blog.csdnimg.cn/103f20773c824ce1a90481efdaa50ae1.png)


**解题思路:**


只要我们在中序遍历中定位到根节点,那么我们就可以分别知道左子树和右子树中的节点数目。由于同一颗子树的前序遍历和中序遍历的长度显然是相同的,因此我们就可以对应到前序遍历的结果中,对上述形式中的所有左右括号进行定位。


这样以来,我们就知道了左子树的前序遍历和中序遍历结果,以及右子树的前序遍历和中序遍历结果,我们就可以递归地对构造出左子树和右子树,再将这两颗子树接到根节点的左右位置。


**代码演示:**



class Solution {
public:
TreeNode* buildTree(vector& preorder, vector& inorder) {
// 若节点个数等于0
if(preorder.size() == 0){
return nullptr;
}
// 先序遍历的第一个节点 preorder[0] 为 根节点 root
TreeNode* root = new TreeNode(preorder[0]);
stack<TreeNode*> s;
// 将根节点入栈
s.push(root);
// 初始化扫描中序遍历的指针
int idx = 0;
for(int i = 1; i < preorder.size(); i++){
TreeNode* node = s.top();
// 若 栈顶元素的值 与 中序遍历当前所指的值 不相同
// 表明当前节点存在子树
if(node->val != inorder[idx]){
node->left = new TreeNode(preorder[i]);
s.push(node->left);
}
// 若 栈顶元素的值 与 中序遍历当前所指的值 相同
// 表明当前节点不存在子树,即为最左下角的节点
else{
// 当 栈非空 且 栈顶元素的值 与 中序遍历当前所指的值 相同
while(!s.empty() && s.top()->val == inorder[idx]){
// 指针向右扫描中序遍历
node = s.top();
// 弹出栈中所有与当前指针所指元素值相同的节点
s.pop();
// 中序遍历指针向右移动
idx++;
}
// while循环结束后,当前node所指向的节点就是需要重建右子树的节点
node->right = new TreeNode(preorder[i]);
s.push(node->right);
}
}
return root;
}
};




---


### ✨ 第七题


链接:[从中序与后序遍历构造二叉树]( )


![请添加图片描述](https://img-blog.csdnimg.cn/9f1dd187a88e48358c026da871162270.png)


**解题思路**


首先解决这道题我们需要明确给定一棵二叉树,我们是如何对其进行中序遍历与后序遍历的:


中序遍历的顺序是每次遍历左孩子,再遍历根节点,最后遍历右孩子。  
 后序遍历的顺序是每次遍历左孩子,再遍历右孩子,最后遍历根节点。


**代码演示**



class Solution {
int post_idx;
unordered_map<int, int> idx_map;
public:
TreeNode* helper(int in_left, int in_right, vector& inorder, vector& postorder){
// 如果这里没有节点构造二叉树了,就结束
if (in_left > in_right) {
return nullptr;
}

    // 选择 post\_idx 位置的元素作为当前子树根节点
    int root_val = postorder[post_idx];
    TreeNode\* root = new TreeNode(root_val);

    // 根据 root 所在位置分成左右两棵子树
    int index = idx_map[root_val];

    // 下标减一
    post_idx--;
    // 构造右子树
    root->right = helper(index + 1, in_right, inorder, postorder);
    // 构造左子树
    root->left = helper(in_left, index - 1, inorder, postorder);
    return root;
}
TreeNode\* buildTree(vector<int>& inorder, vector<int>& postorder) {
    // 从后序遍历的最后一个元素开始
    post_idx = (int)postorder.size() - 1;

    // 建立(元素,下标)键值对的哈希表
    int idx = 0;
    for (auto& val : inorder) {
        idx_map[val] = idx++;
    }
    return helper(0, (int)inorder.size() - 1, inorder, postorder);
}

};




---


### ✨ 第八题


链接:[二叉树的前序遍历]( )


![请添加图片描述](https://img-blog.csdnimg.cn/5a57bb7ef52c41a5a0d33e7decc6fddb.png)




**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)**
![img](https://img-blog.csdnimg.cn/img_convert/3e4ffa0787c9bab1fc131e33fc1964fb.png)

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

    post_idx--;
        // 构造右子树
        root->right = helper(index + 1, in_right, inorder, postorder);
        // 构造左子树
        root->left = helper(in_left, index - 1, inorder, postorder);
        return root;
    }
    TreeNode\* buildTree(vector<int>& inorder, vector<int>& postorder) {
        // 从后序遍历的最后一个元素开始
        post_idx = (int)postorder.size() - 1;

        // 建立(元素,下标)键值对的哈希表
        int idx = 0;
        for (auto& val : inorder) {
            idx_map[val] = idx++;
        }
        return helper(0, (int)inorder.size() - 1, inorder, postorder);
    }
};



✨ 第八题

链接:二叉树的前序遍历

请添加图片描述

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
[外链图片转存中…(img-qlpfDOgH-1713339430861)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值