前言:终于来更新了,这几天一直在赶进度,这些底层的东西听懂加实现需要很长时间,大家在看完之后一定要自己动手,要不然白学,接下来四篇学的内容都是面试与笔试的高频考点,小伙伴们要重视起来哦。看这几篇之前,这篇"【二叉树与堆】:从"根"本说起,一起爬满数据的枝桠!"一定要学会哦!
目录
[1. ⼆叉搜索树的插⼊](#1. ⼆叉搜索树的插⼊)
[3. ⼆叉搜索树的删除](#3. ⼆叉搜索树的删除)
[1. Key模型(纯关键词搜索)](#1. Key模型(纯关键词搜索))
[2. Key/Value模型(关键词与对应值)](#2. Key/Value模型(关键词与对应值))
[1. 根据二叉树创建字符串](#1. 根据二叉树创建字符串)
[2. 正向层序遍历](#2. 正向层序遍历)
[3. 反向层序遍历](#3. 反向层序遍历)
[4. 最近公共祖先](#4. 最近公共祖先)
[5. 从前序/后序与中序遍历序列构造二叉树](#5. 从前序/后序与中序遍历序列构造二叉树)
[6. 非递归前序遍历/中序遍历](#6. 非递归前序遍历/中序遍历)
[7. 非递归后序遍历](#7. 非递归后序遍历)
[8. 将二叉搜索树转化为排序的双向链表](#8. 将二叉搜索树转化为排序的双向链表)
一、二叉搜索树的概念
二叉搜索树,也称为二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
若它的左子树不为空,则左子树上所有节点的值 都小于等于根节点的值
若它的右子树不为空,则右子树上所有节点的值 都大于等于根节点的值
它的左右子树也分别为二叉搜索树
(所有节点!所有节点!不是只有它的孩子那一个结点符合大小关系!)
简单来说,它就像一个有严格家规的家族:每个节点的左孩子 都比自己小(或相等),右孩子都比自己大(或相等)。

注意点:
二叉搜索树可以支持插入相等的值,也可以不支持,具体取决于使用场景。我们后续要学习的C++ STL容器中,
map和set不支持插入相等键值,而multimap和multiset支持。
二、二叉搜索树的实现
树的结构还和以前一样
cpp
template<class T>
struct BTNode
{
T _key;
BTNode<T>* _left;
BTNode<T>* _right;
BTNode(const T& key)
:_key(key)
, _left(nullptr)
, _right(nullptr)
{}
};
1. ⼆叉搜索树的插⼊
插入过程遵循一个简单的原则:"比大小,找位置":
树为空:直接创建新节点作为根节点。
树不为空:从根开始,将插入值与当前节点比较:
插入值 大于 当前节点值 → 往右子树走
插入值 小于 当前节点值 → 往左子树走
插入值 等于 当前节点值(且允许重复)→ 按约定方向走(统一向左或向右)
直到找到空位置,插入新节点。
cpp
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new node(key);
return true;
}
//树为空,直接创建新节点作为根节点
node* cur = _root; //树不为空,从根开始比较
node* parent = nullptr;
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
//插入值小于当前节点值 → 往左子树走
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
//插入值大于当前节点值 → 往右子树走
else return false;
//这里实现不支持插入的
}
//直到走空停下来,插入新节点
cur = new node(key);
if (parent->_key > key)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
return true;
}
为什么增加parent指针?
保证在插入新节点时,能找到其父节点并正确连接。 parent->_key > key;这一步判断左右孩子,parent->_left = cur; 这一步进行链接。所以下面只要有链接需要,都要增加parent指针。
2.⼆叉搜索树的查找
查找过程与插入类似,也是"比大小,定方向":
从根开始比较:
查找值 大于 当前节点值 → 往右子树找
查找值 小于 当前节点值 → 往左子树找
查找值 等于 当前节点值 → 找到目标
最多查找树的高度次,如果走到空还没找到,说明该值不存在。
注意:如果支持重复值,通常要求找到中序遍历的第一个匹配值。
cpp
//不支持重复值
bool Find(const K& key)
{
node* cur = _root;
while (cur)
{
if (cur->_key > key)
{
cur = cur->_left;
}
else if (cur->_key < key)
{
cur = cur->_right;
}
else return true;
}
return false;
}
怎么查找中序遍历的第一个重复值
中序遍历是"左根右",找到相等的不要停,再往左树走找相等的,直到左树为空,就是中序遍历的第一个重复值。
cppNode* Find(const K& key) { Node* cur = _root; Node* firstFound = nullptr; // 记录第一个找到的节点 while (cur) { if (cur->_key < key) cur = cur->_right; else if (cur->_key > key) cur = cur->_left; else { // 找到匹配的节点,但可能不是中序的第一个 // 继续往左走,尝试找到更早出现的相同值 firstFound = cur; // 记录当前找到的 cur = cur->_left; // 继续向左寻找 } } return firstFound; // 返回找到的第一个节点(可能为nullptr) }
3. ⼆叉搜索树的删除
这个就有一些难度了,我认为难点只有两个,一个是删完这个结点仍然要保持规则,一个是删完之后要重新链接其附近的结点。因为我们链接时只用看该结点的左右孩子,那么我们就可以分为四类。
(1)删除叶子结点N(左右孩子都为空)
这可太好了,直接删除就好了,不会影响其他结点。
(2)删除只有右孩子的结点N
把N结点的⽗亲对应的孩⼦指针指向N的右孩⼦,直接删除N结点。比如N结点是其父亲的左结点,那么就把父亲的左结点指向N的右孩⼦,再删除N,由于N结点是其父亲的左结点,所以以N为根的树的所有结点都小于N的父亲,不会影响规则。
(3)删除只有左孩子的结点N
与上面类似,将N结点的父节点的对应指针指向该节点的左孩子。
我们看这三种情况,都是先判断N结点是其父亲的什么结点,然后把N结点不空的那一个孩子赋给N结点对应其父亲的结点,要是都为空,那就随便赋呗,反正是空,最后删除就行。所以可以写成一种。


cpp
if (cur->_left == nullptr)
{
if (parent == nullptr)
_root = cur->_right;
//防止删根
else if (parent->_left == cur)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
delete cur;
return true;
}
else if (cur->_right == nullptr)
{
if (parent == nullptr)
_root = cur->_left;
else if (parent->_left == cur)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
delete cur;
return true;
}
else
{ //......
}
(4)删除有两个孩子的结点N
这时候我们直接删除就无法链接了,所以要用替换法。那用哪个结点替换后可以符合规则呢?答案是结点N的左子树的最右结点或右子树的最左结点。
如果用其左子树的最右结点,那么就可以保证,我比N的所有左结点都大,而又因为我在N的左子树,我又比N的所有右结点都小,而且我不可能破坏我与我父亲的关系,因为我是在N的子树里的结点。如果用其右子树的最左结点,也是一样。
替换后如何链接以用其左子树的最右结点替换为例,我们直接把值赋给要删除的结点cur即可,cur的链接关系不用变。我们最后是要删除拿来替换的结点,而它一定是没有右结点,又返回到上面的情况了。要注意cur的左结点就没有右结点的情况,要单独考虑(比如下面删除10)

cpp
node* lmaxnp = cur;
node* lmaxn = cur->_left;
while (lmaxn->_right)
{
lmaxnp = lmaxn;
lmaxn = lmaxn->_right;
}
//找到左子树的最右结点
cur->_key = lmaxn->_key;
//替换
if (lmaxnp->_left == lmaxn)
lmaxnp->_left = lmaxn->_left;
else
lmaxnp->_right = lmaxn->_left;
//这么写是为了防止cur->_left就是要找的最大值
delete lmaxn;
完整代码:
cpp
template<class T>
struct BTNode
{
T _key;
BTNode<T>* _left;
BTNode<T>* _right;
BTNode(const T& key)
:_key(key)
, _left(nullptr)
, _right(nullptr)
{}
};
template<class K>
class BSTree
{
private:
typedef BTNode<K> node;
node* _root = nullptr;
void _InOrder(node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
public:
BSTree() = default;
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new node(key);
return true;
}
node* cur = _root;
node* parent = nullptr;
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else return false;
}
cur = new node(key);
if (parent->_key > key)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
return true;
}
bool Find(const K& key)
{
node* cur = _root;
while (cur)
{
if (cur->_key > key)
{
cur = cur->_left;
}
else if (cur->_key < key)
{
cur = cur->_right;
}
else return true;
}
return false;
}
bool Erase(const K& key)
{
node* cur = _root;
node* parent = nullptr;
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else
{
if (cur->_left == nullptr)
{
if (parent == nullptr)
_root = cur->_right;
//防止删根
else if (parent->_left == cur)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
delete cur;
return true;
}
else if (cur->_right == nullptr)
{
if (parent == nullptr)
_root = cur->_left;
else if (parent->_left == cur)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
delete cur;
return true;
}
else
{
node* lmaxnp = cur;
node* lmaxn = cur->_left;
while (lmaxn->_right)
{
lmaxnp = lmaxn;
lmaxn = lmaxn->_right;
}
cur->_key = lmaxn->_key;
if (lmaxnp->_left == lmaxn)
lmaxnp->_left = lmaxn->_left;
else
lmaxnp->_right = lmaxn->_left;
//这么写是为了防止cur->_left就是要找的最大值
delete lmaxn;
return true;
}
}
}
return 0;
}
//中序输出后,数据可以从小到大排列
void InOrder()
{
_InOrder(_root);
cout << endl;
}
};
三、二叉搜索树的应用模型
1. Key模型(纯关键词搜索)
结构中只存储key,用于判断某个值是否存在:
-
场景1:小区车牌识别系统 - 判断车牌是否在授权列表中
-
场景2:单词拼写检查 - 判断单词是否在词典中
2. Key/Value模型(关键词与对应值)
每个key对应一个value,用于通过key查找对应的value:
-
场景1:中英词典 - 通过英文单词查找中文释义
-
场景2:停车场计费系统 - 通过车牌查找入场时间,计算停车费用

- 场景3:单词频率统计 - 统计每个单词在文章中出现的次数
Key/Value模型的二叉搜索树实现与Key模型类似,只是在节点中增加了value成员,插入和查找时以key为依据,但可以访问和修改对应的value。
下面我实现一个Key/Value模型的二叉搜索树,这主要用到我们泛型编程的思想
(这只是开始,后面几篇容器的封装才真正考验你泛型编程的功底!)
cpp
#include <iostream>
#include <string>
using namespace std;
template<class K, class V>
struct BSTNode {
K _key;
V _value;
BSTNode<K, V>* _left;
BSTNode<K, V>* _right;
BSTNode(const K& key, const V& value)
: _key(key), _value(value), _left(nullptr), _right(nullptr) {}
};
//支持重复键
template<class K, class V>
class BSTree {
typedef BSTNode<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) {
parent = cur;
if (cur->_key < key) {
cur = cur->_right;
} else if (cur->_key > key) {
cur = cur->_left;
} else {
// 键已存在,根据需求处理:
// 1. 返回false表示插入失败
// 2. 更新值
cur->_value = value;
return true;
}
}
cur = new Node(key, value);
if (parent->_key < key) {
parent->_right = cur;
} else {
parent->_left = cur;
}
return true;
}
Node* Find(const K& key) {
Node* cur = _root;
Node* firstFound = nullptr;
while (cur) {
if (cur->_key < key) {
cur = cur->_right;
} else if (cur->_key > key) {
cur = cur->_left;
} else {
// 找到匹配节点,记录并继续向左寻找更早的匹配
firstFound = cur;
cur = cur->_left;
}
}
return firstFound;
}
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) {
if (parent == nullptr) {
_root = cur->_right;
} else {
if (parent->_left == cur)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
}
delete cur;
} else if (cur->_right == nullptr) {
if (parent == nullptr) {
_root = cur->_left;
} else {
if (parent->_left == cur)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
}
delete cur;
} else {
Node* rightMinParent = cur;
Node* rightMin = cur->_right;
while (rightMin->_left) {
rightMinParent = rightMin;
rightMin = rightMin->_left;
}
cur->_key = rightMin->_key;
cur->_value = rightMin->_value;
if (rightMinParent->_left == rightMin)
rightMinParent->_left = rightMin->_right;
else
rightMinParent->_right = rightMin->_right;
delete rightMin;
}
return true;
}
}
return false;
}
void InOrder() {
_InOrder(_root);
cout << endl;
}
// 统计某个键出现的次数
int Count(const K& key) {
return _Count(_root, key);
}
private:
void _InOrder(Node* root) {
if (root == nullptr) return;
_InOrder(root->_left);
cout << root->_key << ":" << root->_value << " ";
_InOrder(root->_right);
}
//递归思想
int _Count(Node* root, const K& key)
{
if (root == nullptr) return 0;
int count = 0;
if (root->_key == key) {
count = 1;
}
// 由于可能有重复键,需要搜索左右子树
return count + _Count(root->_left, key) + _Count(root->_right, key);
}
Node* _root = nullptr;
};
四、二叉搜索树的性能分析
二叉搜索树的性能高度依赖于树的形状:
-
最优情况 :树为完全二叉树(或接近完全二叉树),高度为 log₂N ,此时增删查改的时间复杂度为 O(logN)
-
最差情况 :树退化为单支树(类似链表),高度为 N ,此时时间复杂度退化为 O(N)
正因为存在退化为O(N)的风险,单纯的二叉搜索树无法满足实际需求,这才催生了AVL树 和红黑树等平衡二叉搜索树。我们下节讲。

与二分查找的对比
有人可能会问:二分查找也有O(logN)的查找效率,为什么还需要二叉搜索树?
二分查找有两大局限:
需要数据存储在支持随机访问的结构中(如数组),且必须有序
插入和删除数据效率很低,需要移动大量元素
而二叉搜索树在保持较好查找效率的同时,也提供了高效的插入和删除能力。
五、二叉树进阶算法题
(点击名称即可跳转到对应OJ题)
下面问题统一的树的结构:
cpp
struct TreeNode
{
int val;
TreeNode* left;
TreeNode* right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode* left, TreeNode* right) : val(x), left(left), right(right) {}
};
1. 根据二叉树创建字符串
问题核心 :分析加括号的时机,只有一个结点有左树无右树时,才可以不加右树的括号
关键步骤:前序递归
根节点:直接输出节点值。
左子树:左子树不管是否为空,必须加括号,除非两个子树全是空。
右子树:右子树存在,就给右子树加括号;不存在就不加。
cpp
string tree2str(TreeNode* root)
{
string res;
if (root == nullptr) return res;
//访问跟
res += to_string(root->val);
//遍历左树(当左树空,右树不空时只用于打括号)
if (root->left || root->right)
{
res += '(';
res += tree2str(root->left);
res += ')';
}
//遍历右树
if (root->right)
{
res += '(';
res += tree2str(root->right);
res += ')';
}
return res;
}
2. 正向层序遍历
之前讲过层序遍历了,不同点是这个需要用动态二维数组存储,所以要加一个size记录每一层结点的数量,确保上一层出完队列后,它带入且仅带入了下一层的全部结点。
cpp
vector<vector<int>> levelOrder(TreeNode* root)
{
vector<vector<int>> vv;
queue<TreeNode*> q;
if (root)
{
q.push(root);
}
while (!q.empty())
{
size_t size = q.size();
vector<int> v;
while (size--)
{
if (q.front()->left)
q.push(q.front()->left);
if (q.front()->right)
q.push(q.front()->right);
v.push_back(q.front()->val);
q.pop();
}
vv.push_back(v);
}
return vv;
}
3. 反向层序遍历
结尾把数组倒置就行
cpp
vector<vector<int>> levelOrder(TreeNode* root)
{
vector<vector<int>> vv;
queue<TreeNode*> q;
if (root)
{
q.push(root);
}
while (!q.empty())
{
size_t size = q.size();
vector<int> v;
while (size--)
{
if (q.front()->left)
q.push(q.front()->left);
if (q.front()->right)
q.push(q.front()->right);
v.push_back(q.front()->val);
q.pop();
}
vv.push_back(v);
}
reverse(vv.begin(), vv.end());
return vv;
}
4. 最近公共祖先
**关键点:**两个结点一定分别分布在最近公共祖先的左右两棵子树中,或者其中一个就是最近公共祖先!搞清楚这个问题其实就解决了。
方法一: 依次找每个有用结点(要是都在左边,右子树就不用找了)的左右子树,直到成功在两个子树中找到。
**方法二:**用栈记录根节点到p、q的路径,找最后一个公共节点
cpp
// //最近公共祖先
//方法一
bool Seeknode(TreeNode* root, TreeNode* p)
{
if (!root) return false;
if (root == p) return true;
bool a = Seeknode(root->left, p);
if (a) return true;
bool b = Seeknode(root->right, p);
return b;
}
TreeNode* lowestCommonAncestor1(TreeNode* root, TreeNode* p, TreeNode* q)
{
if (root == p || root == q)
{
return root;
}
bool pInLeft, pInRight, qInLeft, qInRight;
//注意题目中说一定存在
pInLeft = Seeknode(root->left, p);
pInRight = !pInLeft; //减少递归的重要步骤
qInLeft = Seeknode(root->left, q);
qInRight = !qInLeft; //减少递归的重要步骤
if ((pInLeft && qInRight) || (qInLeft && pInRight))
{
return root;
}
else if (pInLeft && qInLeft)
{
return lowestCommonAncestor1(root->left, p, q);
}
else
{
return lowestCommonAncestor1(root->right, p, q);
}
}
//方法二
bool NodePath(TreeNode* root, TreeNode* p, stack<TreeNode*>& st)
{
if (!root) return false;
st.push(root);
if (root == p) return true;
if (NodePath(root->left, p, st)) return true;
if (NodePath(root->right, p, st)) return true;
st.pop();
return false;
}
//递归找路径(难点)
TreeNode* GetIntersection(stack<TreeNode*> st1, stack<TreeNode*> st2)
{
if (st1.size() > st2.size())
{
while (st1.size() != st2.size())
{
st1.pop();
}
}
else if (st1.size() < st2.size())
{
while (st1.size() != st2.size())
{
st2.pop();
}
}
while (st1.top() != st2.top())
{
st1.pop();
st2.pop();
}
return st1.top();
}
TreeNode* lowestCommonAncestor2(TreeNode* root, TreeNode* p, TreeNode* q)
{
stack<TreeNode*> st1;
stack<TreeNode*> st2;
NodePath(root, p, st1);
NodePath(root, q, st2);
TreeNode* res = GetIntersection(st1, st2);
return res;
}
5. 从前序/后序与中序遍历序列构造二叉树
核心思想:利用前序确定根节点,利用中序划分左右子树,递归构建
关键步骤:
确定根节点:前序遍历的第一个元素就是当前子树的根
定位中序位置:在中序遍历中找到根节点位置,左边是左子树,右边是右子树
递归构建:
左子树:前序下一元素 + 中序左半部分
右子树:前序后续元素 + 中序右半部分
cpp
//从前序与中序遍历序列构造二叉树
TreeNode* _buildTree1
(vector<int>& preorder, vector<int>& inorder, int& prei, int inbegin, int inend)
{
if (inbegin > inend) return nullptr;
TreeNode* root = new TreeNode;
int temp = preorder[prei];
prei++;
root->val = temp;
int rootIndex;
for (rootIndex = inbegin; rootIndex <= inend; rootIndex++)
{
if (temp == inorder[rootIndex]) break;
}
root->left = _buildTree1(preorder, inorder, prei, inbegin, rootIndex - 1);
root->right = _buildTree1(preorder, inorder, prei, rootIndex + 1, inend);
return root;
}
TreeNode* buildTree1(vector<int>& preorder, vector<int>& inorder)
{
int i = 0;
TreeNode * res = _buildTree1(preorder, inorder, i, 0, inorder.size() - 1);
return res;
}
后序也是一样,左右根,就是倒着来,先确立根,在确定右子树,然后左子树。
6. 非递归前序遍历/中序遍历
其实就是用循环和栈模拟递归,因为递归不就是不断地压栈再出栈吗?
把结点存在栈中, 方便类似递归回退时取父路径结点。跟这里不同的是,这里把一棵二叉树分为两个部分:先访问左路结点,再访问左路结点的右子树。
这里访问右子树要以循环,从栈依次取出这些结点,循环子问题的思想访问左路结点的右子树。
cpp
//非递归前序遍历
vector<int> preorderTraversal(TreeNode* root)
{
vector<int> v;
stack<TreeNode*> st;
if (!root) return v;
TreeNode* cur = root;
while (cur || !st.empty())
{
//访问左路结点,左路结点入栈
while (cur)
{
v.push_back(cur->val);
st.push(cur);
cur = cur->left;
}
if (!st.empty())
{
//从栈中依次访问左路结点的右子树
cur = st.top();
st.pop();
//循环子问题方式访问左路结点的右子树
cur = cur->right;
}
}
return v;
}
cpp
//非递归中序遍历
vector<int> inorderTraversal(TreeNode* root)
{
vector<int> v;
stack<TreeNode*> st;
if (!root) return v;
TreeNode* cur = root;
while (cur || !st.empty())
{
while (cur)
{
st.push(cur);
cur = cur->left;
}
if (!st.empty())
{
cur = st.top();
st.pop();
v.push_back(cur->val);//只是访问时机发生变化
cur = cur->right;
}
}
return v;
}
7. 非递归后序遍历
可以按上面的思路做,但是有一个难点是,由于后序遍历的顺序是左子树 → 右子树 → 根,所以当取到左路节点的右子树时,我们不知道它是否访问过了。例如下图:
我们栈顶是2时,有可能是访问6之后,回到2,发现还有右子树继续访问7;也可能是访问7之后回到2来访问根。所以我们需要做标记,我认为这个方法操作难度其实挺大的,我先放这种解法,之后我会再讲一种简单的。
cpp
vector<int> postorderTraversal(TreeNode* root) {
TreeNode* cur = root;
stack<TreeNode*> s;
vector<int> v;
TreeNode* prev = nullptr;
while (cur || !s.empty())
{
//访问一颗树的开始
while (cur)
{
s.push(cur);
cur = cur->left;
}
TreeNode* top = s.top();
// top结点的右为空 或者 上一个访问结点等于他的右孩子
// 那么说明(空)不用访问 或者 (不为空)右子树已经访问过了
// 那么说明当前结点左右子树都访问过了,可以访问当前结点了
if (top->right == nullptr || top->right == prev)
{
s.pop();
v.push_back(top->val);
prev = top;
}
else
{
// 右子树不为空,且没有访问,循环子问题方式右子树
cur = top->right;
}
}
return v;
}
方法二:
出现以上问题的本质是我的根在最后访问,而我要访问左右子树又必须经过根。那如果我倒着来,变成右左根,再把数组倒置,不是也可以吗?如果我能保证我每棵子树都可以先访问根,再将他的左子树压入栈,之后再将右子树压入栈(后进先出),那就保证了右左根。所以代码如下:
cpp//非递归后序遍历 vector<int> postorderTraversal(TreeNode* root) { //根右左 vector<int> v; stack<TreeNode*> st; if (!root) return v; st.push(root); while (!st.empty()) { TreeNode* temp = st.top(); st.pop(); v.push_back(temp->val); if (temp->left) st.push(temp->left); if (temp->right) st.push(temp->right); //它的右子树在栈顶,而每次离开时又会带入它的左右子树, //所以当它的右子树没有走完时,它不可能走到左树 } reverse(v.begin(), v.end()); return v; }
8. 将二叉搜索树转化为排序的双向链表
中序遍历二叉搜索树时,遍历顺序本身就是有序的。
思路:
使用两个指针:
cur表示当前中序遍历到的节点,prev表示上一个中序遍历的节点当遍历到
cur节点时,将cur->left指向prev,建立指向前驱的链接此时
cur->right无法指向中序下一个节点,因为还不知道下一个节点是谁但是可以通过
prev->right指向cur,为上一个节点建立指向后继的链接即:
每个节点的左指针在遍历到该节点时修改,指向前驱节点
每个节点的右指针在遍历到下一个节点时,通过前一个节点的
prev->right = cur来修改,指向后继节点
cpp
//将二叉搜索树转化为排序的双向链表
void InOrderConvert(Node* cur, Node*& prev)
{
if (cur == nullptr)
return;
InOrderConvert(cur->left, prev);
cur->left = prev;
if (prev) prev->right = cur;
prev = cur;
InOrderConvert(cur->right, prev);
}
Node* treeToDoublyList(Node* root)
{
if (root == nullptr)
return nullptr;
Node* prev = nullptr;
InOrderConvert(root, prev);
Node* head = root;
while (head->left)
{
head = head->left;
}
head->left = prev;
prev->right = head;
return head;
}
后记:这几道算法题是非常重要的,大家一定要重点掌握。后面学习AVL与红黑树会难一些,但也是面试的高频考点,如果有帮助大家可以点个红心支持一下。
