概念

链接方式存储

链接方式存储的线性表简称为链表(Linked List)。

链表的具体存储表示为:

  1. 用一组任意的存储单元来存放线性表的结点(这组存储单元既可以是连续的,也可以是不连续的)。

  2. 链表中结点的逻辑次序和物理次序不一定相同。为了能正确表示结点间的逻辑关系,在存储每个结点值的同时,还必须存储指示其后继结点的地址(或位置)信息(称为指针(pointer)或链(link))。

链式存储是最常用的存储方式之一,它不仅可用来表示线性表,而且可用来表示各种非线性的数据结构。

单链表

单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。

链表中的数据是以结点来表示的,每个结点的构成为:

元素(数据元素的映象,通常称为“数据域”) + 指针(指示后继元素存储位置,通常称为“指针域”)。

元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。

如图1所示,数据域data--存放结点值的数据域;指针域next--存放结点的直接后继的地址(位置)的指针域(链域)。

链表通过每个结点的链域将线性表的n个结点按其逻辑顺序链接在一起的,每个结点只有一个链域的链表称为单链表(Single Linked List)。


图1 链表结点的结构

头指针pHead、头结点pHeadNode、首元结点p1Node和终端结点(尾结点)pTailNode

  • 头结点pHeadNode:

有时,在链表的第一个结点之前会额外增设一个结点,结点的数据域一般不存放数据(有些情况下也可以存放链表的长度等信息),此结点被称为头结点。

若头结点的指针域为空(NULL),表明链表是空表(如图2 所示)。头结点对于链表来说,不是必须的,在处理某些问题时,给链表添加头结点会使问题变得简单。


在这里插入图片描述

在这里插入图片描述
图2 空链表
  • 头指针pHead:

永远指向链表中第一个结点的位置(如果链表有头结点,头指针指向头结点;否则,头指针指向首元结点)。

  • 头结点和头指针的区别:

头指针是一个指针,头指针指向链表的头结点或者首元结点;头结点是一个实际存在的结点,它包含有数据域和指针域。两者在程序中的直接体现就是:头指针只声明而没有分配存储空间,头结点进行了声明并分配了一个结点的实际物理内存。

单链表中可以没有头结点,但是不能没有头指针

单链表中每个结点的存储地址是存放在其前趋结点next域中

开始结点无前趋,故应设头指针pHead指向开始结点。

链表由头指针唯一确定,单链表可以用头指针的名字来命名。

  • 首元结点p1Node:

链表中第一个元素所在的结点,如果存在头结点则它是头结点后边的第一个结点。如图 3 所示。



图3 非空链表
  • 终端结点(尾结点)pTailNode:

终端结点(尾结点)无后继,故终端结点的指针域为空,即NULL。

单链表的定义

C语言使用结构体来定义单链表:



点击查看详细内容

2//定义结点数据域的类型
3typedef char DataType;
4
5//定义结点
6typedef struct Node{
7​    DataType data;//数据域
8​    struct Node *next;//指针域
9}Node;
10
11//Node和SinglyLinkedList是不同名字的同一个类型(命名的不同是为了概念上更明确)
12typedef struct Node SinglyLinkedList;
13
14//显示定义SinglyLinkedList类型的指针变量*pHead表示它是单链表的头指针
15SinglyLinkedList *pHead;
16 

单链表的建立

初始化

带头结点的单链表的初始化就是创建一个头结点,给他分配存储空间。并将头结点的指针域指向NULL。



点击查看详细内容

2  /**
3  * 初始化单链表,创建一个带头结点的空链表
4  * @return 链表头指针
5  */
6  SinglyLinkedList *InitSinglyLinkedList()
7  {
8      // 申请存储空间可使用malloc()函数实现,需设立一申请单元指针,这里是头指针pHead,
9      // 但malloc()函数得到的指针并不是指向结构体的指针,因此,需使用强制类型转换,
10      // 将其转换成结构体型指针。
11      pHead = (SinglyLinkedList *)malloc(sizeof(SinglyLinkedList));
12      // 刚开始时,链表还没建立,是一空链表,pHead结点的next指针为NULL。
13      pHead->next = NULL;
14
15      return pHead;
16  }
17 

