什么是morris二叉树遍历?
Morris 二叉树遍历是一种无需额外空间的二叉树遍历算法(即空间复杂度为 O(1)),它利用了树中空闲的右子指针,实现中序遍历或前序遍历。这种方法不需要使用栈或递归,通过动态修改树结构,遍历完成后恢复原树。
不需要栈或者递归实现前,中,后遍历二叉树确实很不错,我们省去递归的开销和,多余内存的使用
morris遍历是通过临时的连接回到上层节点,然后断开临时连接
达到以下效果
5
3 7
1 4 10
将5左子树最右子节点4 连接 也就是 4->right=5;
同理 1->right=3; 4->right=7;
void morris(Treenode* root)
{
if(root==nullptr) return;
treenode* cur=root;
treenode* cur_left=nullptr;
while(cur)
{
cur_left=cur->left;
if(cur_left!=nullptr)
{
//两种情况
//1.cur左子树最右子节点未连接cur 连接即可
//2.cur左子树最右子节点连接cur 说明我们已经访问过cur左子树 还原二叉树
while(cur_left->right&&cur_left->right!=cur) //找到左子树的最右子节点
{
cur_left=cur_left->right;
}
//第一种情况未连接cur节点 此时连接 用于后序返回上一级节点
if(cur_left->right==nullptr)
{
cur_left->right=cur;
cur=cur->left;
continue; //不断向cur的左处理左子树
}
//第二种情况已连接 说明此时cur左子树访问完毕 断开左子树最右子节点和cur 还原二叉树
else cur_left->right=nullptr;
}
//这里我们只需要向右就行 可能是去上一级节点,或者单纯的向右节点
cur=cur->right;
}
}
morris前序遍历
前序遍历是根左右,先访问根节点
上述morris的遍历 我们知道它是不断的向左遍历建立临时连接 否则向右移动
那我们来探讨下数据访问的位置在哪里
1.不断向左建立临时连接的时候
2.没有左子树的节点
/*
二叉树结构
5
3 7
1 4 8
10
pre_morris遍历情况
前序遍历是先打印当前节点然后 再去处理左右子树
我们只需要将访问数据的位置改变就行
我们访问数据的情况有两种 1.不断向左遍历(5,3,7) 2.无左子树(1,4,8,10)
*/
void pre_morris(Treenode *root)
{
Treenode *cur = root;
Treenode *cur_left = nullptr;
while (cur)
{
// cout<<cur->val_<<" "<<endl; 避免重复 我们需要检测节点左子树是否连接过 所以我们不能直接访问cur节点
cur_left = cur->left;
if (cur_left != nullptr)
{
while (cur_left->right && cur_left->right != cur)
{
cur_left = cur_left->right;
}
//不断处理左子树连接的时候访问数据
if (cur_left->right == nullptr)
{
cur_left->right = cur;
cout << cur->val_ << " ";
cur = cur->left;
continue;
}
else
{
cur_left->right = nullptr;
}
}
//访问没有左子树的节点
else
{
cout << cur->val_ << " ";
}
cur = cur->right;
}
}
完整代码
#include <iostream>
using namespace std;
struct Treenode
{
int val_;
Treenode *left;
Treenode *right;
Treenode(): val_(0), left(nullptr), right(nullptr) {}
Treenode(int val): val_(val), left(nullptr), right(nullptr) {}
~Treenode() {
if (left) delete left;
if (right) delete right;
val_ = 0;
}
};
Treenode *insert(Treenode *root, int val) //递归创建二叉树
{
if (root == nullptr)
return new Treenode(val);
if (root->val_ > val)
{
root->left = insert(root->left, val);
} else if (root->val_ < val)
{
root->right = insert(root->right, val);
}
return root;
}
void pre_dis(Treenode *root) { //递归先序遍历二叉树
if (root == nullptr)
return;
cout << root->val_ << " ";
pre_dis(root->left);
pre_dis(root->right);
}
/*
二叉树结构
5
3 7
1 4 8 10
pre_morris遍历情况
前序遍历是先打印当前节点然后 再去处理左右子树
我们只需要将访问数据的位置改变就行
我们访问数据的情况有两种 1.不断向左遍历(5,3,7) 2.无左子树(1,4,8,10)
*/
void pre_morris(Treenode *root) {
Treenode *cur = root;
Treenode *cur_left = nullptr;
while (cur) {
// cout<<cur->val_<<" "<<endl; 避免重复 我们需要检测节点左子树是否连接过 所以我们不能直接访问cur节点
cur_left = cur->left;
if (cur_left != nullptr) {
while (cur_left->right && cur_left->right != cur) {
cur_left = cur_left->right;
}
if (cur_left->right == nullptr) {
cur_left->right = cur;
cout << cur->val_ << " ";
cur = cur->left;
continue;
} else {
cur_left->right = nullptr;
}
} else {
cout << cur->val_ << " ";
}
cur = cur->right;
}
}
int main() {
Treenode *root = new Treenode(5);
insert(root, 3);
insert(root, 1);
insert(root, 4);
insert(root, 7);
insert(root, 10);
insert(root, 8);
pre_dis(root); //先序递归
cout << endl;
pre_morris(root); //morris先序
cout << endl;
pre_dis(root); //先序递归检验二叉树结构
delete root;
return 0;
}
运行代码:
morris中序遍历
先序和中序的差别就是访问数据位置的区别
我们知道我们基本的morris不断的处理左子树,到达最左节点后不断右移,所以我们只需要在cur节点右移的时候访问数据就行
/*
二叉树结构
5
3 7
1 4 10
mid_morris遍历情况
4->right=5 处理5左子树 最右子节点和根节点连接
1->right=3 处理3左子树
到达1节点此时没有左子树返回之前连接的根节点3 打印 1
此时左子树不为空但是此时的左子树的最右节点已经访问过,我们将左子树还原后 打印3
此时到达节点4无左子树,打印4 ,返回到连接的根节点 5
同理检查5节点左子树是否连接过(是否访问过) 置空4->right 打印5
此时到达节点7无左子树,直接打印 7
到达10节点 无左子树打印 10
此时遍历顺序为 1 3 4 5 7 10
总结:
1.不断将节点和其左子树最右节点连接 以实现返回上一节点
2.处理完毕后,cur=cur->right,往上移动,或者向右移动
*/
void mid_morris(Treenode *root)
{
Treenode *cur = root;
Treenode *cur_left = nullptr;
while (cur != nullptr)
{
cur_left = cur->left;
//左子树非空 则连接左子树最右子节点和cur节点
if (cur_left != nullptr)
{
while (cur_left->right != nullptr && cur_left->right != cur)
{
cur_left = cur_left->right;
}
if (cur_left->right == nullptr) //如果该最右子节点未连接
{
cur_left->right = cur; //连接相应的根节点
cur = cur->left; //继续处理左子树
continue;
}
else //表示该节点左子树连接过,也就是访问过了,我们将它还原
{
cur_left->right = nullptr;
}
}
//左子树为空 或者还原二叉树
cout << cur->val_ << " "; //访问节点数据
cur = cur->right; //该节点的右节点 返回连接过的根节点 或者 进入右孩子
}
}
完整代码:
#include <iostream>
using namespace std;
struct Treenode {
int val_;
Treenode *left;
Treenode *right;
Treenode(): val_(0), left(nullptr), right(nullptr) {}
Treenode(int val): val_(val), left(nullptr), right(nullptr) {}
~Treenode() {
if (left)
delete left;
if (right)
delete right;
val_ = 0;
}
};
Treenode *insert(Treenode *root, int val) {
if (root == nullptr)
return new Treenode(val);
if (root->val_ > val) {
root->left = insert(root->left, val);
} else if (root->val_ < val) {
root->right = insert(root->right, val);
}
return root;
}
void mid_dis(Treenode *root) {
if (root == nullptr)
return;
mid_dis(root->left);
cout << root->val_ << " ";
mid_dis(root->right);
}
/*
二叉树结构
5
3 7
1 4 10
mid_morris遍历情况
4->right=5 处理5左子树 最右子节点和根节点连接
1->right=3 处理3左子树
到达1节点此时没有左子树返回之前连接的根节点3 打印 1
此时左子树不为空但是此时的左子树的最右节点已经访问过,我们将左子树还原后 打印3
此时到达节点4无左子树,打印4 ,返回到连接的根节点 5
同理检查5节点左子树是否连接过(是否访问过) 置空4->right 打印5
此时到达节点7无左子树,直接打印 7
到达10节点 无左子树打印 10
此时遍历顺序为 1 3 4 5 7 10
总结:
1.不断将节点和其左子树最右节点连接 以实现返回上一节点
2.处理完毕后,cur=cur->right,往上移动,或者向右移动
*/
void mid_morris(Treenode *root)
{
Treenode *cur = root;
Treenode *cur_left = nullptr;
while (cur != nullptr)
{
cur_left = cur->left;
//左子树非空 则连接左子树最右子节点和cur节点
if (cur_left != nullptr)
{
while (cur_left->right != nullptr && cur_left->right != cur)
{
cur_left = cur_left->right;
}
if (cur_left->right == nullptr) //如果该最右子节点未连接
{
cur_left->right = cur; //连接相应的根节点
cur = cur->left; //继续处理左子树
continue;
}
else //表示该节点左子树连接过,也就是访问过了,我们将它还原
{
cur_left->right = nullptr;
}
}
//左子树为空 或者还原二叉树
cout << cur->val_ << " "; //访问节点数据
cur = cur->right; //该节点的右节点 返回连接过的根节点 或者 进入右孩子
}
}
int main() {
Treenode *root = new Treenode(5);
insert(root, 3);
insert(root, 1);
insert(root, 4);
insert(root, 7);
insert(root, 10);
mid_dis(root); //中序递归
cout << endl;
mid_morris(root); //中序morris//中序递归 检验二叉树结构
cout << endl;
mid_dis(root); //中序递归 检验二叉树结构
delete root;
return 0;
}
运行结果:
morris后序遍历
后序遍历稍微难点,因为根节点是最后访问
1.返回连接节点cur时,访问反转的cur->left右链表
2.最后root右链表
5
3 7
1 4 10
8
准备一个vector<int> dp 存后序遍历结果
我们在判断连接时也就是回到根节点时访问根节点的左子树的右反转链表
当我们通过1返回3节点时 此时3的左子树只有1 那么将他存入dp中 dp:1
然后4返回 5节点时 此时5左子树的右链表为 3 4,反转后为 4 3 然后存入 dp,后再反转还原二叉树结 dp:1 4 3
当8节点返回10 只有一个节点8 存入 dp:1 4 3 8
最后处理root右链表 5 7 10 反转后10 7 5 存入dp,恢复反转 dp:1 4 3 8 10 7 5
//链表反转
Treenode *reverse_tree(Treenode *root) {
if (root == nullptr)
return nullptr;
treenode *cur = root;
treenode *head = nullptr;
while (cur) { //头插法反转节点链表
treenode *next = cur->right;
cur->right = head;
head = cur;
cur = next;
}
return head;
}
//访问数据
void last_visit(Treenode *root, vector<int> &s) {
if (root == nullptr)
return;
Treenode *head = reverse_tree(root); //反转节点链表
Treenode *cur = head;
while (cur) {
s.push_back(cur->val_);
cur = cur->right;
}
head = reverse_tree(head); //还原节点
}
//morris后序遍历
void last_morris(Treenode *root) {
if (root == nullptr)
return;
treenode *cur = root;
treenode *cur_left = nullptr;
vector<int> s;
while (cur)
{
cur_left = cur->left;
if (cur_left != nullptr)
{
while (cur_left->right && cur_left->right != cur)
{
cur_left = cur_left->right;
}
if (cur_left->right == nullptr)
{
cur_left->right = cur;
cur = cur->left;
continue;
}
else
{
cur_left->right = nullptr;
last_visit(cur->left, s);
}
}
cur = cur->right;
}
last_visit(root, s);
for (auto ie : s) {
cout << ie << " ";
}
}
完整代码:
#include <iostream>
#include <vector>
using namespace std;
typedef struct treenode {
int val_;
treenode *left;
treenode *right;
treenode(): val_(0), left(nullptr), right(nullptr) {}
treenode(int val): val_(val), left(nullptr), right(nullptr) {}
~treenode() {
if (left)
delete left;
if (right)
delete right;
val_ = 0;
}
} Treenode;
Treenode *insert(Treenode *root, int val) {
if (root == nullptr)
return new Treenode(val);
if (root->val_ > val) {
root->left = insert(root->left, val);
} else if (root->val_ < val) {
root->right = insert(root->right, val);
}
return root;
}
void last_dis(Treenode *root) {
if (root == nullptr)
return;
last_dis(root->left);
last_dis(root->right);
cout << root->val_ << " ";
}
Treenode *reverse_tree(Treenode *root) {
if (root == nullptr)
return nullptr;
treenode *cur = root;
treenode *head = nullptr;
while (cur) { //头插法反转节点链表
treenode *next = cur->right;
cur->right = head;
head = cur;
cur = next;
}
return head;
}
void last_visit(Treenode *root, vector<int> &s) {
if (root == nullptr)
return;
Treenode *head = reverse_tree(root); //反转节点链表
Treenode *cur = head;
while (cur) {
s.push_back(cur->val_);
cur = cur->right;
}
head = reverse_tree(head); //还原节点
}
void last_morris(Treenode *root) {
if (root == nullptr)
return;
treenode *cur = root;
treenode *cur_left = nullptr;
vector<int> s;
while (cur)
{
cur_left = cur->left;
if (cur_left != nullptr)
{
while (cur_left->right && cur_left->right != cur)
{
cur_left = cur_left->right;
}
if (cur_left->right == nullptr)
{
cur_left->right = cur;
cur = cur->left;
continue;
}
else
{
cur_left->right = nullptr;
last_visit(cur->left, s);
}
}
cur = cur->right;
}
last_visit(root, s);
for (auto ie : s) {
cout << ie << " ";
}
}
int main() {
Treenode *root = new treenode(5);
insert(root, 3);
insert(root, 1);
insert(root, 4);
insert(root, 7);
insert(root, 10);
insert(root, 8);
last_dis(root); //后序递归
cout << endl;
last_morris(root); //morris后序
cout << endl;
last_dis(root); //后序递归检验二叉树结构
delete root;
return 0;
}
运行结果: