【C语言】深入浅出:C语言链表的全面解析

news2024/12/23 18:46:26

LuckiBit

目录

    • 一、单链表
      • 1. 基本概念
        • 节点结构定义
      • 2. 创建链表
        • 示例代码
        • 输出结果
      • 3. 插入节点
        • 示例代码
        • 输出结果
      • 4. 删除节点
        • 示例代码
        • 输出结果
    • 二、双向链表
      • 1. 基本概念
        • 节点结构定义
      • 2. 创建双向链表
        • 示例代码
        • 输出结果
      • 3. 插入节点
        • 示例代码
        • 输出结果
      • 4. 删除节点
        • 示例代码
        • 输出结果
    • 三、循环链表
      • 1. 基本概念
        • 节点结构定义
      • 2. 创建循环链表
        • 示例代码
        • 输出结果
      • 3. 插入节点
        • 示例代码
        • 输出结果
      • 4. 删除节点
        • 示例代码
        • 输出结果
    • 四、链表的优缺点与应用
      • 1. 优点
      • 2. 缺点
      • 3. 常见应用
    • 五、总结
    • 六、结束语
    • 相关文章:

链表是一种常见的数据结构,由一系列节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。链表的最大特点是节点在内存中不必连续存储,因而在插入和删除操作时更加高效。下面我们将详细讲解C语言中单链表、双向链表和循环链表的基本概念、实现方法及其相关操作。

以下是本文中提到的重要内容及其简要描述的表格:

内容描述
单链表(Singly Linked List)每个节点包含一个数据域和一个指针域,指向下一个节点。头节点指向链表的第一个节点,尾节点指向 NULL
双向链表(Doubly Linked List)每个节点包含数据域、前驱指针和后继指针,允许双向遍历。前驱指针指向前一个节点,后继指针指向后一个节点。
循环链表(Circular Linked List)最后一个节点的指针域指向头节点,形成一个环形结构。可以是单向的或双向的。
创建节点动态分配内存,为链表创建新节点。
插入节点将新节点插入到链表中的特定位置或链表末尾。
删除节点从链表中移除特定节点,并释放相应的内存。
动态内存分配链表节点在运行时动态分配和释放内存,不需要在编译时指定大小。
插入和删除效率插入和删除操作效率高,在已知节点位置的情况下时间复杂度为 O(1)。
随机访问效率随机访问效率低,无法通过索引直接访问元素,必须从头开始遍历,时间复杂度为 O(n)。
应用常用于实现队列、栈、图和网络的表示等数据结构,以及内存管理中的空闲块管理。

一、单链表

1. 基本概念

单链表(Singly Linked List)是一种链表结构,其中每个节点包含一个数据域和一个指针域,指针域指向下一个节点。链表的第一个节点称为头节点,最后一个节点的指针域指向NULL,表示链表的结束。

节点结构定义
struct Node {
    int data;          // 数据域
    struct Node* next; // 指针域,指向下一个节点
};

2. 创建链表

示例代码
#include <stdio.h>
#include <stdlib.h>

// 节点结构定义
struct Node {
    int data;
    struct Node* next;
};

// 创建新节点
struct Node* createNode(int data) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    newNode->data = data;
    newNode->next = NULL;
    return newNode;
}

// 添加节点到链表末尾
void append(struct Node** headRef, int data) {
    struct Node* newNode = createNode(data);
    if (*headRef == NULL) {
        *headRef = newNode;
    } else {
        struct Node* temp = *headRef;
        while (temp->next != NULL) {
            temp = temp->next;
        }
        temp->next = newNode;
    }
}

// 打印链表
void printList(struct Node* head) {
    struct Node* temp = head;
    while (temp != NULL) {
        printf("%d -> ", temp->data);
        temp = temp->next;
    }
    printf("NULL\n");
}

int main() {
    struct Node* head = NULL;
    append(&head, 1);
    append(&head, 2);
    append(&head, 3);
    printList(head);
    return 0;
}
输出结果
1 -> 2 -> 3 -> NULL

3. 插入节点

示例代码
// 在特定位置插入新节点
void insertAfter(struct Node* prevNode, int data) {
    if (prevNode == NULL) {
        printf("前置节点不能为空\n");
        return;
    }
    struct Node* newNode = createNode(data);
    newNode->next = prevNode->next;
    prevNode->next = newNode;
}