单链表是用户不断申请存储单元和改变链接关系而得到的一种特殊数据结构,将链表的左边称为链头,右边称为链尾

带头结点的单链表的创建有头插法、尾插法两种方法。

头插法

头插法建单链表是将链表右端看成固定的,链表不断向左延伸而得到的。头插法最先得到的是尾结点。如图 4 所示:



图4 头插法

由于链表的长度是随机的,故用一个for循环来控制链表中结点个数。

申请存储空间可使用malloc()函数实现,需设立一申请单元指针,但malloc()函数得到的指针并不是指向结构体的指针,需使用强制类型转换,将其转换成结构体型指针。

刚开始时,链表还没建立,是一空链表,pHead指针为NULL。

链表建立的过程是申请空间、得到数据、建立链接的循环处理过程。

头插法实现代码如下:



点击查看详细内容

2       /**
3        * 头插法创建带头结点的单链表
4        * 如:pHead-->d-->c-->b-->a-->NULL [逆序]
5        * @param  pHead     链表头指针
6        * @param  pData    要插入数据的指针
7        * @param  dataCount 要插入数据的数量
8        * @return          插入后链表的头指针
9        */
10        SinglyLinkedList *
11        CreateListFrHead (SinglyLinkedList *pHead, DataType *pData, int dataCount)
12        {
13            //创建一个搜索结点,用于遍历链表
14            SinglyLinkedList *pCurrent = pHead;
15
16            for(int i = 0; i < dataCount; i++)
17            {
18            // 创建新结点pInsertNode用于存放要插入的数据
19            SinglyLinkedList *pInsertNode = (SinglyLinkedList *)malloc(sizeof(SinglyLinkedList));
20            pInsertNode->data = pData[i];
21
22            // 将pInsertNode插在原结点之前,前驱结点之后
23            // 因为每个结点的地址都是存放在其前驱结点的指针域next中
24            pInsertNode->next = pCurrent->next;        //原结点之前
25            pCurrent->next = pInsertNode;        //前驱节点结点之后
26            }
27
28            return pHead;
29        }
30 

尾插法

若将链表的左端固定,链表不断向右延伸,这种建立链表的方法称为尾插法。如图 5 所示:



图5 尾插法

尾插法建立链表时,头指针固定不动,故必须设立一个搜索指针,向链表右边延伸,则整个算法中应设立三个链表指针,即头指针pHead、搜索指针pCurrent、申请单元指针pInsertNode。尾插法最先得到的是头结点。

尾插法实现代码如下:



点击查看详细内容

2    /**
3     * 尾插法建立带头结点的单链表
4     * 例如:pHead-->a-->b-->c-->d-->NULL [顺序]
5     * @param  pHead     链表头指针
6     * @param  pData     要插入的数据指针
7     * @param  dataCount 要插入的数据数量
8     * @return           插入后的链表头指针
9     */
10    SinglyLinkedList * CreateListFrTail(SinglyLinkedList *pHead, DataType *pData,
11            int dataCount) {
12        //创建搜索指针pCurrent用于遍历链表
13        SinglyLinkedList *pCurrent = pHead;
14
15        //遍历链表
16        for (int i = 0; i < dataCount; i++) {
17            //创建新结点pInsertNode用于保存要插入的数据
18            SinglyLinkedList *pInsertNode = (SinglyLinkedList *) malloc(
19                    sizeof(SinglyLinkedList));
20            pInsertNode->data = pData[i];
21
22            //将pInsertNode插入pCurrent之后
23            pCurrent->next = pInsertNode;
24            //pCurrent始终指向尾结点
25            pCurrent = pInsertNode;
26        }
27
28        //插入完成后,尾结点的next域置为NULL
29        pCurrent->next = NULL;
30
31        return pHead;
32    }
33 

完整的测试代码如下



点击查看详细内容

