线性结构:有且只有一个根节点,且每个节点最多有一个直接前驱和一个直接后继的非空数据结构

非线性结构:不满足线性结构的数据结构

链表(单向链表的建立、删除、插入、打印)

1、链表一般分为: 

  单向链表

双向链表

环形链表

2、基本概念

链表实际上是线性表的链式存储结构,与数组不同的是,它是用一组任意的存储单元来存储线性表中的数据,存储单元不一定是连续的,

且链表的长度不是固定的,链表数据的这一特点使其可以非常的方便地实现节点的插入和删除操作

链表的每个元素称为一个节点,每个节点都可以存储在内存中的不同的位置,为了表示每个元素与后继元素的逻辑关系,以便构成“一个节点链着一个节点”的链式存储结构,

除了存储元素本身的信息外,还要存储其直接后继信息,因此,每个节点都包含两个部分,第一部分称为链表的数据区域,用于存储元素本身的数据信息,这里用data表示,

它不局限于一个成员数据,也可是多个成员数据,第二部分是一个结构体指针,称为链表的指针域,用于存储其直接后继的节点信息,这里用next表示,

next的值实际上就是下一个节点的地址,当前节点为末节点时,next的值设为空指针

1 struct link
2 {
3 int data;
4 struct link *next;
5 };

像上面这种只包含一个指针域、由n个节点链接形成的链表,就称为线型链表或者单向链表,链表只能顺序访问,不能随机访问,链表这种存储方式最大缺点就是容易出现断链

一旦链表中某个节点的指针域数据丢失,那么意味着将无法找到下一个节点,该节点后面的数据将全部丢失

3、链表与数组比较

数组(包括结构体数组)的实质是一种线性表的顺序表示方式,它的优点是使用直观,便于快速、随机地存取线性表中的任一元素,但缺点是对其进行 插入和删除操作时需要移动大量的数组元素,同时由于数组属于静态内存分配,定义数组时必须指定数组的长度,程序一旦运行,其长度就不能再改变,实际使用个数不能超过数组元素最大长度的限制,否则就会发生下标越界的错误,低于最大长度时又会造成系统资源的浪费,因此空间效率差

4、单向链表的建立

1 #include <stdio.h>
2 #include <stdlib.h>
3
4 struct link *AppendNode (struct link *head);
5 void DisplyNode (struct link *head);
6 void DeletMemory (struct link *head);
7
8 struct link
9 {
10 int data;
11 struct link *next;
12 };
13
14 int main(void)
15 {
16 int i = 0;
17 char c;
18 struct link *head = NULL; //链表头指针
19 printf("Do you want to append a new node(Y/N)?");
20 scanf_s(" %c", &c);
21 while (c == 'Y' || c == 'y')
22 {
23 head = AppendNode(head);//向head为头指针的链表末尾添加节点
24 DisplyNode(head); //显示当前链表中的各节点的信息
25 printf("Do your want to append a new node(Y/N)");
26 scanf_s(" %c", &c);
27 i++;
28 }
29 printf("%d new nodes have been apended", i);
30 DeletMemory(head); //释放所有动态分配的内存
31
32 return 0;
33 }
34 /* 函数功能:新建一个节点并添加到链表末尾,返回添加节点后的链表的头指针 */
35 struct link *AppendNode(struct link *head)
36 {
37 struct link *p = NULL, *pr = head;
38 int data;
39 p = (struct link *)malloc(sizeof(struct link));//让p指向新建的节点
40 if (p == NULL) //若新建节点申请内存失败,则退出程序
41 {
42 printf("No enough memory to allocate\n");
43 exit(0);
44 }
45 if (head == NULL) //若原链表为空表
46 {
47 head = p; //将新建节点置为头节点
48 }
49 else //若原链表为非空,则将新建节点添加到表尾
50 {
51 while (pr->next != NULL)//若未到表尾,则移动pr直到pr指向表尾
52 {
53 pr = pr->next; //让pr指向下一个节点
54 }
55 pr->next = p; //让末节点的指针指向新建的节点
56 }
57 printf("Input node data\n");
58 scanf_s("%d", &data); //输入节点数据
59 p->data = data; //将新建节点的数据域赋值为输入的节点数据值
60 p->next = NULL; //将新建的节点置为表尾
61 return head; //返回添加节点后的链表的头指针
62 }
63 /* 函数的功能:显示链表中所有节点的节点号和该节点中的数据项的内容*/
64 void DisplyNode (struct link *head)
65 {
66 struct link *p = head;
67 int j = 1;
68 while (p != NULL) //若不是表尾,则循环打印节点的数值
69 {
70 printf("%5d%10d\n", j, p->data);//打印第j个节点数据
71 p = p->next; //让p指向下一个节点
72 j++;
73 }
74 }
75 //函数的功能:释放head所指向的链表中所有节点占用的内存
76 void DeletMemory(struct link *head)
77 {
78 struct link *p = head, *pr = NULL;
79 while (p != NULL) //若不是表尾,则释放节点占用的内存
80 {
81 pr = p; //在pr中保存当前节点的指针
82 p = p->next;//让p指向下一个节点
83 free(pr); //释放pr指向的当前节点占用的内存
84 }
85 }

