当前位置: 首页 > news >正文

深入链表操作:C语言中的链表中间节点查找与合并

目录

链表的基本结构

尾插法建立链表

打印链表

找到链表中间节点 

原地逆置链表

合并两个链表

主函数

示例运行

输出示例

总结

关键点回顾


在数据结构的学习中,链表是一种非常重要的线性结构。它的动态特性使得在插入和删除操作时比数组更为高效。今天,我们将探讨如何在链表中找到中间节点,并将链表的后半部分逆置后与前半部分合并。通过这篇博客,你将更深入地理解链表的实现和操作。

链表的基本结构

在C语言中,链表的基本结构由节点(Node)构成,每个节点包含数据部分和指向下一个节点的指针。我们首先定义一个链表节点的结构体:

typedef int ElemType;

typedef struct node {
    int data;
    struct node *next;
} Node;

这里,Node结构体包含了数据和指向下一个节点的指针。

尾插法建立链表

我们使用尾插法来创建链表。以下是尾插法的实现:

void list_tail_insert(Node *&L) {
    L = (Node *) malloc(sizeof(Node));
    L->next = NULL;
    ElemType x;
    scanf("%d", &x);
    Node *s, *r = L; // s 用来指向申请的新结点 r 始终指向链表尾部
    while (x != 9999) {
        s = (Node *) malloc(sizeof(Node)); // 为新结点申请空间
        s->data = x;
        r->next = s; // 新结点给尾结点的next指针
        r = s; // r 要指向新的尾部
        scanf("%d", &x);
    }
    r->next = NULL; // 让尾结点的 next 为 Null
}

在这个函数中,我们创建一个头节点,并通过循环读取用户输入的数据,直到输入9999为止。每次读取到一个新数据时,我们都会创建一个新节点,并将其插入到链表的尾部。

打印链表

为了验证我们的链表操作是否成功,我们需要一个函数来打印链表的内容:

void print_list(Node *L) {
    L = L->next; // 跳过头结点
    while (L != NULL) {
        printf("%d ", L->data);
        L = L->next;
    }
    printf("\n");
}

这个函数从头节点的下一个节点开始遍历链表,打印每个节点的数据。 

找到链表中间节点 

我们实现了一个函数来找到链表的中间节点,并将后半部分链表设置为新的链表L2: 

void find_middle(Node *L, Node *&L2) {
    L2 = (Node *) malloc(sizeof(Node)); // 第二条链表的头节点
    Node *p_cur, *p_pre; // 双指针法
    p_pre = p_cur = L->next;
    while (p_cur) {
        p_cur = p_cur->next;
        if (NULL == p_cur) {
            break;
        }
        p_cur = p_cur->next;
        if (NULL == p_cur) { // 为了使得偶数个 p_pre 依然指向中间节点
            break;
        }
        p_pre = p_pre->next;
    }
    L2->next = p_pre->next; // L2 头节点指向后面的一半链表
    p_pre->next = NULL; // 前一半链表的最后一个节点 next 要为 NULL
}

在这个函数中,我们使用双指针法来找到链表的中间节点。p_cur每次移动两步,而p_pre每次移动一步。当p_cur到达链表末尾时,p_pre正好指向中间节点。我们将后半部分链表的头节点指向L2。 

原地逆置链表

接下来,我们实现一个函数来逆置链表L2

void reverse(Node *L2) {
    Node *r, *s, *t;
    r = L2->next;
    if (NULL == r) {
        return; // 链表为空
    }
    s = r->next;
    if (NULL == s) {
        return; // 链表只有一个节点
    }
    t = s->next;
    while (t) {
        s->next = r; // 原地逆置
        r = s; // 更新指针
        s = t;
        t = t->next; // 移动到下一个节点
    }
    s->next = r; // 最后一个节点指向原链表的头
    L2->next->next = NULL; // 逆置后,链表第一个节点的 next 要为 NULL
    L2->next = s; // s 是链表的第一个节点,L2 指向它
}

在这个reverse函数中,我们使用三个指针rst来实现链表的原地逆置。r指向当前逆置后的链表的头,s指向当前正在处理的节点,t指向下一个节点。通过不断更新这些指针,我们可以将链表的顺序反转。 

合并两个链表

最后,我们实现一个函数来合并两个链表LL2

void merge(Node *L, Node *L2) {
    Node *p_cur, *p, *q;
    p_cur = L->next; // p_cur 始终指向组合后的链表的链表尾
    p = p_cur->next; // p 指向 L 的第二个节点
    q = L2->next; // q 指向 L2 第一个节点
    while (p != NULL && q != NULL) {
        p_cur->next = q; // 将 L2 的节点连接到合并链表
        q = q->next; // q 指向下一个
        p_cur = p_cur->next; // 更新合并链表的尾部
        p_cur->next = p; // 将 L 的节点连接到合并链表
        p = p->next; // p 指向下一个
        p_cur = p_cur->next; // 更新合并链表的尾部
    }
    // 处理剩余的节点
    if (p != NULL) {
        p_cur->next = p; // 如果 L 还有剩余节点,连接到合并链表
    }
    if (q != NULL) {
        p_cur->next = q; // 如果 L2 还有剩余节点,连接到合并链表
    }
}

