入门:链表的基本操作

标签:C语言 链表

By 小威威


1.写这篇博文的原因

C语言有三大重要部分:流程控制、函数、指针。
对于指针,单单了解它的简单应用是不够的,最重要的还是学习链表。许多参考书对链表的基本操作的概括还是不大完整的,所以在此通过这篇博文介绍链表的几种常用操作,以帮助初学者入门链表。

2.链表的基本操作主要有哪一些

1)链表的建立;
2)链表的输出;
3)链表结点的插入;
4)链表结点的删除;
5)链表各结点指向的倒置;
6)链表的粉碎。
补充:入门基本操作以后,可以进行其他操作:如各种类型的排序,这里就不介绍了,在以后的博文可能会介绍。

3.链表基本操作的完整代码

# include <stdio.h>
# include <stdlib.h>
struct student {  //  用结构体定义链表的结点int num;    //  学号float score;  //  分数struct student *next;  //  存储的是指向下一个结构体的地址
};
struct student *establish(void);  //  建立链表的函数的声明
struct student *lessen(struct student *head, int num);  //  删除结点的函数的声明
struct student *insert(struct student *head, int num, struct student *node);  //  插入结点的函数的声明
struct student *reserve(struct student *head);  //  链表指向倒置的函数的声明
void output(struct student *head);  //  输出链表的函数的声明
void deletelist(struct student *head);  //  粉碎链表的函数的声明
int n;  // 定义全局变量,表示链表中结点的个数
int main(void) {struct student *head;  // 定义链表的头指针int num, num2;  // num表示要删去的结点head = establish();  // 调用建立链表的函数output(head);  //  调用输出链表的函数printf("请输入你要删除的结点:\n");scanf("%d", &num);head = lessen(head, num);  //  调用删除结点的函数output(head);  //  调用输出链表的函数struct student *node = malloc(sizeof(struct student));  // 定义一个新的结点,并为他开辟一个空间printf("请输入需要插入新结点的数据:\n");scanf("%d%f", &node->num, &node->score);  // 给该新结点赋值printf("请输入要插入的位置(哪个结点之后):\n");scanf("%d", &num2);  // 输入要插入的位置head = insert(head, num2, node);  // 调用插入结点的函数output(head);  // 调用输出的函数printf("现在对该链表的指向进行倒置\n");head = reserve(head);  // 调用链表指向倒置的函数output(head);  // 调用输出链表的函数deletelist(head);  //  调用粉碎链表的函数return 0;
}struct student *establish(void) {  // 定义建立链表的函数struct student *p1, *p2, *head;  // 定义三个指针变量,p1、p2可以对相邻结点进行操作,head为头指针n = 0;  //  给n初始化,即结点为0p1 = p2 = malloc(sizeof(struct student));  // 给两个指针变量分配动态内存head = NULL;  // 一定要将头指针置空。(如果没有初始化,一旦没有执行循环,该函数就会直接返回没有初始化的头指针,即没用的头指针)printf("请输入数据(0 0结束):\n");scanf("%d%f", &p1->num, &p1->score);  // 给结点赋值while (p1->num != 0) {  //  建立一个while循环,判断条件是检测p1->num是否为0,以决定是否终止循环n += 1;  // 若不为0,则可建立新结点,即结点数+1if (n == 1)  //  将第一个结点的地址赋给头指针head = p1;else  //  将p1的地址赋给p2,如此p1,p2就能实现对相邻结点的操作p2 = p1;p1 = malloc(sizeof(struct student));  // 建立新结点p2->next = p1;  // 把新结点的位置赋给新结点前的结点存储的地址区间内(也就是p2对应的结点)scanf("%d%f", &p1->num, &p1->score);  // 给新结点赋值}p2->next = NULL;  //  循环结束后,p2就是最后一个结点,故将其存储的地址区间置空free(p1);  // 此时的p1起到的是结束循环的作用,是一个没有作用的结点,应该删去,故清空其内存,并加以置空p1 = NULL;  //  将无作用的结点置空return head;  // 返回头指针
}
struct student *lessen(struct student *head, int num) { // 动态链表结点的删除函数struct student *p1, *p2 = NULL;//初始化,避免p2没有被初始化(在本程序中确实有可能)if (head == NULL) {printf("List is NULL\n");//先判断是否为空链表,如果是,返回头指针而不做其他操作return head;}p1 = head;//把头指针赋给p1while (p1->num != num && p1->next != NULL) { //while循环条件:结构体的数值不等于num且结构体地址不为NULL(即不对最后一个结点操作)p2 = p1; //用p2保存p1的地址,便于储存所要删除的结点的地址区间的内容p1 = p1->next; //结点后移}if (p1->num == num) { //判断该结点是否符合要求if (p1 == head) { //判断该结点是否为第一个结点head = p1->next; //如果删除的是第一个结点,则把第一个结点的地址区间的内容赋给headprintf("Delete %d success\n", num);} else { //如果删除的不是第一个结点,则将所删除结点的地址区间内容赋给p2的地址区间p2->next = p1->next;printf("Delete %d success\n", num);}free(p1); //清空p1,即清空删除结点所占的内存p1 = NULL; //将p1置空,避免生成野指针n -= 1; //结点数减少一个} else {printf("Can't find the num in the list\n");}return head; //返回头指针
}
struct student *insert(struct student *head, int num, struct student *node) { //定义动态链表插入结点的函数struct student *p1; // 定义一个p1,相当于结构体指针的副本if (head == NULL) { // 倘若这是一个空链表,则加一个结点它就不是空链表了head = node; // 所以就把新结点的位置赋给头指针headnode->next = NULL; // 将新结点的地址区间清空n += 1; // 结点数加1} else {  //如果不是空链表,则执行接下来的操作p1 = head; // 将头指针赋给p1,防止接下来的操作改变头指针的值while (p1->num != num && p1->next != NULL) // 用一个while循环对目标结点(新结点前面的结点)进行搜索p1 = p1->next; // 将链表推进}if (p1->num == num) { // 判断是否存在目标结点node->next = p1->next;  // 将node插入p1与原p1下一个结点之间,把p1指向下一结点的地址区间赋给node的地址区间p1->next = node; // 再把node的地址赋给p1的地址区间n += 1; // 结点数加一} else {printf("Cannot find the figure\n");  //  如果没有找到目标结点,则输出提示信息}return head;  //  返回头指针
}
struct student *reserve(struct student *head) {  //定义一个将链表指向倒置的函数struct student *p, *p1, *p2;  //  定义三个结构体指针变量,p用于暂时储存,p1,p2用于对相邻结点的操作p1 = NULL;  // p1指前面的结点(原链表),刚开始赋NULL是为了让第一个结点(也就是倒置后的尾结点)的地址区间清空p2 = head;  // p2指后面的结点(原链表),刚开始赋head是要通过p2对该链表进行操作while (p2 != NULL) {  // 定义一个while循环,第一是判断该链表是否为空链表,第二是判断是否到达了原先链表的尾结点p = p2->next;  // 先用p暂时储存p2所存储的下一个结点地址区间,目的是为了实现链表操作时结点的后移p2->next = p1;  // 然后把前面结点的地址赋给后面结点的地址区间,目的是使后一个结点指向前面的一个结点,以实现指向的倒置p1 = p2;   //  将p2位置赋给p1,因为结点后移时p1,p2的指向也要跟着后移p2 = p;   // 将下一个结点的位置赋给p2,完成结点的后移}head = p1;  //  循环结束时,p1对应的就是新链表的第一个结点,故将其地址赋给头指针return head;  //  返回头指针
}
void output(struct student *head) {  // 定义一个链表输出的函数struct student *p1;  // 定义结构体指针变量p1,用于结点的后移,以实现输出操作p1 = head;  // 将head赋给p1,以实现对该链表的操作while (p1 != NULL) {  // 建立一个while循环,结束条件是到达尾结点printf("%d\n%f\n", p1->num, p1->score);  // 输出结点中的数值部分p1 = p1->next;  // 将下一个结点的位置赋给p1}return;
}
void deletelist(struct student *head) {struct student *p1; // 定义结构体指针变量副本while (head != NULL) { // 建立一个while循环,条件是head不为空p1 = head->next;  // 将head所指向地址赋给p1free(head); // 然后释放head指向的结构体所占的内存head = p1; // 再把下一个结构体的地址赋给head}
}