上面的代码使用了三个函数AppendNode、DisplyNode、DeletMemory

struct link *AppendNode (struct link *head);函数作用:新建一个节点并添加到链表末尾,返回添加节点后的链表的头指针)

void DisplyNode (struct link *head);函数功能:显示链表中所有节点的节点号和该节点中的数据项的内容)

void DeletMemory (struct link *head);(函数功能:释放head所指向的链表中所有节点占用的内存)

(还使用了malloc函数和free函数)

5、malloc函数

作用:用于分配若干字节的内存空间,返回一个指向该内存首地址的指针,若系统不能提供足够的内存单元,函数将返回空指针NULL,函数原型为void *malloc(unsigned int size)

其中size是表示向系统申请空间的大小,函数调用成功将返回一个指向void的指针(void*指针是ANSIC新标准中增加的一种指针类型,

具有一般性,通常称为通用指针或者无类型的指针)常用来说明其基类型未知的指针,即声明了一个指针变量,但未指定它可以指向哪一种基类型的数据,

因此,若要将函数调用的返回值赋予某个指针,则应先根据该指针的基类型,用强转的方法将返回的指针值强转为所需的类型,然后再进行赋值

1 int *pi; 2 pi = (int *)malloc(4);

其中malloc(4)表示申请一个大小为4字节的内存,将malloc(4)返回值的void*类型强转为int*类型后再赋值给int型指针变量pi,即用int型指针变量pi指向这段存储空间的首地址

若不能确定某种类型所占内存的字节数,则需使用sizeof()计算本系统中该类型所占的内存字节数,然后再用malloc()向系统申请相应字节数的存储空间

pi = (int *)malloc(sizeof(int));

6、free函数

释放向系统动态申请的由指针p指向的内存存储空间,其原型为:Void free(void *p);该函数无返回值,唯一的形参p给出的地址只能由malloc()和calloc()申请内存时返回的地址,

该函数执行后,将以前分配的指针p指向的内存返还给系统,以便系统重新分配

 为什么要用free释放内存

(在程序运行期间,用动态内存分配函数来申请的内存都是从堆上分配的,动态内存的生存期有程序员自己来决定,使用非常灵活,但也易出现内存泄漏的问题,

为了防止内存泄漏的发生,程序员必须及时调用free()释放已不再使用的内存)

7、单向链表的删除操作

删除操作就是将一个待删除的节点从链表中断开,不再与链表的其他节点有任何联系

需考虑四种情况:

  1.若原链表为空表,则无需删除节点,直接退出程序

  2.若找到的待删除节点p是头节点,则将head指向当前节点的下一个节点(p->next),即可删除当前节点

  3.若找到的待删除节点不是头节点,则将前一节点的指针域指向当前节点的下一节点(pr->next = p->next),即可删除当前节点,当待删除节点是末节点时,

由于p->next值为NULL,因此执行pr->next = p->next后,pr->next的值也变成NULL,从而使pr所指向的节点由倒数第2个节点变成了末节点

  4.若已搜索到表尾(p->next == NULL),仍未找到待删除节点,则显示“未找到”,注意:节点被删除后,只是将它从链表中断开而已,它仍占用着内存,必须释放其所占的内存,否则将出现内存泄漏

(头结点不是头指针,注意两者区别)

8、头节点和头指针

头指针存储的是头节点内存的首地址,头结点的数据域可以存储如链表长度等附加信息,也可以不存储任何信息

    参考链接---头指针和头节点:https://www.cnblogs.com/didi520/p/4165486.html

                  https://blog.csdn.net/qq_37037492/article/details/78453333

                   https://www.cnblogs.com/marsggbo/p/6622962.html

                  https://blog.csdn.net/hunjiancuo5340/article/details/80671298

