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

C语言数据结构:链表的操作实现

本文包括链表的基本操作:初始化、头插法、尾插法、遍历打印、获取尾结点地址、指定位置添加和删除结点、获取链表长度、得到尾指针、释放链表、获得倒数第K个结点的值(快慢指针法)、翻转链表。

在链表的学习中(个人觉得)我们需要注意的几点:

1、结点类型声明的格式,指针域不可以使用别名取声明,

2、指针域的熟悉,要懂得L->next的含义,看到后知道其内容是什么意思。

3、循环的临界条件判断,需要多次写代码去熟悉。

4、多学习技巧,去降低时间复杂度,如快慢指针。

快慢指针用法(后续还会补充):

1.找到倒数第K个值:快指针先走K步,循环向链表尾部走,直到指向NULL,这时慢指针就会指向想要的结点。

2、寻找链表的中点(也可以找到其他比例的点去分割链表):快指针一次向尾部走两步,慢指针走一步,直到指向NULL,这时慢指针就会指向中间结点(偶数链表会指向中间的上一个,奇数会指向中间值)。

3.链表是否循环:通过快2步慢1步的方法、判断地址是否有重复的地方。

#include <stdio.h>
#include <stdlib.h>
typedef int Elemtype;
// 正确认识 typedef的重要性, 如果需要把成千上万的int 改为double 会有很大工作量,
// 可以事先在表头定义typedef int i,修改时只需要把int 改为double
typedef struct node
{
    Elemtype data;     // 数据域
    struct node *next; // 指针域
} Node;
Node *Node_init();                                 // 链表初始化 分配空间
void insertHead(Node *L, Elemtype e);              // 头插法
void listNode(Node *L);                            // 遍历链表(应该使用一个临时指针来遍历链表,而不是修改传入的头节点指针 L)
Node *get_tail(Node *L);                           // 获取尾结点的地址
Node *insertTail(Node *tail, Elemtype e);          // 在尾结点后插入一个数据
int insertNode(Node *L, int location, Elemtype e); // 在特定位置插入数据
int deletNode(Node *L, int location);              // 删除特定位置结点
int getlength(Node *L);                            // 获取链表长度
void freeList(Node *L);                            // 释放链表
int findNode(Node *L, Elemtype k);                 // 找到倒数第K个值
Node *reverslist(Node *L);                         // 翻转链表
Node *head;                                        // 定义头指针 在init初始化函数中对头指针分配空间
int main(int argc, char const *argv[])
{
    head = Node_init();  // 带头节点  链表初始化
    insertHead(head, 3); // 头插法
    insertHead(head, 2);
    insertHead(head, 1);
    Node *tail = get_tail(head); // 得到尾指针
    tail = insertTail(tail, 4);  // 尾插法 返回新的尾指针
    tail = insertTail(tail, 5);
    insertTail(tail, 6);   // 尾指针不变动
    listNode(head);        // 遍历链表
    Node *hd;              // 创建新的指针
    hd = reverslist(head); // 翻转链表
    listNode(hd);          // 遍历链表

    return 0;
}
Node *Node_init()
{
    Node *head = (Node *)malloc(sizeof(Node)); // 为头节点分配空间
    head->data = 0;                            // 初始化值为0
    head->next = NULL;                         // 初始化的指针为空
    return head;                               // 返回头指针
}
void insertHead(Node *L, Elemtype e)
{
    Node *p = (Node *)malloc(sizeof(Node)); // 要插入的结点分配空间
    p->next = L->next;                      // 新结点指向原头结点
    p->data = e;                            // 新结点赋值
    L->next = p;                            // 头指针指向新头结点
}
void listNode(Node *L)
{
    Node *p = L->next; // 因为头指针只能指向头结点,所以需要新的指针来遍历数组
    while (p != NULL)  // 尾结点的指针与为空,所以可以作为循环的条件
    {
        printf("%d ", p->data); // 打印新的指针指向的数据与
        p = p->next;            // 指针指向下一个结点
    }
    printf("\n");
}
Node *get_tail(Node *L)
{
    Node *p = L;            // 因为头指针只能指向头结点,所以需要新的指针来遍历数组
    while (p->next != NULL) // 尾结点的指针与为空,所以可以作为循环的条件 直到指向尾结点
    {
        p = p->next;
    }
    return p;
}
Node *insertTail(Node *tail, Elemtype e)
{
    Node *p = (Node *)malloc(sizeof(Node)); // 为新的尾结点分配空间
    tail->next = p;                         // 原尾结点指向新的尾结点,衔接
    p->data = e;                            // 新结点数据与赋值
    p->next = NULL;                         // 新尾结点指向空
    return p;
}
int insertNode(Node *L, int location, Elemtype e)
{
    Node *p = (Node *)malloc(sizeof(Node)); // 为新的结点分配空间
    for (int i = 0; i < location - 1; i++)  // 循环location-1次到达目标前一个位置,然后进行插入
    {
        L = L->next;
        if (L == NULL)
        {
            return 0;
        }
    }
    p->next = L->next;
    p->data = e;
    L->next = p;
}
int deletNode(Node *L, int location)
{
    Node *p = L;                           // 创建q指针,最后指向要被删除的结点
    for (int i = 0; i < location - 1; i++) // 循环location-1次到达目标前一个位置,然后进行删除
    {
        p = p->next;
        if (p->next == NULL) // 判断下一个结点(要被删除的结点)是否为空
        {
            printf("要删除的位置错误");
            return 1;
        }
    }
    Node *q = p->next; // q指向被删除的结点
    p->next = q->next; // p指向下一个结点,将q从链表中断开
    free(q);           // 释放删除结点的空间
    return 0;
}
int getlength(Node *L)
{
    Node *p = L;
    int count = 0;
    while (p != NULL) // 让计数器跟随指针自增,直到尾结点
    {
        p = p->next;
        count++;
    }
    return count; // 返回长度
}
void freeList(Node *L)
{
    Node *p = L->next;      // 定义p指针 指向头结点
    Node *q;                // 定义q指针
    while (p->next != NULL) // 检查是否到尾结点
    {
        q = p->next; // q指针指向p的下一个结点
        free(p);     // 释放p指向结点的内存
        p = q;       // p追随q
    }
    L->next = NULL; // 清空链表后,使头指针指向空
}
int findNode(Node *L, Elemtype k)
{
    Node *f = L->next; // 快指针
    Node *s = L->next; // 慢指针
    int len = 0;
    if (k > (len = getlength(head)))
    {
        printf("输入值不合法");
    }
    else
    {
        for (int i = 0; i < k; i++) // 让快指针先走k步
        {
            f = f->next;
        }
        while (f->next != NULL) // 当快指针指向末尾,慢指针所指就是倒数第K个结点
        {
            f = f->next;
            s = s->next;
        }
        printf("%d \n", s->data); // 打印倒数K结点的值
    }
    return 0;
}
Node *reverslist(Node *L)
{                           // 定义三个指针,使链表的指针方向翻转,达到翻转链表的目的
    Node *first = NULL;     // 1指针先作为新链表的尾指针指向空
    Node *second = L->next; // 2指针指向头结点
    Node *third;
    while (second != NULL) // 最后1指针指向原尾结点
    {
        third = second->next; // 3指针指向2的下一个结点
        second->next = first; // 2所指向的结点的指针域指1,翻转指针的指向
        first = second;       // 1 2两个指针下移
        second = third;
    }
    Node *hd = Node_init(); // 新建链表头指针,并初始化
    hd->next = first;       // 新指针指向新的头结点

    return hd; // 返回新链表的头指针
}

