接下来一节就是链表
链表是存取顺序访问的数据对象集
数据对象它所占用的存储空间
按照动态的分配方式来分配
用malloc和free或者是
new和delete来进行管理的
从定义的角度来讲
一个链表它是像这样一个元素序列
它的其中的每一个元素和前后的元素
是相互链接在一起的
也可能一个结点和后一个结点链接在一起
也可能是某一个结点和它的前一个结点
或后一个结点都链接在一起
这个我们就称它为链表
这里面最重要的概念就是我刚才讲的结点
它是指链表中的
某一个特定的元素 这叫结点
一个NODE 它包括两个字段（两个域）
一个叫数据域 一个叫做链接域
我们用data和next来表达
实际上你在设计这个链表的过程中
你的data这个字段是有可能是很多个域
你的数据可能很复杂
在这样的一个链表中
为了维持数据之间的关系
需要一个表头结点和一个表尾结点
我们用head和tail来表示
这样的一个表头或表尾
你当然可以使用链表中的
特定的结点来表达
但实际上没有必要
只需要使用一个指向链表的首结点的
一个指针来表达它就可以了
所以表头结点并没有data字段
表尾结点也一样
按照我们这样的设计
这个head和tail就是这个链表的
表头指针或表尾指针
它们分别指向链表的表头结点
和表尾结点
这样的话我们就可以通过head这个表头指针
来访问这个链表的头结点
然后访问链表的下一个结点
这个是通过表头结点的next域来达到的
然后按照这个链表的顺序依次地向后访问
一直到最后的结点
就完成整个链表的操作
这是链表的最基本的处理方式
而我们的表尾结点呢
将直接指向链表的最后一个元素
也就是链表的表尾结点
这样的话可以直接操纵链表的表尾的元素
头指针和尾指针将指向我们的表头和表尾
这样的两个结点
那么这样的链表数据结构
正常情况下面
如果要想符合数据封装和信息隐藏的
基本要求的话
那么我们就需要通过这样的两个域
data域和next域来分别描述
它的数据字段和链接字段
假设定义的这个链表是用来表达
刚才那个点结构体的
所以在这样的链表中
每一个结点它都会包括两个部分
我们刚才那个链表的那个图例
每一个结点的形状是一样的
每一个结点的存储表示都是一样的
一个data字段 一个next字段
每个next字段将指向它的下一个结点
那么这个结点类型是什么呢
和第一个结点类型是一样的
所以在设计的时候
我们就需要特别注意这一条
next这个字段 它的类型是PNODE
PNODE是什么呢
PNODE就是指向struct NODE的一个指针
你看我们这样一个定义你就明确了
我有一个struct NODE
一个结点的类型的一个声明
然后我们typedef struct NODE * PNODE
PNODE指向这个结构体
然后struct NODE具体的实现里
data 类型就是PPOINT
然后next域为指向NODE的一个指针
就是PNODE
这个就是我们链表结点的数据结构
它包含两个域
一个是结点数据域
还有一个链接指针域
注意在这个结构体类型的定义中
它里面有一个成员为指向结构体的指针
这是允许的
你如果让它的next字段为
struct NODE类型的成员
那么这样的定义就涉及到了
结构体的递归定义 这是不允许的
因为它没有办法决定
这个结构体的存储空间的大小
而如果是指向本结构体的一个指针
这就是合法的
因为每一个指针存储空间的大小
它都是固定的
不管它是指向别人还是指向它自身
尺寸是固定的
32位的体系结构 32位的编译器
那么这个指针就是4个字节 就是32位
如果是64位指针 它的尺寸就是64位
不管它是指向哪一个类型
尺寸都是固定的
所以它不影响struct NODE结构体的
内存的分配和管理
所以就可以用指向本结构体的指针
作为结构体的某一个特定成员类型
这个就是链表的结点的数据结构
要维持整个这个链表
仅有这个链表结点数据结构是不够的
所以我们还要为它定义整个链表的结构
来管理所有的结点
那么我们会定义一个struct LIST
然后typedef struct LIST * PLIST
一个指向我们的链表的一个指针PLIST
然后我们定义struct LIST
这个链表结构体 它包括三个字段
一个是count字段
然后有两个字段head和tail
分别指向链表的表头和表尾
它们的类型当然都是PNODE
有了前面这个定义
现在就能够看到链表的
一个完整的存储布局
首先这个链表的结构体
它包括三个字段count、head、tail
count表示这个链表结点的元素个数
head将会指向这个表头结点
表尾指针tail将会指向表尾结点
这个就是完整的链表的数据结构
当然在这个链表数据结构之外
我们仍然有一个指针指向LIST
这个就是标准的链表结构
那么在整个链表里面
所有的链表的结点
它都是动态分配内存的
所以这些链表的结点
在逻辑上它们是连续的
但是在物理上不一定是连续的
因为我没有办法规定malloc
没有办法规定new
它们分配出来的存储空间一定是连续的
你上来malloc一个 又malloc一个
或new一个结点 然后再new一个结点
这样分配出来的这两个结点
它们的存储布局在物理上你是没有办法
要求它们是连续分配的
逻辑上是顺序的 而物理上不一定
这是第一个要说明的地方
第二个 在访问和使用这个链表的过程中
要时刻注意这个链表的完整性
如果你在某一个特定的时刻
删除了这个链表中的某个结点
而你又没有维持这个链表完整性的话
那么链表的首结点
到链表的后续结点之间就会脱节
这个是一个完整的链条
你把其中某一个环给抠掉了
你又没有完整地将链表这个链接关系维持住
那么这个链条就断了
它会带来什么问题啊
这个问题是巨大的
因为他们在逻辑上是连续的
而物理上不连续的
所以一旦斩断了这个链条
那么后续的链表的结点都是无法访问的
需要特别去注意的
整个后续结点的数据全都会丢失掉
在链表实现过程中
我们当然可以实现很多种形状的链表
按照这样的图例的格式
这样实现的链表称之为单链表
也就是单向的链表
你看我们结点之间的关系
首结点 有个next域
然后指向它的下一个结点
下一个结点next域指向它下一个结点
这是一个单向的链条
你可能会说这些链表的结点域里面
不仅具有next字段 还有一个previous字段
这个链表的指向关系呢
将会返回去指向它的前一个结点
每个结点都如此的话
那么我们事实上就构造了一个双向的链表
不仅可以从前指向后 也可以从后指向前
这样的链表就叫一个双向的链表
如果我这个链表的最后一个结点（就是尾结点）
不是NULL值 不是空值
而是指向这个链表的表头
我就构成了一圈
就像我们自行车链条一样
这个就称它为循环链表
如果这个链表不仅是双向的
而且它还是循环的
那么我们就称它为双向循环链表
对于我们单向的链表来说
链表最后一个结点的next域
一般都设为NULL
我们图例实际上是画了一个接地的符号
表示它什么也不指向