int main() {
    struct Node* head = NULL;
    append(&head, 1);
    append(&head, 2);
    append(&head, 3);
    insertAfter(head->next, 4); // 在第二个节点后插入4
    printList(head);
    return 0;
}
输出结果
1 -> 2 -> 4 -> 3 -> NULL

4. 删除节点

示例代码
// 删除包含特定数据的节点
void deleteNode(struct Node** headRef, int key) {
    struct Node* temp = *headRef;
    struct Node* prev = NULL;

    if (temp != NULL && temp->data == key) {
        *headRef = temp->next;
        free(temp);
        return;
    }

    while (temp != NULL && temp->data != key) {
        prev = temp;
        temp = temp->next;
    }

    if (temp == NULL) return;

    prev->next = temp->next;
    free(temp);
}

int main() {
    struct Node* head = NULL;
    append(&head, 1);
    append(&head, 2);
    append(&head, 3);
    deleteNode(&head, 2); // 删除包含数据2的节点
    printList(head);
    return 0;
}
输出结果
1 -> 3 -> NULL

二、双向链表

1. 基本概念

双向链表(Doubly Linked List)是一种链表结构,其中每个节点包含三个部分:数据域、前驱指针域和后继指针域。前驱指针指向前一个节点,后继指针指向后一个节点。双向链表允许双向遍历。

节点结构定义
struct DNode {
    int data;
    struct DNode* prev; // 前驱指针
    struct DNode* next; // 后继指针
};

2. 创建双向链表

示例代码
#include <stdio.h>
#include <stdlib.h>

// 双向链表节点结构定义
struct DNode {
    int data;
    struct DNode* prev;
    struct DNode* next;
};

// 创建新节点
struct DNode* createDNode(int data) {
    struct DNode* newNode = (struct DNode*)malloc(sizeof(struct DNode));
    newNode->data = data;
    newNode->prev = NULL;
    newNode->next = NULL;
    return newNode;
}

// 添加节点到链表末尾
void appendD(struct DNode** headRef, int data) {
    struct DNode* newNode = createDNode(data);
    if (*headRef == NULL) {
        *headRef = newNode;
    } else {
        struct DNode* temp = *headRef;
        while (temp->next != NULL) {
            temp = temp->next;
        }
        temp->next = newNode;
        newNode->prev = temp;
    }
}

// 打印双向链表
void printDList(struct DNode* head) {
    struct DNode* temp = head;
    while (temp != NULL) {
        printf("%d <-> ", temp->data);
        temp = temp->next;
    }
    printf("NULL\n");
}

int main() {
    struct DNode* head = NULL;
    appendD(&head, 1);
    appendD(&head, 2);
    appendD(&head, 3);
    printDList(head);
    return 0;
}
输出结果
1 <-> 2 <-> 3 <-> NULL

3. 插入节点

示例代码
// 在特定节点后插入新节点
void insertAfterD(struct DNode* prevNode, int data) {
    if (prevNode == NULL) {
        printf("前置节点不能为空\n");
        return;
    }
    struct DNode* newNode = createDNode(data);
    newNode->next = prevNode->next;
    prevNode->next = newNode;
    newNode->prev = prevNode;
    if (newNode->next != NULL) {
        newNode->next->prev = newNode;
    }
}

int main() {
    struct DNode* head = NULL;
    appendD(&head, 1);
    appendD(&head, 2);
    appendD(&head, 3);
    insertAfterD(head->next, 4); // 在第二个节点后插入4
    printDList(head);
    return 0;
}
输出结果
1 <-> 2 <-> 4 <-> 3 <-> NULL

4. 删除节点

示例代码
// 删除包含特定数据的节点
void deleteDNode(struct DNode** headRef, int key) {
    struct DNode* temp = *headRef;

    while (temp != NULL && temp->data != key) {
        temp = temp->next;
    }

    if (temp == NULL) return;

    if (temp->prev != NULL) {
        temp->prev->next = temp->next;
    } else {
        *headRef = temp->next;
    }

    if (temp->next != NULL) {
        temp->next->prev = temp->prev;
    }

    free(temp);
}

int main() {
    struct DNode* head = NULL;
    appendD(&head, 1);
    appendD(&head, 2);
    appendD(&head, 3);
    deleteDNode(&head, 2); // 删除包含数据2的节点
    printDList(head);
    return 0;
}
输出结果
1 <-> 3 <-> NULL