4.各基本操作的函数

1)链表的建立

struct student *establish(void) {  // 定义建立链表的函数struct student *p1, *p2, *head;  // 定义三个指针变量,p1、p2可以对相邻结点进行操作,head为头指针n = 0;  //  给n初始化,即结点为0p1 = p2 = malloc(sizeof(struct student));  // 给两个指针变量分配动态内存head = NULL;  // 一定要将头指针置空。(如果没有初始化,一旦没有执行循环,该函数就会直接返回没有初始化的头指针,即没用的头指针)printf("请输入数据(0 0结束):\n");scanf("%d%f", &p1->num, &p1->score);  // 给结点赋值while (p1->num != 0) {  //  建立一个while循环,判断条件是检测p1->num是否为0,以决定是否终止循环n += 1;  // 若不为0,则可建立新结点,即结点数+1if (n == 1)  //  将第一个结点的地址赋给头指针head = p1;else  //  将p1的地址赋给p2,如此p1,p2就能实现对相邻结点的操作p2 = p1;p1 = malloc(sizeof(struct student));  // 建立新结点p2->next = p1;  // 把新结点的位置赋给新结点前的结点存储的地址区间内(也就是p2对应的结点)scanf("%d%f", &p1->num, &p1->score);  // 给新结点赋值}p2->next = NULL;  //  循环结束后,p2就是最后一个结点,故将其存储的地址区间置空free(p1);  // 此时的p1起到的是结束循环的作用,是一个没有作用的结点,应该删去,故清空其内存,并加以置空p1 = NULL;  //  将无作用的结点置空return head;  // 返回头指针
}

