概念
链接方式存储
链接方式存储的线性表简称为链表(Linked List)。
链表的具体存储表示为:
用一组任意的存储单元来存放线性表的结点(这组存储单元既可以是连续的,也可以是不连续的)。
链表中结点的逻辑次序和物理次序不一定相同。为了能正确表示结点间的逻辑关系,在存储每个结点值的同时,还必须存储指示其后继结点的地址(或位置)信息(称为指针(pointer)或链(link))。
链式存储是最常用的存储方式之一,它不仅可用来表示线性表,而且可用来表示各种非线性的数据结构。
单链表
单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。
链表中的数据是以结点来表示的,每个结点的构成为:
元素(数据元素的映象,通常称为“数据域”) + 指针(指示后继元素存储位置,通常称为“指针域”)。
元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。
如图1所示,数据域data–存放结点值的数据域;指针域next–存放结点的直接后继的地址(位置)的指针域(链域)。
链表通过每个结点的链域将线性表的n个结点按其逻辑顺序链接在一起的,每个结点只有一个链域的链表称为单链表(Single Linked List)。
头指针pHead、头结点pHeadNode、首元结点p1Node和终端结点(尾结点)pTailNode
- 头结点pHeadNode:
有时,在链表的第一个结点之前会额外增设一个结点,结点的数据域一般不存放数据(有些情况下也可以存放链表的长度等信息),此结点被称为头结点。
若头结点的指针域为空(NULL),表明链表是空表(如图2 所示)。头结点对于链表来说,不是必须的,在处理某些问题时,给链表添加头结点会使问题变得简单。
- 头指针pHead:
永远指向链表中第一个结点的位置(如果链表有头结点,头指针指向头结点;否则,头指针指向首元结点)。
- 头结点和头指针的区别:
头指针是一个指针,头指针指向链表的头结点或者首元结点;头结点是一个实际存在的结点,它包含有数据域和指针域。两者在程序中的直接体现就是:头指针只声明而没有分配存储空间,头结点进行了声明并分配了一个结点的实际物理内存。
单链表中可以没有头结点,但是不能没有头指针!
单链表中每个结点的存储地址是存放在其前趋结点next域中。
开始结点无前趋,故应设头指针pHead指向开始结点。
链表由头指针唯一确定,单链表可以用头指针的名字来命名。
- 首元结点p1Node:
链表中第一个元素所在的结点,如果存在头结点则它是头结点后边的第一个结点。如图 3 所示。
- 终端结点(尾结点)pTailNode:
终端结点(尾结点)无后继,故终端结点的指针域为空,即NULL。
单链表的定义
C语言使用结构体来定义单链表:
1 | //定义结点数据域的类型 |
单链表的建立
初始化
带头结点的单链表的初始化就是创建一个头结点,给他分配存储空间。并将头结点的指针域指向NULL。
1 | /** |
单链表是用户不断申请存储单元和改变链接关系而得到的一种特殊数据结构,将链表的左边称为链头,右边称为链尾。
带头结点的单链表的创建有头插法、尾插法两种方法。
头插法
头插法建单链表是将链表右端看成固定的,链表不断向左延伸而得到的。头插法最先得到的是尾结点。如图 4 所示:
由于链表的长度是随机的,故用一个for循环来控制链表中结点个数。
申请存储空间可使用malloc()函数实现,需设立一申请单元指针,但malloc()函数得到的指针并不是指向结构体的指针,需使用强制类型转换,将其转换成结构体型指针。
刚开始时,链表还没建立,是一空链表,pHead指针为NULL。
链表建立的过程是申请空间、得到数据、建立链接的循环处理过程。
头插法实现代码如下:
1 | /** |
尾插法
若将链表的左端固定,链表不断向右延伸,这种建立链表的方法称为尾插法。如图 5 所示:
尾插法建立链表时,头指针固定不动,故必须设立一个搜索指针,向链表右边延伸,则整个算法中应设立三个链表指针,即头指针pHead、搜索指针pCurrent、申请单元指针pInsertNode。尾插法最先得到的是头结点。
尾插法实现代码如下:
1 | /** |
完整的测试代码如下
1 |
|
未完待续。。。