2#include 
3#include 
4#include 
5
6//定义结点数据域的类型
7typedef char DataType;
8
9//定义结点
10typedef struct Node {
11    DataType data; //数据域
12    struct Node *next; //指针域
13} Node;
14
15//定义SinglyLinkedList类型变量表示单链表
16//注意:Node和SinglyLinkedList是不同名字的同一个类型(命名的不同是为了概念上更明确)
17typedef struct Node SinglyLinkedList;
18
19//显示定义SinglyLinkedList类型的指针变量*pHead表示它是单链表的头指针
20SinglyLinkedList *pHead;
21
22/**
23 * 初始化单链表,创建一个带头结点的空链表
24 * @return 链表头指针
25 */
26SinglyLinkedList *InitSinglyLinkedList();
27
28/**
29 * 判断链表是否为空
30 * @param  pHead 链表头指针
31 * @return       1为空,0不为空
32 */
33int IsEmpty(SinglyLinkedList *pHead);
34
35/**
36 * 头插法创建带头结点的单链表
37 * 如:pHead-->d-->c-->b-->a [逆序]
38 * @param  pHead     链表头指针
39 * @param  pData    要插入数据的指针
40 * @param  dataCount 要插入数据的数量
41 * @return          插入后链表的头指针
42 */
43SinglyLinkedList *CreateSinglyLinkedListFrHead(SinglyLinkedList *pHead, DataType *pData,
44        int dataCount);
45
46/**
47 * 尾插法建立带头结点的单链表
48 * 例如:pHead-->a-->b-->c-->d-->NULL [顺序]
49 * @param  pHead     链表头指针
50 * @param  pData     要插入的数据指针
51 * @param  dataCount 要插入的数据数量
52 * @return           插入后的链表头指针
53 */
54SinglyLinkedList * CreateSinglyLinkedListFrTail(SinglyLinkedList *pHead, DataType *pData,
55        int dataCount);
56
57/**
58 * 输出链表的长度
59 * @param  pHead 链表头指针
60 * @return       链表中结点个数
61 */
62int SinglyLinkedListLength(SinglyLinkedList *pHead);
63
64/**
65 * 输出链表中的数据
66 * @param pHead 链表头指针
67 */
68 void DispSinglyLinkedList(SinglyLinkedList *pHead);
69
70 int main(void) {
71    //带头结点的单链表初始化
72    pHead = InitSinglyLinkedList();
73
74    //输出链表
75    DispSinglyLinkedList(pHead);
76
77    //创建如下单链表
78    DataType *pData = "abcdefg";
79
80    //头插法创建链表
81    pHead = CreateSinglyLinkedListFrHead(pHead, pData, strlen(pData));
82
83    //尾插法创建链表
84    //    pHead = CreateSinglyLinkedListFrTail(pHead, pData, strlen(pData));
85
86    printf("链表长度为:%d\n", SinglyLinkedListLength(pHead));
87
88    //输出链表
89    DispSinglyLinkedList(pHead);
90
91    //释放存储空间
92    free(pHead);
93
94    return EXIT_SUCCESS;
95}
96
97/**
98 * 初始化单链表,创建一个带头结点的空链表
99 * @return 链表头指针
100 */
101SinglyLinkedList *InitSinglyLinkedList() {
102    // 申请存储空间可使用malloc()函数实现,需设立一申请单元指针,这里是头指针pHead,
103    // 但malloc()函数得到的指针并不是指向结构体的指针,因此,需使用强制类型转换,
104    // 将其转换成结构体型指针。
105    pHead = (SinglyLinkedList *) malloc(sizeof(SinglyLinkedList));
106    // 刚开始时,链表还没建立,是一空链表,pHead结点的next指针为NULL。
107    pHead->next = NULL;
108
109    return pHead;
110}
111
112/**
113 * 判断链表是否为空
114 * @param  pHead 链表头指针
115 * @return       1为空,0不为空
116 */
117int IsEmpty(SinglyLinkedList *pHead)
118{
119    return (pHead->next == NULL);
120}
121
122/**
123 * 头插法创建带头结点的单链表
124 * 如:pHead-->d-->c-->b-->a-->NULL [逆序]
125 * @param  pHead     链表头指针
126 * @param  pData    要插入数据的指针
127 * @param  dataCount 要插入数据的数量
128 * @return          插入后链表的头指针
129 */
130SinglyLinkedList *CreateSinglyLinkedListFrHead(SinglyLinkedList *pHead, DataType *pData,
131        int dataCount) {
132    //创建一个搜索结点,用于遍历链表
133    SinglyLinkedList *pCurrent = pHead;
134
135    for (int i = 0; i < dataCount; i++) {
136        // 创建新结点pInsertNode用于存放要插入的数据
137        SinglyLinkedList *pInsertNode = (SinglyLinkedList *) malloc(
138                sizeof(SinglyLinkedList));
139        pInsertNode->data = pData[i];
140
141        // 将pInsertNode插在原结点之前,前驱结点之后
142        // 因为每个结点的地址都是存放在其前驱结点的指针域next中
143        pInsertNode->next = pCurrent->next;        //原结点之前
144        pCurrent->next = pInsertNode;        //前驱节点结点之后
145    }
146
147    return pHead;
148}
149
150/**
151 * 尾插法建立带头结点的单链表
152 * 例如:pHead-->a-->b-->c-->d-->NULL [顺序]
153 * @param  pHead     链表头指针
154 * @param  pData     要插入的数据指针
155 * @param  dataCount 要插入的数据数量
156 * @return           插入后的链表头指针
157 */
158SinglyLinkedList * CreateSinglyLinkedListFrTail(SinglyLinkedList *pHead, DataType *pData,
159        int dataCount) {
160    //创建搜索指针pCurrent用于遍历链表
161    SinglyLinkedList *pCurrent = pHead;
162
163    //遍历链表
164    for (int i = 0; i < dataCount; i++) {
165        //创建新结点pInsertNode用于保存要插入的数据
166        SinglyLinkedList *pInsertNode = (SinglyLinkedList *) malloc(
167                sizeof(SinglyLinkedList));
168        pInsertNode->data = pData[i];
169
170        //将pInsertNode插入pCurrent之后
171        pCurrent->next = pInsertNode;
172        //pCurrent始终指向尾结点
173        pCurrent = pInsertNode;
174    }
175
176    //插入完成后,尾结点的next域置为NULL
177    pCurrent->next = NULL;
178
179    return pHead;
180}
181
182/**
183 * 输出链表中的数据
184 * @param pHead 链表头指针
185 */
186void DispSinglyLinkedList(SinglyLinkedList *pHead) {
187    if (IsEmpty(pHead))
188    {
189        printf("链表为空!\n");
190        return;
191    }
192
193    //创建搜索结点pCurrent用于遍历链表
194    //因为头结点中不存放数据,所以需要跳过头结点
195    SinglyLinkedList *pCurrent = pHead->next;
196
197    //遍历链表
198    while (pCurrent != NULL) {
199        printf("%c ", pCurrent->data);
200        pCurrent = pCurrent->next;
201    }
202
203    printf("\n");
204}
205
206/**
207 * 输出链表的长度
208 * @param  pHead 链表头指针
209 * @return       链表中结点个数
210 */
211int SinglyLinkedListLength(SinglyLinkedList *pHead)
212{
213    int ListLength = 0;
214    SinglyLinkedList *pCurrent = pHead;
215    while (pCurrent->next != NULL)
216    {
217        ListLength++;
218        pCurrent = pCurrent->next;
219    }
220
221    return ListLength;
222}
223   