2)链表的输出

void output(struct student *head) {  // 定义一个链表输出的函数struct student *p1;  // 定义结构体指针变量p1,用于结点的后移,以实现输出操作p1 = head;  // 将head赋给p1,以实现对该链表的操作while (p1 != NULL) {  // 建立一个while循环,结束条件是到达尾结点printf("%d\n%f\n", p1->num, p1->score);  // 输出结点中的数值部分p1 = p1->next;  // 将下一个结点的位置赋给p1}return;
}

3)链表结点的插入

struct student *insert(struct student *head, int num, struct student *node) { //定义动态链表插入结点的函数struct student *p1; // 定义一个p1,相当于结构体指针的副本if (head == NULL) { // 倘若这是一个空链表,则加一个结点它就不是空链表了head = node; // 所以就把新结点的位置赋给头指针headnode->next = NULL; // 将新结点的地址区间清空n += 1; // 结点数加1} else {  //如果不是空链表,则执行接下来的操作p1 = head; // 将头指针赋给p1,防止接下来的操作改变头指针的值while (p1->num != num && p1->next != NULL) // 用一个while循环对目标结点(新结点前面的结点)进行搜索p1 = p1->next; // 将链表推进}if (p1->num == num) { // 判断是否存在目标结点node->next = p1->next;  // 将node插入p1与原p1下一个结点之间,把p1指向下一结点的地址区间赋给node的地址区间p1->next = node; // 再把node的地址赋给p1的地址区间n += 1; // 结点数加一} else {printf("Cannot find the figure\n");  //  如果没有找到目标结点,则输出提示信息}return head;  //  返回头指针
}

4)链表结点的删除