在这个merge函数中,我们通过指针p_curpq来合并两个链表。p_cur始终指向合并后的链表的尾部,p指向链表L的当前节点,q指向链表L2的当前节点。我们交替将两个链表的节点连接到合并链表中,直到一个链表遍历完。

主函数

最后,我们在main函数中调用这些操作,构建链表并进行合并操作:

int main() {
    Node *L; // 链表头 是结构体指针类型
    list_tail_insert(L); // 尾插法输入数据
    print_list(L); // 打印链表

    // 寻找中间节点 并返回第二条链表
    Node *L2 = NULL;
    find_middle(L, L2); // 将后半部分链表设置为 L2
    printf("-------------------------------------\n");
    print_list(L); // 打印前半部分链表
    reverse(L2); // 逆置后半部分链表
    print_list(L2); // 打印逆置后的后半部分链表
    printf("-------------------------------------\n");
    merge(L, L2); // 合并两个链表
    free(L2); // 释放 L2 的内存
    print_list(L); // 打印合并后的链表
    return 0;
}

main函数中,我们首先定义一个链表L,然后调用list_tail_insert函数来填充链表。接着,我们使用print_list函数打印链表的内容,以验证插入操作的正确性。

示例运行

假设用户输入以下数据来创建链表:

1
2
3
4
5
9999

在这种情况下,链表的初始状态为:1 -> 2 -> 3 -> 4 -> 5。接着,我们进行以下操作:

  1. 找到中间节点并将后半部分链表设置为L2,此时L2将包含4 -> 5
  2. 逆置L2,结果为5 -> 4
  3. 合并LL2,最终链表将变为:`1 ->2 -> 3 -> 5 -> 4

输出示例

假设用户输入了上述数据,程序的输出将类似于:

1 2 3 4 5 
-------------------------------------
1 2 3 
5 4 
-------------------------------------
1 2 3 5 4 

在这个输出中,我们可以看到:

  1. 初始链表:用户输入的链表为1 2 3 4 5
  2. 分割后的链表:在找到中间节点后,前半部分链表为1 2 3,后半部分链表L24 5,经过逆置后变为5 4
  3. 合并后的链表:最终合并后的链表为1 2 3 5 4,展示了合并操作的结果。

总结

通过这篇博客,我们深入探讨了链表的基本操作,包括尾插法、打印链表、找到中间节点、逆置链表和合并两个链表。链表的灵活性使得它在许多应用中都非常有用,尤其是在需要频繁插入和删除操作的场景中。

关键点回顾

  1. 双指针法:使用双指针法有效地找到链表的中间节点,避免了多次遍历。
  2. 原地逆置:通过指针操作实现链表的原地逆置,节省了额外的空间。
  3. 合并操作:交替合并两个链表,确保合并后的链表保持顺序。

希望这篇文章能帮助你更好地理解链表的实现和操作。如果你有任何问题或想法,欢迎在评论区留言讨论!链表的世界充满了可能性,继续探索吧!

相关文章:

  • 《Android 平台架构系统启动流程详解》
  • sparkTTS window 安装
  • MFC 项目:简易销售系统实践
  • MoonSharp 文档四
  • LLM学习之路-01-第一章-预训练/搞懂大模型的分词器(二)
  • electron builder打包时,出现errorOut=ERROR: Cannot create symbolic link
  • Talking Head Review (数字人算法综述)
  • Django 初始化导入数据详解
  • 数据结构:有序表的合并
  • Unity 扩散式布局
  • Unity 带阻尼感的转盘
  • 数智读书笔记系列015 探索思维黑箱:《心智社会:从细胞到人工智能,人类思维的优雅解读》读书笔记
  • Openlayer+天地图+山东天地图
  • Html5学习教程,从入门到精通, HTML5超链接应用的详细语法知识点和案例代码(18)
  • uni-app+vue3学习随笔
  • 深度学习PyTorch之数据加载DataLoader
  • KafkaRocketMQ
  • AI智能导航站HTML5自适应源码帝国cms7.5模板
  • word甲烷一键下标
  • 08 HarmonyOS NEXT 仿uv-ui Tag组件开发教程系列(二)
  • 翻越高山,成为高山!浙江广厦成CBA历史第八支夺冠球队
  • 广东茂名信宜出现龙卷,一家具厂铁皮房受损
  • 研究显示:肺活量衰减始于20至25岁
  • 王毅同丹麦外交大臣拉斯穆森会谈
  • 小米法务部:犯罪团伙操纵近万账号诋毁小米,该起黑公关案告破
  • 完善劳动关系协商协调机制,《共同保障劳动者合法权益工作指引》发布