目录
1. 二叉树搜索树应用分析
1.1. K模型 && KV模型
二叉搜索树的应用:
- K模型 --- 在不在的问题, K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值,解决的是在不在的问题。
- 比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树 ,在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。 还有我们日常生活中的门禁系统、车库系统等等。
- KV模型 --- 通过一个值查找另一个值,每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:
- 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;
- 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。
KV模型,假如我现在要实现简易版的英汉词典,通过英文单词,查询对应的中文,这时候我们的搜索二叉树的每个节点就是<key,value>的键值对,通过key查找我们的value:
#include <iostream>
#include <string>
namespace Xq
{
template<class T, class K>
struct BinarySearchTreeNode
{
BinarySearchTreeNode<T, K>* _left;
BinarySearchTreeNode<T, K>* _right;
T _key;
K _value;
BinarySearchTreeNode(const T& key, const K& value) :_key(key),_value(value), _left(nullptr), _right(nullptr) {}
};
template<class T, class K>
class BinarySearchTree
{
private:
typedef BinarySearchTreeNode<T, K> Node;
public:
BinarySearchTree(Node* root = nullptr) :_root(root) {}
bool insert(const T& key, const K& value)
{
// 1. 如果是空树,直接对_root赋值即可,插入成功并返回true
if (_root == nullptr)
{
_root = new Node(key, value);
return true;
}
else
{
// step 1: 先找目标位置
Node* cur = _root;
// 为了更好的连接新节点, 因此记录父节点
Node* parent = nullptr;
while (cur)
{
// 如果当前节点的Key大于目标Key
// 当前节点应该向左子树走
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
// 如果当前节点的Key小于目标Key
// 当前节点应该向右子树走
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else
{
// 找到了相同的 key, 在这里不插入
return false;
}
}
// cur 走到了空, 即 cur 就是合适的位置
cur = new Node(key, value);
// 我们需要判断cur是parent的左节点还是右节点
// 如果key小于parent的key,那么插入左节点
if (key < parent->_key)
parent->_left = cur;
// 反之连接到右节点
else
parent->_right = cur;
return true;
}
}
Node* find(const T& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key > key)
cur = cur->_left;
else if (cur->_key < key)
cur = cur->_right;
else
return cur;
}
return nullptr;
}
bool erase(const T& key)
{
// 先找要删除的节点
Node* del = _root;
Node* del_parent = nullptr;
while (del)
{
if (del->_key < key)
{
del_parent = del;
del = del->_right;
}
else if (del->_key > key)
{
del_parent = del;
del = del->_left;
}
else
{
// 锁定了要删除的节点
// 分三种情况:
// case 1: 左子树为空
if (del->_left == nullptr)
{
// 如果要删除的节点是根
if (del == _root)
{
Node* newroot = del->_right;
delete _root;
_root = newroot;
}
else
{
// 托孤法删除
if (del_parent->_left == del)
del_parent->_left = del->_right;
else
del_parent->_right = del->_right;
delete del;
del = nullptr;
}
}
// case 2: 右子树为空
else if (del->_right == nullptr)
{
if (_root == del)
{
Node* newroot = del->_left;
delete _root;
_root = newroot;
}
else
{
if (del_parent->_left == del)
del_parent->_left = del->_left;
else
del_parent->_right = del->_left;
delete del;
del = nullptr;
}
}
// case 3: 左右子树都不为空
else
{
// 从被删除节点开始, 找右子树的最小(左)节点 || 找左子树的最大(右)节点
if (del->_right)
_erase_right_min_node(del);
else
_erase_left_max_node(del);
}
return true;
}
}
return false;
}
Node* find_recursion(const T& key)
{
return _find_recursion(_root, key);
}
bool insert_recursion(const T& key, const K& value)
{
return _insert_recursion(_root, key, value);
}
bool erase_recursion(const T& key)
{
return _erase_recursion(_root, key);
}
~BinarySearchTree()
{
_BSTDestroy(_root);
}
BinarySearchTree(const BinarySearchTree<T, K>& copy)
{
_root = _creat_new_root(copy._root);
}
// 传值传参会进行拷贝构造
BinarySearchTree<T, K>& operator=(BinarySearchTree<T, K> copy)
{
std::swap(_root, copy._root);
return *this;
}
void InOrder()
{
_InOrder(_root);
std::cout << std::endl;
}
private:
void _InOrder(Node* root)
{
if (root)
{
_InOrder(root->_left);
std::cout << root->_key << " ";
_InOrder(root->_right);
}
}
Node* _find_recursion(Node* root, const T& key)
{
if (root == nullptr)
return root;
else
{
if (root->_key < key)
return _find_recursion(root->_right, key);
else if (root->_key > key)
_find_recursion(root->_left, key);
else
return root;
}
}
bool _insert_recursion(Node*& root, const T& key, const K& value)
{
if (root == nullptr)
{
root = new Node(key, value);
return true;
}
else
{
if (root->_key < key)
return _insert_recursion(root->_right, key);
else if (root->_key > key)
return _insert_recursion(root->_left, key);
else
return false;
}
}
void _erase_right_min_node(Node* del)
{
// 从被删除结点开始, 找右子树的最小(左)节点
Node* right_min = del->_right;
// 并记录这个节点的父亲节点, 让其从del开始
Node* right_min_parent = del;
while (right_min->_left)
{
right_min_parent = right_min;
right_min = right_min->_left;
}
// 交换这个节点和要删除节点的 key
std::swap(del->_key, right_min->_key);
// 将删除 del 转化为删除 right_min (托孤法删除)
if (right_min_parent->_left == right_min)
right_min_parent->_left = right_min->_right;
else
right_min_parent->_right = right_min->_right;
delete right_min;
right_min = nullptr;
}
void _erase_left_max_node(Node* del)
{
// 从被删除节点开始, 找左子树的最大(右)节点
Node* left_max = del->_left;
// 并记录这个节点的父亲节点, 让其从del开始
Node* left_max_parent = del;
while (left_max->_right)
{
left_max_parent = left_max;
left_max = left_max->_right;
}
// 交换这个节点和要删除节点的 key
std::swap(del->_key, left_max->_key);
// 将删除 del 转化为删除 left_max (托孤法删除)
if (left_max_parent->_left == left_max)
left_max_parent->_left = left_max->_left;
else
left_max_parent->_right = left_max->_left;
delete left_max;
left_max = nullptr;
}
bool _erase_recursion(Node*& root, const T& key)
{
if (!root)
return false;
else
{
// 如果当前节点的key > 目标key,那么递归它的左子树即可
if (root->_key > key)
return _erase_recursion(root->_left, key);
// 如果当前节点的key < 目标key,那么递归它的右子树即可
else if (root->_key < key)
return _erase_recursion(root->_right, key);
// 如果找到了,进行删除
else
{
// 此时的root就是要删除的节点
Node* del = root;
// a. 左子树为空
if (root->_left == nullptr)
root = root->_right;
// b. 右子树为空
else if (root->_right == nullptr)
root = root->_left;
// c. 左右子树都不为空
else
{
// 左子树的最右节点
Node* left_max = root->_left;
while (left_max->_right)
left_max = left_max->_right;
// 交换"合适节点"和"被删除节点"的key
std::swap(left_max->_key, root->_key);
// 在这里递归左子树即可
return _erase_recursion(root->_left, key);
}
delete del;
del = nullptr;
return true;
}
}
}
Node* _creat_new_root(Node* root)
{
// 如果遇到空了,就不用构造了
if (root == nullptr)
return nullptr;
else
{
// 根据前序的思想(NLR),依次构造它的根、左子树、右子树
// 同时将它们连接起来
Node* new_root = new Node(root->_key);
new_root->_left = _creat_new_root(root->_left);
new_root->_right = _creat_new_root(root->_right);
return new_root;
}
}
// 注意我们这里传递的是根的引用
void _BSTDestroy(Node*& root)
{
if (root == nullptr)
return;
else
{
// 依据后序的思想
_BSTDestroy(root->_left);
_BSTDestroy(root->_right);
delete root;
root = nullptr;
}
}
private:
Node* _root;
};
}
void Test1(void)
{
// 这就是我们的KV模型,通过我们的单词可以查到对应的中文
Xq::BinarySearchTree<std::string, std::string> dict;
dict.insert("left", "左边");
dict.insert("right", "右边");
dict.insert("superman", "超人");
dict.insert("step", "步骤");
std::string str;
while (std::cin >> str)
{
Xq::BinarySearchTreeNode<std::string, std::string>* ret = dict.find(str);
if (ret == nullptr)
std::cout << "没有此单词" << std::endl;
else
std::cout << ": " << ret->_value << std::endl;
}
}
int main()
{
Test1();
return 0;
}
假如我现在要实现统计动物园中不同动物的个数呢?
其实这也是一个KV模型,只不过这里的V是动物的个数,实现如下:
// 打印 key 和 value
void _InOrder(Node* root)
{
if (root)
{
_InOrder(root->_left);
std::cout << root->_key << ":" << root->_value << std::endl;
_InOrder(root->_right);
}
}
void Test2(void)
{
Xq::BinarySearchTree<std::string, size_t> animals_size;
std::string str[6] = { "狮子", "老虎", "猴子", "猩猩", "大熊猫", "黑熊" };
srand((size_t)time(nullptr));
int count = 10;
while (count--)
{
std::string arr = str[rand() % 6];
Xq::BinarySearchTreeNode<std::string, size_t>* ret = animals_size.find(arr);
if (ret == nullptr)
{
animals_size.insert(arr, 1);
}
else
{
++ret->_value;
}
}
animals_size.InOrder();
}
1.2. 二叉搜索树的性能分析
相信经过我们前面对于BST的模拟实现,我们对于增删查改有了一定的理解。
我们可能会认为它们的时间复杂度就是O(logN),但是呢,这只是较好的情况或者称之为一般情况,什么意思呢?
就是当这棵二叉树是较为均衡的一棵二叉树,那么它的增删查改时间复杂度的确是O(logN),但是如果是下面这棵树呢?
可以看到,这棵树是一棵"歪脖子树",它只有左子树,没有右子树,那么此时对于这棵树而言,他的增删查改就变成了O(N),也就是说,对于普通的搜索二叉树的增删查改的时间复杂度我们认为是O(N)(最坏情况)。
因此,为了避免这种最坏情况,我们需要调整搜索二叉树的结构,让它避免产生这种结构。
2. 二叉树进阶面试题
2.1. 根据一棵树的前序遍历与中序遍历构造二叉树
105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)
思路:前序NLR 中序 LNR,因此前序可以确立根,中序可以分割左右子区间。
Solution:
- Step 1: 通过前序确定根;
- Step 2: 通过中序分割左右子区间,由于前序确定的根,那么在中序中根左边的元素在左子树、在根右边的元素在右子树;
- Step 3: 递归子问题,通过递归左右子树,并通过返回值连接它们
代码如下:
class Solution {
public:
TreeNode* _build_tree(std::vector<int>& pre_order,std::vector<int> in_order,int& pre_index, int in_begin,int in_end)
{
// 注意,如果是非法区间,返回nullptr
// 参数解释: pre_index是前序的下标,由于我们需要在每个子问题上都是同一个值,因此这里使用引用
// in_begin 和 in_end 分别是中序区间的开始和结束
if(in_begin > in_end)
return nullptr;
// 因为pre_order.length >= 1,因此没有空表,直接构造根节点,前序的第一个elements就是root
//1. 前序确定根
TreeNode* root = new TreeNode(pre_order[pre_index++]);
//2. 中序分割左右子区间
//a. 找根
int in_index = in_begin;
//b. 分割左右子区间
// 区间被分为 [in_begin,in_index-1] in_index [in_index+1,in_end]
while(in_order[in_index] != pre_order[pre_index-1])
++in_index;
//3. 递归子问题
root->left = _build_tree(pre_order,in_order,pre_index,in_begin,in_index-1);
root->right = _build_tree(pre_order,in_order,pre_index,in_index+1,in_end);
//4. 返回根
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int pre_index = 0;
return _build_tree(preorder,inorder,pre_index,0,inorder.size()-1);
}
};
2.2. 根据一棵树的中序遍历与后序遍历构造二叉树
106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)
思路:后续确定根,中序分割左右子树
由于题目明确了没有重复元素。
Solution:
- Step 1: 通过后续确定根;
- Step 2: 通过中序分割左右子树;
- Step 3: 递归子问题,并通过返回值链接它们。
由于我们是后序(LRN)确立根,因此递归子问题时需要先递归右子树、在递归左子树。
class Solution {
public:
TreeNode* _build_tree(std::vector<int>& in_order,std::vector<int>& post_order,int& post_index,int in_begin, int in_end)
{
// 注意: 对于非法的子区间,直接返回nullptr
if(in_begin > in_end)
return nullptr;
//1. 后序确定根,题干明确了不可能为空表,因此直接构造根
TreeNode* root = new TreeNode(post_order[post_index--]);
//2. 根据根,分割中序的左右区间
int in_index = in_begin;
while(post_order[post_index+1] != in_order[in_index])
++in_index;
// 将左右子树分为 [in_begin,in_index-1] in_index [in_index+1,in_end]
//3. 递归子问题
//注意: 因为我们是后序确立根(LRN),因此需要先递归右子树、在递归左子树
root->right = _build_tree(in_order,post_order,post_index,in_index+1,in_end);
root->left = _build_tree(in_order,post_order,post_index,in_begin,in_index-1);
//4. 返回根
return root;
}
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
int post_index = postorder.size() - 1;
return _build_tree(inorder,postorder,post_index,0,inorder.size()-1);
}
};
2.3. 二叉树的前序遍历 --- 非递归
思路:
前序的非递归解决方法是将一颗树分为:
左路节点
左路节点的右子树
步骤如下:
- step 1: 先将左路节点同时入栈和vector(目的是为了访问左路节点的右子树);
- step 2: 取栈顶数据,出栈,访问右子树。继续重复step1的过程。
注意:如果树不为空或者栈不为空就继续,树不为空说明树还有数据,栈不为空说明有可能还有右子树没有被访问。
class Solution {
public:
void _pre_order(TreeNode* root,std::vector<int>& v,std::stack<TreeNode*>& st)
{
TreeNode* cur = root;
// 树还有数据 或者 栈还有数据就继续
while(cur || !st.empty())
{
// 将左路节点分别入栈、vector中,栈中数据的目的是为了获得我们的右子树
while(cur)
{
v.push_back(cur->val);
st.push(cur);
cur = cur->left;
}
// 去栈顶数据
TreeNode* top = st.top();
st.pop();
// 遍历我们的右子树
cur = top->right;
}
}
vector<int> preorderTraversal(TreeNode* root) {
std::vector<int> v;
std::stack<TreeNode*> st;
_pre_order(root,v,st);
return v;
}
};
2.4. 二叉树的中序遍历 --- 非递归
思路:根据中序的思想(LNR),我们的迭代写法:
- Step 1: 先向栈入左路节点;
- Step 2: 取栈顶数据入vector,pop栈顶数据,如果栈顶数据的右子树不为空,终止循环,向栈入右子树的左路节点(重复Step 1)。
从栈里面取左路节点,意味着这个节点的左子树被访问完了,因此要访问右子树的左路节点。
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
std::vector<int> v;
if(nullptr == root) return v;
std::stack<TreeNode*> st;
TreeNode* cur = root;
// 树还有数据 || 栈不为空
while(cur || !st.empty())
{
// 先向栈入左路节点
while(cur)
{
st.push(cur);
cur = cur->left;
}
// 取栈顶数据,入vector,如果栈顶数据的右节点不为空,终止循环,继续向栈入右子树的左路节点
//从栈里面取左路节点,意味着这个节点的左子树被访问完了,因此要访问右子树的左路节点。
while(!st.empty())
{
TreeNode* top= st.top();
st.pop();
v.push_back(top->val);
if(top->right)
{
cur = top->right;
break;
}
}
}
return v;
}
};
2.5. 二叉树的后序遍历 --- 非递归
思路:通过栈存储左路节点,用两个vector,第一个vector存储后序遍历的节点(为了判断某个节点的右子树是否被访问过),第二个vector存储后序遍历的节点的val(用于返回结果);
如何判断某个节点的右子树被访问过?
只要这个节点的右子树 == 存储后序遍历节点的vector的back(),那么就意味着这个节点的右子树已经被访问过了,此时只需要将这个节点分别push到两个vector中,并pop栈顶数据(就是这个节点),进入下一次循环即可。
只要某个节点的右树为空树,那么就将这个结点分别push到两个vector中;否则,入右树的左路节点。
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
// 存储后序节点中的val的vector
std::vector<int> v;
if(nullptr == root) return v;
// 存储左路节点的栈
std::stack<TreeNode*> st;
// 存储后序节点的vector
std::vector<TreeNode*> v_node;
TreeNode* cur = root;
// 如果树还有数据 || 栈还有左路节点,那么就继续
while(cur || !st.empty())
{
// 入左路节点
while(cur)
{
st.push(cur);
cur = cur->left;
}
// 走到这里说明,左路节点走完了,需要走左路节点的右子树
while(!st.empty())
{
// 取栈顶的数据
TreeNode* top = st.top();
// 如果栈顶数据的右子树已经被访问过了,那么此时直接将栈顶数据push到vector中,
// 并把这个栈顶数据给pop掉,进行下一次循环
// 我们在这里用了一个vector存储右子树的节点,只要这个vector的back()是栈顶数据的右子树
// 那么就代表栈顶数据的右子树已经被访问过了
if(!v_node.empty() && top->right == v_node.back())
{
v.push_back(top->val);
v_node.push_back(top);
st.pop();
continue;
}
// 如果栈顶数据的右子树为空,那么就push到vector中
if(!top->right)
{
st.pop();
v.push_back(top->val);
v_node.push_back(top);
}
// 否则,就去入右子树的左路节点
else
{
cur = top->right;
break;
}
}
}
return v;
}
};
2.6. 二叉树的层序遍历
思路:将每一层的数据都push进队列中,队列的大小就是当前层数的个数(因为当pop掉cur层的最后一个数据时,那么也意味着cur的下一层的数据都进入了队列中,此时队列的大小就是当前层数的元素个数 )。通过每一层的个数控制每一次的循环次数
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
std::vector<std::vector<int>> vv;
if(root == nullptr)
return vv;
else
{
// 每层的元素个数
int level_size = 0;
std::queue<TreeNode*> qu;
// 因为此时root != nullptr,因此直接push队列中
qu.push(root);
++level_size;
// 只要队列还有数据,就继续
while(!qu.empty())
{
std::vector<int> v;
while(level_size--)
{
TreeNode* front = qu.front();
qu.pop();
v.push_back(front->val);
if(front->left)
qu.push(front->left);
if(front->right)
qu.push(front->right);
}
// 当前层数的元素个数 = 队列的大小
level_size = qu.size();
vv.push_back(v);
}
}
return vv;
}
};
2.7. 二叉树的层序遍历Ⅱ
107. 二叉树的层序遍历 II - 力扣(LeetCode)
思路:这是4.4.的变形题,但是如果我们解决了4.4,那么这里就很简单了,我们按照4.4.的思路得到结果,只需要逆置下该结果即可。
class Solution {
public:
vector<vector<int>> levelOrderBottom(TreeNode* root) {
std::vector<std::vector<int>> vv;
if(root == nullptr)
return vv;
else
{
// 每层的元素个数
int level_size = 0;
std::queue<TreeNode*> qu;
// 因为此时root != nullptr,因此直接push队列中
qu.push(root);
++level_size;
// 只要队列还有数据,就继续
while(!qu.empty())
{
std::vector<int> v;
while(level_size--)
{
TreeNode* front = qu.front();
qu.pop();
v.push_back(front->val);
if(front->left)
qu.push(front->left);
if(front->right)
qu.push(front->right);
}
// 当前层数的元素个数 = 队列的大小
level_size = qu.size();
vv.push_back(v);
}
}
// 逆置结果即可
std::reverse(vv.begin(),vv.end());
return vv;
}
};
2.8. 二叉树的最近公共祖先
236. 二叉树的最近公共祖先 - 力扣(LeetCode)
思路一:如果这是一个三叉链(每个节点有parent),我们可以转化为链表相交问题。但是很抱歉,这里并不是三叉链,因此这种解决方案不可行。
思路二:通过两个目标节点的所在位置进行判断:
case 1: 只要一个节点是根,那么根就是公共祖先
case 2: 如果一个在我的左子树,一个在我的右子树,那么我就是公共祖先
case 3: 如果两个都在我的左树,递归子问题(递归左树)
case 4: 如果两个都在我的右树,递归子问题(递归右树)
时间复杂度为O(N^2)
只有满二叉树或者完全二叉树我们可以认为高度是logN,因为会有歪脖子树
优化思路:如果是搜索二叉树可以优化到O(N)
一个比根小,一个比根大,根就是最近公共祖先
思路三:用DFS(在这里我们用前序遍历)求出p和q的路径并分别将从根节点到目标节点的路径入栈,将该问题转化为链表相交问题
注意:入栈可以依据前序的思想,如果遇到了就return true即可,如果没有遇到,递归左子树和递归右子树,如果左子树、右子树都为空,那么返回false
时间复杂度是O(N)
2.8.1. 第二种思路实现
class Solution {
public:
bool is_exist_node(TreeNode* root, TreeNode* obj)
{
if(root == nullptr)
return false;
else
{
return root == obj
|| is_exist_node(root->left,obj)
|| is_exist_node(root->right,obj);
}
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
//只要一个节点是根,那么根就是最近公共祖先
if(p == root || q == root)
return root;
// 因为p不是根,因此p要么在左树,要么在右树
bool p_left = is_exist_node(root->left,p);
bool p_right = !p_left;
// 同理, q要么在左树,要么在右树
bool q_right = is_exist_node(root->right,q);
bool q_left = !q_right;
// 如果p在左且q在右 或者 p在右且q在左,那么最近公共祖先是root
if((p_left && q_right) || (p_right && q_left))
return root;
// 如果p和q都在左树,那么递归左树
if(p_left && q_left)
return lowestCommonAncestor(root->left,p,q);
// 如果p和q都在右树,那么递归右树
else
return lowestCommonAncestor(root->right,p,q);
}
};
2.8.2. 第三种思路实现
class Solution {
public:
bool get_path(TreeNode* root, TreeNode* obj,std::stack<TreeNode*>& st)
{
// 依据前序的思想
if(root == nullptr)
return false;
else
{
// 无脑入根
st.push(root);
// 如果找到了,就返回true
if(root == obj)
return true;
bool ret = get_path(root->left,obj,st);
// 如果没找到继续递归
if(!ret)
ret = get_path(root->right,obj,st);
// 如果左右子树都找了且没找到,那么说明obj不在该路径,pop栈顶元素
if(!ret)
st.pop();
return ret;
}
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
std::stack<TreeNode*> p_st;
std::stack<TreeNode*> q_st;
get_path(root,p,p_st);
get_path(root,q,q_st);
// 将求最近公共祖先的问题转化了"链表相交"问题
// "链表相交"问题: 长的先走差距步,然后同时走,相等就是相交节点
while(p_st.size() != q_st.size())
{
if(p_st.size() > q_st.size())
p_st.pop();
else
q_st.pop();
}
while(!p_st.empty() && !q_st.empty() && (p_st.top() != q_st.top()))
{
p_st.pop();
q_st.pop();
}
return p_st.top();
}
};
2.9. 二叉搜素树和双向链表
二叉搜索树与双向链表_牛客题霸_牛客网 (nowcoder.com)
思路:
利用中序的前驱节点和后继结点,
cur的前驱节点是prev,prev的后继节点是cur
即cur的left指向它的前驱节点;
如果前驱节点不为空,那么prev的后继节点指向cur
注意:cur需要保持唯一性(同一个prev),需要带引用。
例如:
class Solution {
public:
void _tree_to_become_list(TreeNode* cur,TreeNode*& prev)
{
if(cur == nullptr)
return ;
else
{
// 依据中序的思想LNR
_tree_to_become_list(cur->left, prev);
// cur的left指向前驱节点prev,prev的right指向后继节点cur
cur->left = prev;
if(prev)
prev->right = cur;
prev = cur;
_tree_to_become_list(cur->right, prev);
}
}
TreeNode* Convert(TreeNode* pRootOfTree) {
// 如果是空树,直接返回nullptr
if(pRootOfTree == nullptr)
return nullptr;
TreeNode* prev = nullptr;
_tree_to_become_list(pRootOfTree, prev);
TreeNode* head = pRootOfTree;
// 找链表的头节点
while(head->left)
head = head->left;
return head;
}
2.10. 二叉树创建字符串
题意:通过前序构造字符串。
注意,空节点要使用(),但是题干告诉我们有些括号会省略,但是如下情况不能省略:
case 1:如果左不为空,不能省略。
case 2:左树为空且右树不为空,括号不能省略。
case 3:右树不为空,括号不能省略
class Solution {
public:
string tree2str(TreeNode* root) {
if(root == nullptr)
return "";
else
{
std::string str = std::to_string(root->val);
// if(root->left || root->right)
if(root->left != nullptr || (root->left == nullptr && root->right != nullptr))
// 左树不为空 或者 左树为空且右树不为空, 左树的括号不能省略
{
str += '(';
str += tree2str(root->left);
str += ')';
}
// 右树不为空,右树的括号不能省略
if(root->right)
{
str += '(';
str += tree2str(root->right);
str += ')';
}
return str;
}
}
};
二叉树进阶 - 下 done,有了这些铺垫,我们就可以了解平衡二叉搜索树了。