一、题目
题目链接:力扣
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
为了让您更好地理解问题,以下面的二叉搜索树为例:
我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。
下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。
特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。
二、题解
1、思路
🐗 深度优先--中序遍历
我竟然不知道什么是二叉搜索树!可能是忘记了,不记得之前有学过!可恶!
二叉搜索树有以下的性质:
- 若左子树不空,那左子树所有节点的值均 < 根节点的值。
- 若右子树不空,那右子树所有节点的值均 > 根节点的值。
- 左右子树也均为二叉搜索树。
仔细观察上面的二叉搜索树,你会发现当对二叉搜索树进行中序遍历时,得到的结果是一个有序的序列。
算法流程:
dfs(cur)函数: 递归法中序遍历;
1.终止条件: 当节点 cur 为空,代表越过叶节点,直接返回;
2.递归左子树,即 dfs(cur.left) ;
3.构建链表:
- 当 pre 为空时: 代表正在访问链表头节点,记为 head ;
- 当 pre 不为空时: 修改双向节点引用,即 pre.right = cur , cur.left = pre ;
- 保存 cur : 更新 pre = cur ,即节点 cur 是后继节点的 pre ;
4.递归右子树,即 dfs(cur.right) ;
上面是力扣,讲的,你可能每太看明白。下面是我的理解:
1. 通过上面的解读,我们知道中序遍历搜索树可以获得升序排列的序列,好了,现在对于dfs函数,我们只需要知道,每次进入dfs函数访问结点,都是按照中序遍历的顺序依次访问的结点,其他就不用关心。
2. 我们只要按照要求将两个相邻结点连接即可。
3. 当然对于头节点要特殊处理,尾结点最终会保存在pre之阵中。
2、代码实现
🐗 深度优先--中序遍历
关键点:使用pre指针是否为空,当前是否是链表第一个元素。
class Solution {
public:
Node* treeToDoublyList(Node* root) {
if(root == NULL)return root;// 千万不要忘记,不然会报错
dfs(root);
// 双向链表组成双向循环链表
head->left = pre;// 头接尾
// 最后一个pre指向中序遍历最后一个元素
pre->right = head;// 尾接头
return head;
}
private:
Node* pre = NULL, *head;
void dfs(Node* cur)
{
if(cur == NULL)return;
dfs(cur->left);
// 不需要关心具体怎样遍历的,只要知道两个dfs函数中间就是按照中序遍历的顺序访问结点即可
// pre是前一个元素,搜索二叉树中序遍历为递增序列
// 因此pre指向元素小于cur,而且两者挨着
// pre、cur挨着,因此pre右指cur,cur左指pre
if(pre != NULL)pre->right = cur;// 小的right指向大的
else head = cur;// 当pre==null时,cur左侧没有节点,即此时cur为双向链表中的头节点
// 看上面注释
cur->left = pre;// 第一个结点指向的空
// 更新pre
pre = cur;
dfs(cur->right);
}
};
3、复杂度分析
🐗 深度优先--中序遍历
时间复杂度:O(n);
空间复杂度:O(n)。
4、运行结果
🐗 深度优先--中序遍历