三、循环链表

1. 基本概念

循环链表(Circular Linked List)是一种链表结构,其中最后一个节点的指针域指向链表的头节点,形成一个环形结构。循环链表可以是单向的或双向的。

节点结构定义
struct CNode {
    int data;
    struct CNode* next;
};

2. 创建循环链表

示例代码
#include <stdio.h>
#include <stdlib.h>

// 循环链表节点结构定义
struct CNode {
    int data;
    struct CNode* next;
};

// 创建新节点
struct CNode* createCNode(int data) {
    struct CNode* newNode = (struct CNode*)malloc(sizeof(struct CNode));
    newNode->data = data;
    newNode->next = newNode; // 初始时,节点的next指向自身
    return newNode;
}

// 添加节点到循环链表末尾
void appendC(struct CNode** headRef, int data) {
    struct CNode* newNode = createCNode(data);
    if (*headRef == NULL) {
        *headRef = newNode;
    } else {
        struct CNode* temp = *headRef;
        while (temp->next != *headRef) {
            temp = temp->next;
        }
        temp->next = newNode;
        newNode->next = *headRef;
    }
}

// 打印循环链表
void printCList(struct CNode* head) {
    if (head == NULL) return;
    struct CNode* temp = head;
    do {
        printf("%d -> ", temp->data);
        temp = temp->next;
    } while (temp != head);
    printf("(back to head)\n");
}

int main() {
    struct CNode* head = NULL;
    appendC(&head, 1);
    appendC(&head, 2);
    appendC(&head, 3);
    printCList(head);
    return 0;
}
输出结果
1 -> 2 -> 3 -> (back to head)

3. 插入节点

在循环链表中插入节点时,需要特别小心处理环的连接,以确保新节点正确地链接到链表中。

示例代码
// 在特定位置后插入新节点
void insertAfterC(struct CNode* prevNode, int data) {
    if (prevNode == NULL) {
        printf("前置节点不能为空\n");
        return;
    }
    struct CNode* newNode = createCNode(data);
    newNode->next = prevNode->next;
    prevNode->next = newNode;
}

int main() {
    struct CNode* head = NULL;
    appendC(&head, 1);
    appendC(&head, 2);
    appendC(&head, 3);
    insertAfterC(head->next, 4); // 在第二个节点后插入4
    printCList(head);
    return 0;
}
输出结果
1 -> 2 -> 4 -> 3 -> (back to head)

4. 删除节点

在循环链表中删除节点时,特别要注意处理头节点的删除和尾节点的循环连接。

示例代码
// 删除包含特定数据的节点
void deleteCNode(struct CNode** headRef, int key) {
    if (*headRef == NULL) return;

    struct CNode *curr = *headRef, *prev = NULL;

    // 处理头节点可能是目标节点的情况
    if (curr->data == key) {
        while (curr->next != *headRef) {
            curr = curr->next;
        }
        if (curr == *headRef) { // 链表只有一个节点
            free(*headRef);
            *headRef = NULL;
        } else { // 链表有多个节点
            curr->next = (*headRef)->next;
            free(*headRef);
            *headRef = curr->next;
        }
        return;
    }

    // 查找目标节点
    do {
        prev = curr;
        curr = curr->next;
    } while (curr != *headRef && curr->data != key);

    // 未找到目标节点
    if (curr == *headRef) return;

    // 解除目标节点
    prev->next = curr->next;
    free(curr);
}

int main() {
    struct CNode* head = NULL;
    appendC(&head, 1);
    appendC(&head, 2);
    appendC(&head, 3);
    deleteCNode(&head, 2); // 删除包含数据2的节点
    printCList(head);
    return 0;
}
输出结果
1 -> 3 -> (back to head)

四、链表的优缺点与应用

1. 优点

  1. 动态内存分配:链表可以在运行时动态分配和释放内存,不需要在编译时指定大小。
  2. 插入和删除效率高:在已知节点位置的情况下,链表的插入和删除操作非常高效,时间复杂度为 O(1)。

2. 缺点

  1. 额外的存储开销:每个节点需要存储指针,增加了内存使用。
  2. 随机访问不便:无法通过索引直接访问元素,必须从头开始遍历,时间复杂度为 O(n)。

