You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Morris中序遍历进行时实现二叉搜索查找?

Morris遍历期间的二叉搜索树查找问题

Morris中序遍历是二叉搜索树的一种中序遍历方法,仅使用*O(1)*内存(无需递归),但会临时修改(后续恢复)树的部分->right指针。

示例C代码:

#include <stdio.h>  /* printf(). */
#include <stdlib.h>  /* NULL. */
struct node {
    struct node *left, *right;
    int data;
};
void traverse(struct node *root) {
    struct node *current = root;
    struct node *pre;
    while (current != NULL) { 
        if (current->left == NULL) goto process_node;
        /* 找到current的中序前驱节点 */
        pre = current->left;
        while (pre->right != NULL && pre->right != current) {
            pre = pre->right;
        }
        if (pre->right == NULL) {
            /* 将current设为其中序前驱的右子节点 */
            pre->right = current;  /* 临时修改树结构 */
            current = current->left;
        } else {
            /* 恢复之前修改的指针,还原树结构 */
            pre->right = NULL;
          process_node:
            printf("%d ", current->data);
            /* find(root, current->data + 2); */
            /* find(root, current->data - 2); */
            current = current->right;
        }
    }
}

但由于这种临时修改,额外的二叉树值查找无法正常工作。比如取消上述代码中find(...)调用的注释后,下面的普通find实现会在遍历期间陷入无限循环:

int datacmp(int a, int b) {  /* 升序规则:返回-1表示a < b */
    return a < b ? -1 : a > b;
}

struct node *find(struct node *root, int data) {
    struct node *explore = root;
    int c;
    while (explore != NULL) {
        c = datacmp(data, explore->data);
        if (c == 0) {
            return explore;
        } else if (c < 0) {
            explore = explore->left;
        } else {
            explore = explore->right;
        }
    }
    return NULL;
}

请问是否存在可在Morris中序遍历进行时正常工作的find实现?


解决方案

当然存在可行的find实现,核心思路是在查找过程中识别出Morris遍历创建的临时right指针,避免陷入循环。

方法1:修改find函数,检测临时指针

由于Morris遍历中,临时right指针总是指向某个祖先节点(当前节点的后继),我们可以在查找时跟踪路径,避免回到已经访问过的祖先节点:

struct node *find_morris_safe(struct node *root, int data) {
    struct node *explore = root;
    struct node *prev = NULL;
    int c;
    while (explore != NULL) {
        // 检测是否是临时指针:当前节点的右指针指向祖先,且不是正常的BST右子树
        if (explore->right == prev) {
            // 遇到了Morris临时指针,直接终止这条路径的查找
            break;
        }
        c = datacmp(data, explore->data);
        if (c == 0) {
            return explore;
        } else if (c < 0) {
            prev = explore;
            explore = explore->left;
        } else {
            prev = explore;
            explore = explore->right;
        }
    }
    return NULL;
}

这个实现通过记录上一个访问的节点prev,如果当前节点的right指针指向prev,说明这是Morris遍历创建的临时指针,此时这条路径不存在目标值,直接退出循环。

方法2:遍历期间维护树的状态标记

如果可以修改节点结构,我们可以给每个节点添加一个标记位,标识当前right指针是否是临时修改的:

struct node {
    struct node *left, *right;
    int data;
    int is_temp_right; // 1表示right是临时指针,0表示原始指针
};

然后在Morris遍历的修改和恢复步骤中更新这个标记:

// 修改临时指针时
pre->right = current;
pre->is_temp_right = 1;

// 恢复时
pre->right = NULL;
pre->is_temp_right = 0;

对应的find函数就可以根据这个标记跳过临时指针:

struct node *find_morris_safe(struct node *root, int data) {
    struct node *explore = root;
    int c;
    while (explore != NULL) {
        c = datacmp(data, explore->data);
        if (c == 0) {
            return explore;
        } else if (c < 0) {
            explore = explore->left;
        } else {
            // 如果是临时指针,说明这条路径没有目标值
            if (explore->is_temp_right) {
                break;
            }
            explore = explore->right;
        }
    }
    return NULL;
}

方法3:遍历期间延迟查找操作

另一种思路是把需要执行的查找操作缓存起来,等Morris遍历完成、树结构完全恢复后再批量执行。这种方法不需要修改find函数,但会牺牲实时性:

// 定义缓存结构体
#define MAX_QUERIES 100
struct query {
    int data;
    struct node **result;
};
struct query query_cache[MAX_QUERIES];
int query_count = 0;

// 修改traverse函数,缓存查询
void traverse(struct node *root) {
    struct node *current = root;
    struct node *pre;
    while (current != NULL) { 
        if (current->left == NULL) goto process_node;
        pre = current->left;
        while (pre->right != NULL && pre->right != current) {
            pre = pre->right;
        }
        if (pre->right == NULL) {
            pre->right = current;
            current = current->left;
        } else {
            pre->right = NULL;
          process_node:
            printf("%d ", current->data);
            // 缓存查询,而不是立即执行
            if (query_count < MAX_QUERIES) {
                query_cache[query_count].data = current->data + 2;
                query_cache[query_count].result = malloc(sizeof(struct node*));
                query_count++;
                query_cache[query_count].data = current->data - 2;
                query_cache[query_count].result = malloc(sizeof(struct node*));
                query_count++;
            }
            current = current->right;
        }
    }
    // 遍历完成后执行所有查询
    for (int i = 0; i < query_count; i++) {
        *query_cache[i].result = find(root, query_cache[i].data);
        // 处理查询结果...
    }
}

这三种方法各有优缺点:方法1不需要修改节点结构,实现简单;方法2逻辑更清晰,但需要修改树的节点定义;方法3完全复用原find函数,但无法实时获取查询结果。

内容的提问来源于stack exchange,提问作者pts

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 01:05:18