(图片出处:https://blog.csdn.net/hunjiancuo5340/article/details/80671298

值得注意的是:

  1.无论链表是否为空,头指针均不为空。头指针是链表的必要元素

  2.链表可以没有头节点,但不能没有头指针,头指针是链表的必要元素

  3.记得使用free释放内存

单向链表的删除操作实现

 1 struct link *DeleteNode (struct link *head, int nodeData)
2 {
3 struct link *p = head, *pr = head;
4
5 if (head == NULL)
6 {
7 printf("Linked table is empty!\n");
8 return 0;
9 }
10 while (nodeData != p->data && p->next != NULL)
11 {
12 pr = p; /* pr保存当前节点 */
13 p = p->next; /* p指向当前节点的下一节点 */
14 }
15 if (nodeData == p->data)
16 {
17 if (p == head) /* 如果待删除为头节点 (注意头指针和头结点的区别)*/
18 {
19 head = p->next;
20 }
21 else /* 如果待删除不是头节点 */
22 {
23 pr->next = p->next;
24 }
25 free(p); /* 释放已删除节点的内存 */
26 }
27 else /* 未发现节点值为nodeData的节点 */
28 {
29 printf("This Node has not been found");
30 }
31
32 return head;
33 }

9、单向链表的插入

向链表中插入一个新的节点时,首先由新建一个节点,将其指针域赋值为空指针(p->next = NULL),然后在链表中寻找适当的位置执行节点的插入操作,

此时需要考虑以下四种情况:

    1.若原链表为空,则将新节点p作为头节点,让head指向新节点p(head = p)

   2.若原链表为非空,则按节点值的大小(假设节点值已按升序排序)确定插入新节点的位置,若在头节点前插入新节点,则将新节点的指针域指向原链表的头节点(p->next = head),且让head指向新节点(head =p)

    3.若在链表中间插入新节点,则将新节点的指针域之下一节点(p->next = pr -> next),且让前一节点的指针域指向新节点(pr->next = p)

    4.若在表尾插入新节点,则末节点指针域指向新节点(p->next = p)

单向链表的插入操作实现

 1 /* 函数功能:向单向链表中插入数据 按升序排列*/
2 struct link *InsertNode(struct link *head, int nodeData)
3 {
4 struct link *p = head, *pr = head, *temp = NULL;
5
6 p = (struct link *)malloc(sizeof(struct link));
7 if (p == NULL)
8 {
9 printf("No enough meomory!\n");
10 exit(0);
11 }
12 p->next = NULL; /* 待插入节点指针域赋值为空指针 */
13 p->data = nodeData;
14
15 if (head == NULL) /* 若原链表为空 */
16 {
17 head = p; /* 插入节点作头结点 */
18 }
19 else /* 原链表不为空 */
20 {
21 while (pr->data < nodeData && pr->next != NULL)
22 {
23 temp = pr; /* 保存当前节点的指针 */
24 pr = pr->next; /* pr指向当前节点的下一节点 */
25 }
26 if (pr->data >= nodeData)
27 {
28 if (pr == head) /* 在头节点前插入新节点 */
29 {
30 p->next = head; /* 新节点指针域指向原链表头结点 */
31 head = p; /* 头指针指向新节点 */
32 }
33 else
34 {
35 pr = temp;
36 p->next = pr->next; /* 新节点指针域指向下一节点 */
37 pr->next = p; /* 让前一节点指针域指向新节点 */
38 }
39 }
40 else /* 若在表尾插入新节点 */
41 {
42 pr->next = p; /* 末节点指针域指向新节点*/
43 }
44 }
45
46 return head;
47 }
 
原文来自:https://www.cnblogs.com/lanhaicode/p/10304567.html

c语言中的链表的更多相关文章

  1. C语言中链表怎么删除结点?

    第一个方法: /*根据姓名删除链表的中的学生记录*/ void deleteByName(struct STUDENT * head) { struct STUDENT *p,*q; ]; if(he ...

  2. C语言中的栈和堆

    原文出处<http://blog.csdn.net/xiayufeng520/article/details/45956305#t0> 栈内存由编译器分配和释放,堆内存由程序分配和释放. ...

  3. javascript中的链表结构

    1.定义 很多编程语言中数组的长度是固定的,就是定义数组的时候需要定义数组的长度,所以当数组已经被数据填满的时候,需要再加入新的元素就很困难.只能说在部分变成语言中会有这种情况,在javascript ...

  4. C 语言中的指针和内存泄漏

    引言对于任何使用 C 语言的人,如果问他们 C 语言的最大烦恼是什么,其中许多人可能会回答说是指针和内存泄漏.这些的确是消耗了开发人员大多数调试时间的事项.指针和内存泄漏对某些开发人员来说似乎令人畏惧 ...

  5. C语言中的内存分配与释放

    C语言中的内存分配与释放 对C语言一直都是抱着学习的态度,很多都不懂,今天突然被问道C语言的内存分配问题,说了一些自己知道的,但感觉回答的并不完善,所以才有这篇笔记,总结一下C语言中内存分配的主要内容 ...

  6. C语言实现单链表-03版

    在C语言实现单链表-02版中我们只是简单的更新一下链表的组织方式: 它没有更多的更新功能,因此我们这个版本将要完成如下功能: Problem 1,搜索相关节点: 2,前插节点: 3,后追加节点: 4, ...

  7. C语言实现单链表-02版

    我们在C语言实现单链表-01版中实现的链表非常简单: 但是它对于理解单链表是非常有帮助的,至少我就是这样认为的: 简单的不能再简单的东西没那么实用,所以我们接下来要大规模的修改啦: Problem 1 ...

  8. C语言中的指针和内存泄漏

    引言 对于任何使用C语言的人,如果问他们C语言的最大烦恼是什么,其中许多人可能会回答说是指针和内存泄漏.这些的确是消耗了开发人员大多数调试时间的事项.指针和内存泄漏对某些开发人员来说似乎令人畏惧,但是 ...

  9. C语言中的堆与栈20160604

    首先声明这里说的是C语言中的堆与栈,并不是数据结构中的!一.前言介绍:C语言程序经过编译连接后形成编译.连接后形成的二进制映像文件是静态区域由代码段和数据段(由二部分部分组成:只读数据 段,未初始化数 ...

  10. C语言中堆和栈的区别

    原文:http://blog.csdn.net/tigerjibo/article/details/7423728 C语言中堆和栈的区别 一.前言: C语言程序经过编译连接后形成编译.连接后形成的二进 ...

随机推荐

  1. 解决ubuntu18环境matplotlib无法正常显示中文

    首先看看系统装了中文字体没?命令: fc-list :lang=zh 如果没安装,需要去下载或者从win复制一份到ubuntu, 在/usr/share/fonts文件夹下创建一个chinese文件夹 ...

  2. 3阶(次)贝塞尔曲线的JavaScript(JS)实现

    php中文网数学符号的显示太差了,推荐看这里 贝塞尔曲线简介:贝塞尔曲线,是贝塞尔老爷子在使用电子计算机设计汽车零件的时候 进行曲面设计而采用的一种参数化的样条曲线. 一般参数方程: B(t) = \ ...

  3. 前后端分离--token过期策略方案1

    https://blog.csdn.net/weixin_38827340/article/details/86287496?utm_medium=distribute.pc_aggpage_sear ...

  4. 摹客演示Axure原型,适配更丰富机型

    Hi!各位小伙伴!又到了摹客的新功能播报时间.本次更新,对Axure原型的演示进行了优化,支持预览不同分辨率的布局:在设计规范方面,非编辑者的界面标识也更加清晰:另外还有一些细节体验的优化.下面就一起 ...

  5. eclipse的快捷键都有哪些

    非常实用的快捷键 Ctrl+D: 删除当前行 Ctrl+Alt+↓: 复制当前行到下一行 Ctrl+Alt+↑: 复制当前行到上一行 Alt+↓: 当前行和下面一行交互位置 Alt+↑: 当前行和上面 ...

  6. nginx的nginx.conf配置文件如何修改代理的路由

    方法 location /api/ { set $request_uri_new $request_uri; if ($request_uri ~ "^/api/(.*)$") { ...

  7. 2022-04-22内部群每日三题-清辉PMP

    1.供应商建议项目经理,为了满足要求的规格,需要更换特定材料.为确保成本基准不受影响,项目经理应该审查下列哪一项? A.成本预测 B.挣值(EV)分析 C.管理储备 D.应急储备 2.项目经理确定项目 ...

  8. Carthage 使用介绍

    1.安装 Carthage 安装 brew install carthage 检测当前版本 carthage version 升级至最新版本 brew upgrade carthage 2.如果更新出 ...

  9. cmd 备份 oracle 数据 dmp文件

    语法 :     exp 用户名/密码@数据库地址/数据库名 file=文件导出地址/文件名.dmp 实例:exp develop/123@localhost/orcl file=e:/2019-02 ...

  10. ES6的模块化(import引入)

    先做个前提,新建三个模块JS文件m1,m2,m3,其中m1.js 为分别暴露,m2.js 为统一暴露,m3.js 为默认暴露.接下来进行文件的import引入 1.通用的引入方式,这种方式适合任何暴露 ...