struct student *lessen(struct student *head, int num) { // 动态链表结点的删除函数struct student *p1, *p2 = NULL;//初始化,避免p2没有被初始化(在本程序中确实有可能)if (head == NULL) {printf("List is NULL\n");//先判断是否为空链表,如果是,返回头指针而不做其他操作return head;}p1 = head;//把头指针赋给p1while (p1->num != num && p1->next != NULL) { //while循环条件:结构体的数值不等于num且结构体地址不为NULL(即不对最后一个结点操作)p2 = p1; //用p2保存p1的地址,便于储存所要删除的结点的地址区间的内容p1 = p1->next; //结点后移}if (p1->num == num) { //判断该结点是否符合要求if (p1 == head) { //判断该结点是否为第一个结点head = p1->next; //如果删除的是第一个结点,则把第一个结点的地址区间的内容赋给headprintf("Delete %d success\n", num);} else { //如果删除的不是第一个结点,则将所删除结点的地址区间内容赋给p2的地址区间p2->next = p1->next;printf("Delete %d success\n", num);}free(p1); //清空p1,即清空删除结点所占的内存p1 = NULL; //将p1置空,避免生成野指针n -= 1; //结点数减少一个} else {printf("Can't find the num in the list\n");}return head; //返回头指针
}

5)链表结点指向的倒置

struct student *reserve(struct student *head) {  //定义一个将链表指向倒置的函数struct student *p, *p1, *p2;  //  定义三个结构体指针变量,p用于暂时储存,p1,p2用于对相邻结点的操作p1 = NULL;  // p1指前面的结点(原链表),刚开始赋NULL是为了让第一个结点(也就是倒置后的尾结点)的地址区间清空p2 = head;  // p2指后面的结点(原链表),刚开始赋head是要通过p2对该链表进行操作while (p2 != NULL) {  // 定义一个while循环,第一是判断该链表是否为空链表,第二是判断是否到达了原先链表的尾结点p = p2->next;  // 先用p暂时储存p2所存储的下一个结点地址区间,目的是为了实现链表操作时结点的后移p2->next = p1;  // 然后把前面结点的地址赋给后面结点的地址区间,目的是使后一个结点指向前面的一个结点,以实现指向的倒置p1 = p2;   //  将p2位置赋给p1,因为结点后移时p1,p2的指向也要跟着后移p2 = p;   // 将下一个结点的位置赋给p2,完成结点的后移}head = p1;  //  循环结束时,p1对应的就是新链表的第一个结点,故将其地址赋给头指针return head;  //  返回头指针
}

6)链表的粉碎