推荐某站孙老师的课程:(有详细的C语言代码,不是伪代码!!!)《数据结构(C 语言描述)》也许是全站最良心最通俗易懂最好看的数据结构课(最迟每周五更新~~)_哔哩哔哩_bilibili


http://www.kler.cn/a/578497.html

相关文章:

  • STM32全系大阅兵(1)
  • Java Lambda 表达式在集合操作中的应用
  • 大语言模型下的多智能体协作机制研究综述
  • kettle工具使用从入门到精通(二)-------Java代码案例
  • 八字排盘宝 2025.1.8 | 多模式排盘工具,精准解析八字信息,轻量易用
  • 【机器学习chp11】聚类(K均值+高斯混合模型+层次聚类+基于密度的聚类DBSCAN+基于图的聚类+聚类的性能评价指标)
  • Unity之如何实现哔哩哔哩直播弹幕游戏
  • Python脚本,音频格式转换 和 视频格式转换
  • Pytorch 第八回:卷积神经网络——GoogleNet模型
  • SpringBoot 配置视图控制器
  • Android Activity的启动器ActivityStarter入口
  • 使用 Java 在后端 为 PDF 添加水印
  • 跟着 Lua 5.1 官方参考文档学习 Lua (11)
  • AtCoder ABC E - Min of Restricted Sum 题解
  • Etcd的安装与使用
  • Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现与实战指南
  • 通过定制initramfs实现从单系统分区到双系统的无缝升级
  • 手抖护理全攻略:从生活点滴改善症状
  • AI 智能:开拓未知疆域的科技先锋
  • 11-Agent中配置自己的插件