3. 常见应用

  1. 实现数据结构:如队列、栈等。
  2. 内存管理:如动态内存分配器的空闲块管理。
  3. 图和网络的表示:如邻接表表示法。

五、总结

链表是一种重要的基础数据结构,适用于频繁的插入和删除操作。通过本篇详细讲解了单链表、双向链表和循环链表的概念和实现,以及各类常用操作的示例代码。掌握链表的使用对于理解更复杂的数据结构和算法具有重要意义。

六、结束语

  1. 本节内容已经全部介绍完毕,希望通过这篇文章,大家对C语言链表有了更深入的理解和认识。
  2. 感谢各位的阅读和支持,如果觉得这篇文章对你有帮助,请不要吝惜你的点赞和评论,这对我们非常重要。再次感谢大家的关注和支持!点我关注❤️

相关文章:

  • 指针的神秘探险:从入门到精通的奇幻之旅 !

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1965485.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

名师引路,育梦成光 | 云校名师张梁老师收到学员诚挚感谢信!

在这个蝉鸣悠长的夏日里&#xff0c;一封满载感恩之情的信笺&#xff0c;悄悄诉说着一段珍贵的师生情谊。近日&#xff0c;深圳云校又一届“HCIE精英班”圆满结课&#xff0c;临别之际&#xff0c;姜同学以笔为媒&#xff0c;向我们展示了一位不仅传授知识&#xff0c;更以人格…

在kdevelop中运行程序并调试

补充前序知识&#xff1a; 1.CMakeLists.txt文件中&#xff0c;如下图&#xff0c;第一行生成的是静态库文件&#xff08;我们前一讲所使用的&#xff09;&#xff0c;第二行是动态库文件。 静态库与动态库&#xff1a; 静态库&#xff08;Static Libraries&#xff09; 定义…

torch.minimum与torch.min()

目录 1. torch.minimum 2. torch.minimum可以进行一个数组与多维度的比较 3.torch.min() 4. torch.min()多维度比较 1. torch.minimum torch.minimum() 函数用于逐元素地比较两个张量&#xff0c;并返回一个新的张量&#xff0c;其中每个元素是两个输入张量对应元素中的最小…

pychar安装、pychon安装、pycharm超过试用期30激活

如果pycharm超过试用期&#xff0c;可以双击vbs脚本重新激活 百度网盘&#xff1a; 链接: https://pan.baidu.com/s/1B-XyLOy3wjVWbJwuvZOHOw?pwdmsb6 提取码: msb6

基于域名的虚拟主机、多虚拟主机的配置、基于ip的虚拟主机及上线静态的前端系统(商城系统)

Day 14 基于域名的虚拟主机 说明&#xff1a; 一个配置文件一般只有一个http模块&#xff1b; 一个http模块可以有多个server模块&#xff1b; 一个server模块就是一套web项目&#xff1b; 一个server模块中可以有多个location&#xff1b; location就是项目中的url路由…

判断元音还是辅音字母

1.// kiki开始学习英文字母&#xff0c;bobo老师告诉他&#xff0c;有五个字母A&#xff08;a&#xff09;,E(e),O(o),U(u),I(i)为元音&#xff0c;其他所有字母为辅音 //请帮他编写程序判断输入的字母是元音&#xff08;vowel&#xff09;还是辅音&#xff08;consonant&#…

使用这个Blender工具非破坏性地自动化切割面板线

"Panel Cutter"插件自动化了在Blender中沿选定边缘创建程序化面板线的过程&#xff0c;使其成为硬表面建模的必备工具。 这是一个设计用来非破坏性地自动化切割程序化面板线的小工具。这个工具对于硬表面模型&#xff0c;如汽车、船只和飞机来说&#xff0c;是救星。…

Linux用户-普通用户

作者介绍&#xff1a;简历上没有一个精通的运维工程师。希望大家多多关注我&#xff0c;我尽量把自己会的都分享给大家&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 Linux是一个多用户多任务操作系统,这意味着它可以同时支持多个用户登录并使用系统。…

Java:面向对象(static,代码块,构造方法私有化,内部类)

5&#xff0c;static关键字 static是一个特殊的关键字&#xff0c;static的作用是将实例成员变为类成员&#xff0c;只能修饰在类里定义的成员部分&#xff08;成员变量、方法、内部类&#xff08;枚举和接口&#xff09;、初始化块&#xff09;。static修饰的成员表明它属于这…

