1. 链表的存储结构
前面在学习顺序表的顺序存储时,某一些操作是有缺点的,比如:对于插入和删除操作时都需要移动元素的位置,而这个过程又是十分耗时的,于是又有了一种叫做链表的链式存储结构来弥补顺序存储结构的不足。
线性表的链式存储结构的特点是可以用任意的存储空间来存储线性表的数据元素,这些存储空间可以是连续的,也可以是不连续的,也就是说数据元素可以存储在内存中未被占用的任意位置,如图1所示:
图1-链式存储结构(摘自大话数据结构)
我们在学习顺序结构时知道,存储空间只需存储数据元素就行了。但是在链式存储结构当中,除了要存储数据元素之外,还要存储后继元素的地址,这通常是由链表中的一个一个节点来存储数据元素和后继元素的地址。
图2-链表节点
在这个线性表中有很多节点,为了表示每个数据元素与其下一个数据元素之间的逻辑关系,每个节点在存储数据元素之外,还要存储当前数据元素的下一个数据元素的地址。如上图所示,节点中又分为数据域和指针域,数据域用于存储数据元素,指针域用于存储下一个数据元素的地址,通过指针域可以把数据元素之间链结起来,建立起关系,形成一个链表,这样前驱节点就可以根据指针域找到后继节点
。
每个节点中除了有数据域外,还有一个指针域,用以指向其后继节点,这样构成的链接表称为线性单向链接表,简称单链表。
关于单链表中的节点:
头节点:链表中的第一个结点,指针域指向开始节点的地址,开始节点中存储这第一个数据元素。
尾节点:链表中的最后一个数据结点,指针域为NULL,表示这已经是最后一个节点了。
2. 单链表的存储结构
typedef int ElemType; //这里数据域是int类型,也可以为其他类型
typedef struct LNode //定义单链表节点类型
{
ElemType data; //数据域
struct LNode *next; //指针域,next指向后继节点,也就是说next指向的也是LNode类型的节点
} LinkList;
单链表特点:
在单链表中,由于每个节点只包含有一个指向后继节点的指针,所以当访问过一个节点后,只能接着访问它的后继节点,而无法访问它的前驱节点,虽然在单链表中无法做到这一点,但是在后面学习其他类型的链表时是可以做到。
3. 示例1
我们借用一下之前在学习顺序存储结构中的城市表的数据,如下所示:
图3-城市表
城市表中,每一条城市记录都包括了区号,城市名,说明这几个信息。这一次我们要用线性表的链式存储结构去存储这样的数据,应该怎么去做呢?
来看一下城市表的链式存储结构:
//城市记录的
typedef struct
{
char code[4]; //区号
char name[16]; //城市名
char describe[32]; //说明
} ElemType;
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LinkList;
数据域中要存储的是城市记录(区号,城市,说明)这样的数据,那么可以用结构体ElemType来表示数据域中存储的数据元素。
指针域用于指向后继节点的地址,每个节点都包括了数据域和指针域,于是可以用LNode来表示next指针的类型,毕竟我们还要通过next指针去访问后继节点的数据域和指针域的。
图4-存储城市记录的链表
头结点的数据域一般不存储数据,指针域指向开始节点的地址,以此类推,尾节点的指针域为空,最终形成一个单链表。
4. 存储密度
存储密度,在计算机中是指结点数据本身所占的存储量和整个结点结构所占的存储量之比,存储公式为:
图5-存储公式
一般节点分为指针域和数据域,如果数据域是一个整型,占用4字节的话,在32位系统下那么节点就占用了8字节,其中指针域为4字节,那么存储密度 = 节点占用空间总量8 / 节点数据本身占用空间4,在这种情况下存储密度不是很高。
如果数据域是一个ElemType类型的话,数据域占用54字节,节点占用58字节,存储密度 = 58 / 54,在这种情况下存储密度相对较高。也就是说,存数据域所占空间越多,储密度越大,存储空间的利用率就越高。
对于顺序表来说,顺序表的存储密度为1(若不考虑顺序表中的空闲区),而链表的存储密度小于1。