未完待续。。。

参考

单链表 C语言 学习记录的更多相关文章

  1. 带头结点的循环单链表----------C语言

    /***************************************************** Author:Simon_Kly Version:0.1 Date: 20170520 D ...

  2. 带头节点的单链表-------C语言实现

    /***************************************************** Author:Simon_Kly Version:0.1 Date:20170520 De ...

  3. 不带头结点的单链表------C语言实现

    File name:no_head_link.c Author:SimonKly Version:0.1 Date: 2017.5.20 Description:不带头节点的单链表 Funcion L ...

  4. 单链表c语言实现的形式

    包括初始化,创建,查询,长度,删除,清空,销毁等操作 代码如下: #include<stdio.h> #include<stdlib.h> //定义单链表的数据类型 typed ...

  5. 学习iOS笔记第一天的C语言学习记录

    c语言基础学习 int num1 = 15; int num2 = 5; int temp = 0; //先把num1放到temp里 temp = num1; //先把num2放到num1里 num1 ...

  6. C语言学习记录_2019.02.10

    sizeof:给出某个类型或某个变量在内存中占据的字节数:(1个字节8位,即8比特) 格式符 (1)%ld表示数据按十进制有符号长型整数输入或输出. (2)%d表示数据按十进制有符号整型数输入或输出. ...

  7. C语言学习记录_2019.02.04

    逻辑性变量的定义符:bool,在C语言中只有true和false: 定义方式:bool t = true; 逻辑运算符: !:逻辑非 &&:逻辑与 ||:逻辑或 表达区间的错误形式:4 ...

  8. C语言学习记录

    思路: 工具书: <c程序设计语言> R&K <linux C 编程一站式学习>

  9. 数据结构-多级指针单链表(C语言)

    偶尔看到大一时候写了一个多级链表,听起来好有趣,稍微整理一下. 稍微注意一下两点: 1.指针是一个地址,他自己也是有一个地址.一级指针(带一个*号)表示一级地址,他自身地址为二级地址.二级指针(带两个 ...

随机推荐

  1. NowCoder数列

    题目:https://www.nowcoder.com/questionTerminal/0984adf1f55a4ba18dade28f1ab15003 #include <iostream& ...

  2. iOS UITableView reloadData 刷新结束后执行后续操作

    如果在reloadData后需要立即获取tableview的cell.高度,或者需要滚动tableview. 如果直接在reloadData后执行代码是有可能出问题的,比如indexPath为nil等 ...

  3. django相关命令

    1 安装django pip3 install django 2 django-admin命令 django-admin startproject mysite #创建一个项目 3 manage.py ...

  4. Codeforces Round #408 (Div. 2) C

    Description Although Inzane successfully found his beloved bone, Zane, his owner, has yet to return. ...

  5. Maxim Buys an Apartment CodeForces - 854B

    题意:已知一条街上有n幢房子,依次的编号为1~n,其中有k幢已经卖出去了但是不知道是哪k幢.当且仅当一幢房子没有卖出去且其两旁至少有一幢房子卖出去了的时候,认为这幢房子是好的.问这n幢房子中好的房子最 ...

  6. Cake slicing UVA - 1629

    UVA - 1629 ans[t][b][l][r]表示t到b行,l到r列那一块蛋糕切好的最小值d[t][b][l][r]表示t到b行,l到r列区域的樱桃数,需要预处理 #include<cst ...

  7. h5-27-存储/读取JS对象

    存储JS对象 <script type="text/javascript"> /*封装人员信息*/ function Person(id,name,age) { thi ...

  8. C# 移动开发(Xamarin.Form) Plugin.BLE 蓝牙连接

    随着Xamarin.Form项目接近尾声,仔细一算才发现过来大半年时间了. 期间除了刚开始有闲情写写,现在总算有空来总结一下了. 来先说 Plugin.BLE (https://github.com/ ...

  9. javascript动态添加、修改、删除对象的属性与方法

    在其他语言中,对象一旦生成,就不可更改了,要为一个对象添加修改成员必须要在对应的类中修改,并重新实例化,而且程序必须经过重新编译.JavaScript 中却非如此,它提供了灵活的机制来修改对象的行为, ...

  10. hdu 5402 Travelling Salesman Problem (技巧,未写完)

    题意:给一个n*m的矩阵,每个格子中有一个数字,每个格子仅可以走一次,问从(1,1)走到(n,m) 的路径点权之和. 思路: 想了挺久,就是有个问题不能短时间证明,所以不敢下手. 显然只要n和m其中一 ...