void deletelist(struct student *head) {struct student *p1; // 定义结构体指针变量副本while (head != NULL) { // 建立一个while循环,条件是head不为空p1 = head->next;  // 将head所指向地址赋给p1free(head); // 然后释放head指向的结构体所占的内存head = p1; // 再把下一个结构体的地址赋给head}

5.需要注意的地方

1.分配动态内存要注意释放并清空;
2.某些指针变量的初始化;
3.建立链表完成后记得将最后一个结点的地址区间置空;同样的,指向倒置时,记得将第一个结点的地址赋给头指针。


以上内容皆为本人观点,欢迎大家提出意见,共同探讨!

入门:链表的基本操作相关推荐

  1. 数据结构之【线性表】(顺序表、链表的基本操作实现)

    概念 线性表:是N个数据元素的有限序列. 顺序表:用一组地址连续的存储单元依次存储[线性表 ]的数据元素.(区别于有序表:表中的数据元素存在非递增或非递减有序) 链表:用一组任意的存储单元来存储[线性 ...

  2. 计算机视觉快速入门一 —— 图像基本操作(二)

    计算机视觉快速入门一 -- 图像基本操作(二) 1.灰度图 img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) import cv2 #opencv读取的格 ...

  3. 链表的基本操作 java_详细实现单链表的基本操作【Java版】

    节点类 static Node head=new Node(); static class Node{ int data; Node next; public Node() {//无参构造方法,默认d ...

  4. 链表c++语言 解析,C++ 单链表的基本操作(详解)

    链表一直是面试的高频题,今天先总结一下单链表的使用,下节再总结双向链表的.本文主要有单链表的创建.插入.删除节点等. 1.概念 单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数 ...

  5. 《剑指offer》第二章小结(1)——链表的基本操作

    <剑指offer>第二章小结(1)--链表的基本操作 面试题5是从尾到头打印链表,与此相关的链表的基本操作应该知道. 下面代码中列举了单链表的创建.遍历.插入和删除四种操作.参考网址: h ...

  6. 【BUCTOJ】链表的基本操作

    原始题目 题题目描述 链表是数据结构中一种最基本的数据结构,它是用链式存储结构实现的线性表.它较顺序表而言在插入和删除时不必移动其后的元素.现在给你一些整数,然后会频繁地插入和删除其中的某些元素,会在 ...

  7. 链表的基本操作(C语言)详解(摘取自C语言学习网2.5)

    链表的基本操作(C语言)详解 #include <stdio.h> #include <stdlib.h>typedef struct Link{int elem;struct ...

  8. 单链表的基本操作-插入结点、删除结点、新建链表、查找结点位置

    ** C语言新手小白的学习笔记-------------目前持续更新中 ** 本人90后电气工程及其自动化大学生,大二开始接触C语言,写过前端,Python,但是都不精通,通过许多认识后明白了自身的许 ...

  9. 【C++】链表及基本操作

    链表及基本操作 定义 基本操作 1. 创建单链表 2. 插入节点 3. 删除节点 4. 反转链表 5. 倒数第K个节点 6. 是否有环 本文所述均为单向链表. ps:更多数据结构知识详见: 常见数组结 ...

最新文章

  1. 过分!高校实验动物被学生私自放走,实验兔刚缝完针,连线都没拆......
  2. 50篇经典珍藏 | Docker、Mesos、微服务、云原生技术干货
  3. VARCHART XGantt—世界级甘特图大师
  4. Protocol Buffer数据编码
  5. Master PDF Editor中文版
  6. Adobe Acrobat Reader 快捷键
  7. 53亿美元,今年最大一笔杠杆并购案!为什么会是它?
  8. TiKV 源码解析系列文章(二)raft-rs proposal 示例情景分析
  9. 【数据库】oracle数据库----内嵌视图
  10. python 异步下载图片_python3抓取异步百度瀑布流动态图片(二)get、json下载代码讲解...
  11. python爬虫找工作要掌握什么_python爬虫实战:判断招聘信息的存在
  12. 「Python 编程」编码实现网络请求库中的 URL 解析器
  13. 腾讯基于预训练模型的文本内容理解实践
  14. iOS开发之导航栏(navigationController)透明化
  15. c# xml文件新增同级节点_C# xml文件的创建,修改和添加节点 。
  16. ASP.NET登录状态保持 并 设置IE cookie
  17. electron 使用 node-ffi 调用 C++ 动态链接库(DLL)
  18. 当下最强的 AI art 生成模型 Stable Diffusion 最全面介绍
  19. 工艺过程卡片,工序卡片,工艺卡,刀具卡区别
  20. RGB565 转 HSV C语言实现

热门文章

  1. linux c语言 打开文件,linux c打开文件的方法
  2. 重复代码 Duplicated Code
  3. Linux压缩打包命令使用方法
  4. android状态栏高度px,安卓720*1280界面尺寸规范参考
  5. user.has_perm(perm) 中参数 perm 的格式问题 - Django
  6. Vue入门(二)v-show、v-if、v-bind
  7. 闪电网络一周热点回顾-AToken研究院
  8. 8086寻址方式图解
  9. 理解下DSO/ADSO
  10. 中大新华计算机科学与技术,专业评估|信息科学学院电子信息科学与技术、计算机科学与技术、软件工程、数字媒体技术专业评估考察会议举行...