三,搭建环境:事务控制

三&#xff0c;搭建环境&#xff1a;事务控制 文章目录 三&#xff0c;搭建环境&#xff1a;事务控制声明式事务配置注解写法查询操作增删改操作 声明式事务配置 在 demo-module01-web 的模块下的&#xff0c;spring-persist.xml 配置文件中 开启基于注解的声明式事务支持 <…

专硕复试线298/295!哈尔滨理工大学计算机考研考情分析!

哈尔滨理工大学&#xff08;Harbin University of Science and Technology&#xff09;&#xff0c;位于哈尔滨市&#xff0c;是黑龙江省人民政府与国家国防科技工业局共建高校&#xff0c;入选“中西部基础能力建设工程”高校、国家“特色重点学科项目”建设高校、教育部“卓越…

论文速递 | Management Science 5月文章合集

编者按&#xff1a; 在本系列文章中&#xff0c;我们梳理了运筹学顶刊Management Science在2024年5月份发布有关OR/OM以及相关应用的9篇文章的基本信息&#xff0c;旨在帮助读者快速洞察领域新动态。 推荐文章1 题目&#xff1a;Sensitivity Analysis of the Cost Coefficie…

OCC 布尔操作

一、简介 BRepAlgoAPI_Algo 是 OpenCASCADE 中用于布尔操作的基类&#xff0c;提供了布尔运算的基础功能。布尔操作是计算几何中常见的操作&#xff0c;用于对两个形状进行交、并、差运算等。这些操作在 CAD 和 3D 建模中非常重要。 BRepAlgoAPI_Algo 的基本功能 BRepAlgoAPI…

IDEA 插件 Tongyi Lima - 智能生成 Commit Msg 的强大工具

在当今的软件开发过程中&#xff0c;清晰准确的提交信息&#xff08;Commit Msg&#xff09;对于代码的版本控制和团队协作至关重要。而 IDEA 中的 Tongyi Lima 插件为开发者带来了极大的便利&#xff0c;它支持针对 commit 文件进行 AI 生成 commit msg &#xff0c;显著提升了…

DC-2靶机渗透

DC-2靶机渗透回顾 文章目录 DC-2靶机渗透回顾信息收集FLAG1FLAG2FLAG3FLAG4FLAG5 信息收集 nmap进行主机发现和端口扫描&#xff0c;发现开启80,7744端口 进行全扫描查看7744端口其实是ssh服务端口 FLAG1 访问网址&#xff0c;发现加载失败&#xff0c;看url地址看来是需要配…

RHCSA Liunx基础完整版笔记

虚拟机上安装rhel9 网络模式&#xff1a; 桥接模式&#xff1a;与主机使用同一张网卡。要求虚拟机的ip地址与物理机的ip地址在同一个网段 nat模式&#xff1a;该模式会使用vm8网卡与真机网卡进行通讯的。即虚拟机中的网卡与vm8连接&#xff0c;真机网卡与vm8连接。是的真机与…

Monorepo简介

Monorepo 第一章&#xff1a;与Monorepo的邂逅第二章&#xff1a;Multirepo的困境第三章&#xff1a;Monorepo的魔力 - 不可思议的解决问题能力第四章&#xff1a;Monorepo的挑战与应对策略第五章&#xff1a;总结第六章&#xff1a;参考 第一章&#xff1a;与Monorepo的邂逅 …

打开Excel后无法编辑是什么情况?3种解决方法

当发现Excel表格无法编辑&#xff0c;相关菜单选项也都变成灰色状态&#xff0c;无法点击时&#xff0c;可以看看是否以下3个原因造成的&#xff0c;一起来看看对应的解决方法吧。 一、Excel标记为“最终版本” 原因&#xff1a; 当Excel标记为最终版本时&#xff0c;Excel会…

AWS-负载均衡-创建一个对外的HTTPS ALB

目录 介绍 功能差异 适用场景 性能比较 补充 基本组成部分 创建流程 介绍 Elastic Load Balancing 支持三种类型的负载均衡器&#xff1a;Application Load Balancer、Network Load Balancer 和 Classic Load Balancer。这里用ALB( Application Load Balancer)说明。 功…