~~~~ 大学学的都还给老师了,只好自己重新学习一下,感觉都忘得差不多了,整理一下学过的数据结构,和对应的实现,以及实际例子,本文很多资料都来自维基百科。
定义
~~~~
线性表(英语:Linear List)是由n(n≥0)个数据元素(结点)a[0],a[1],a[2]…,a[n-1]组成的有限序列。
~~~~
其中:
数据元素的个数n定义为表的长度 = “list”.length() (“list”.length() = 0(表里没有一个元素)时称为空表)
将非空的线性表(n>=1)记作:(a[0],a[1],a[2],…,a[n-1])
数据元素a[i](0≤i≤n-1)只是个抽象符号,其具体含义在不同情况下可以不同
~~~~
一个数据元素可以由若干个数据项组成。数据元素称为记录,含有大量记录的线性表又称为文件。
~~~~
这种结构具有下列特点:存在一个唯一的没有前驱的(头)数据元素;存在一个唯一的没有后继的(尾)数据元素;此外,每一个数据元素均有一个直接前驱和一个直接后继数据元素。
~~~~
在学习下面的之前,我们还需要了解一些东西,就是对于算法性能的分析,估量算法或者计算车程序效率的方法称为算法分析。算法分析的目的就在于选择合适算法和改进算法。通常算法分析主要从算法的时间效率和空间需求两个方面进行,这两个方面也是衡量一个算法好坏的重要标准。
~~~
算法的时间效率是指算法执行时间的长短。一个算法的执行时间是指在计算机上算法从开始到结束所花费的总时间。整理一下,我们可以简单地使用如下方法求解算法时间复杂度:
- 首先找出算法中的基本语句。算法中执行次数最多的那条语句就是基本语句,通常是最内层循环的循环体语句。
- 然后计算基本语句的频率 T ( n ) T(n) T(n)。当仅仅是为了求算法的时间复杂度时,还可以忽略低数量级项及最高数量级项的系数,只要保证基本语句频率函数中的最高数量级正确即可。
- 最高永达 O O O记法表示算法的时间性能。将基本语句频率的最高数量级作为 f ( n ) f(n) f(n)放入大 O O O中,记为 T ( n ) = O ( f ( n ) ) T(n)=O(f(n)) T(n)=O(f(n))。
- 如果算法中包含嵌套的循环,则基本语句通常是最内层的循环体。如果算法中包含并列的循环且每个循环的问题规模不同时,则将并列循环的时间复杂度相加;而若每个并列循环的问题规模相同,则只取数量级最高的时间复杂度。
~~~~
还有一个就是算法的空间效率分析,算法的空间效率是指算法在计算机上运行时所需存储空间的大小。一个算法在计算机上运行时所占用的存储空间包括三个方面:算法本身所占存储空间、算法的输入与输出数据所占存储空间及算法运行过程中为了解决问题所需要占用的辅助存储空间。
~~~~
算法本身所占存储空间与算法的指令代码长度相关,书写算法程序代码越长,其所占内存存储空间越多。对于输入与输出数据所占存储空间,通常是与问题规模本身决定的,一般不随算法的不同而改变。解决同一问题的不同算法,其在运行过程中所需的辅助存储空间也是不同的。对于算法的空间效率分析主要针对算法在运行时所需的复制空间的分析。算法所需辅助空间保存局部变量所占存储空间和系统为实现递归(若为递归算法的话)所占用的堆栈空间两个部分。
~~~~
类似算法的渐近时间复杂度,通常用算法的渐近空间复杂度(简称空间复杂度)作为算法空间效率的度量。算法的空间复杂度用大
O
O
O记法表示为:
S
(
n
)
=
O
(
f
(
n
)
)
S(n)=O(f(n))
S(n)=O(f(n))
~~~~
算法的空间复杂度表示:随着问题规模
n
n
n的增大,算法运行时所需辅助存储空间的增加率的数量级为
f
(
n
)
f(n)
f(n)。若算法运行时所占的存储空间与问题规模无关,是个常量,则称这种算法为原地工作,其空间复杂度用
O
(
1
)
O(1)
O(1)表示。
~~~~
算法时间复杂度和空间复杂度合称为算法的复杂度。算法的时间复杂度和空间复杂度往往是相互影响的两个方面。要提高算法的时间效率,往往就需要增加空间的额外开销;同样,欲节省算法的空间,则可能就需要消耗更多的计算时间作为代价。因此,在设计算法时,应综合考虑算法的各项性能,并有所侧重。
线性表的存储结构
线性表包括以下几种数据结构
- 顺序表
- 链表
- 单链表
- 动态单链表
- 静态单链表
- 双链表
- 循环链表
- 单循环链表
- 双循环链表
- 块状链表
- 其它扩展
我们下面就一个一个来学习。
循序表
~~~~
顺序表是在计算机内存中以数组的形式保存的线性表,是指用一组地址连续的存储单元依次存储数据元素的线性结构。
我们先添加一些宏,后面都可以用到:
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
#ifndef OK
#define OK 1
#endif
#ifndef ERROR
#define ERROR 0
#endif
#ifndef INFEASIBLE
#define INFEASIBLE -1
#endif
#ifndef OVERFLOW
#define OVERFLOW -2
#endif
typedef int Status;
typedef int ElemType;
存储结构:
/* 线性表的动态分配顺序存储结构 */
#define LIST_INIT_SIZE 10 /* 线性表存储空间的初始分配量 */
#define LIST_INCREMENT 2 /* 线性表存储空间的分配增量 */
typedef struct
{
ElemType *elem; /* 存储空间基址 */
int length; /* 当前长度 */
int listsize; /* 当前分配的存储容量(以sizeof(ElemType)为单位) */
}SqList;
基本操作:
/* 顺序表示的线性表的基本操作(12个) */
void InitList(SqList *L)
{ /* 操作结果:构造一个空的顺序线性表L */
L->elem = (ElemType *)malloc(LIST_INIT_SIZE * sizeof(ElemType));
if (!L->elem)
exit(OVERFLOW); /* 存储分配失败 */
L->length = 0; /* 空表长度为0 */
L->listsize = LIST_INIT_SIZE; /* 初始存储容量 */
}
void DestroyList(SqList *L)
{ /* 初始条件:顺序线性表L已存在。操作结果:销毁顺序线性表L */
free(L->elem);
L->elem = NULL;
L->length = 0;
L->listsize = 0;
}
void ClearList(SqList *L)
{ /* 初始条件:顺序线性表L已存在。操作结果:将L重置为空表 */
L->length = 0;
}
Status ListEmpty(SqList L)
{ /* 初始条件:顺序线性表L已存在。操作结果:若L为空表,则返回TRUE,否则返回FALSE */
if (L.length == 0)
return TRUE;
else
return FALSE;
}
int ListLength(SqList L)
{ /* 初始条件:顺序线性表L已存在。操作结果:返回L中数据元素个数 */
return L.length;
}
Status GetElem(SqList L, int i, ElemType *e)
{ /* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L)。操作结果:用e返回L中第i个数据元素的值 */
if (i<1 || i>L.length)
return ERROR;
*e = *(L.elem + i - 1);
return OK;
}
int LocateElem(SqList L, ElemType e, Status(*compare)(ElemType, ElemType))
{ /* 初始条件:顺序线性表L已存在,compare()是数据元素判定函数(满足为1,否则为0) */
/* 操作结果:返回L中第1个与e满足关系compare()的数据元素的位序。 */
/* 若这样的数据元素不存在,则返回值为0。 */
ElemType *p;
int i = 1; /* i的初值为第1个元素的位序 */
p = L.elem; /* p的初值为第1个元素的存储位置 */
while (i <= L.length && !compare(*p++, e))
++i;
if (i <= L.length)
return i;
else
return 0;
}
Status PriorElem(SqList L, ElemType cur_e, ElemType *pre_e)
{ /* 初始条件:顺序线性表L已存在 */
/* 操作结果:若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱, */
/* 否则操作失败,pre_e无定义 */
int i = 2;
ElemType *p = L.elem + 1;
while (i <= L.length&&*p != cur_e)
{
p++;
i++;
}
if (i > L.length)
return INFEASIBLE; /* 操作失败 */
else
{
*pre_e = *--p;
return OK;
}
}
Status NextElem(SqList L, ElemType cur_e, ElemType *next_e)
{ /* 初始条件:顺序线性表L已存在 */
/* 操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继, */
/* 否则操作失败,next_e无定义 */
int i = 1;
ElemType *p = L.elem;
while (i < L.length&&*p != cur_e)
{
i++;
p++;
}
if (i == L.length)
return INFEASIBLE; /* 操作失败 */
else
{
*next_e = *++p;
return OK;
}
}
Status ListInsert(SqList *L, int i, ElemType e)
{ /* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L)+1 */
/* 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 */
ElemType *newbase, *q, *p;
if (i<1 || i>L->length + 1) /* i值不合法 */
return ERROR;
if (L->length >= L->listsize) /* 当前存储空间已满,增加分配 */
{
newbase = (ElemType*)realloc(L->elem, (L->listsize + LIST_INCREMENT) * sizeof(ElemType));
if (!newbase)
exit(OVERFLOW); /* 存储分配失败 */
L->elem = newbase; /* 新基址 */
L->listsize += LIST_INCREMENT; /* 增加存储容量 */
}
q = L->elem + i - 1; /* q为插入位置 */
for (p = L->elem + L->length - 1; p >= q; --p) /* 插入位置及之后的元素右移 */
*(p + 1) = *p;
*q = e; /* 插入e */
++L->length; /* 表长增1 */
return OK;
}
Status ListDelete(SqList *L, int i, ElemType *e)
{ /* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) */
/* 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1 */
ElemType *p, *q;
if (i<1 || i>L->length) /* i值不合法 */
return ERROR;
p = L->elem + i - 1; /* p为被删除元素的位置 */
*e = *p; /* 被删除元素的值赋给e */
q = L->elem + L->length - 1; /* 表尾元素的位置 */
for (++p; p <= q; ++p) /* 被删除元素之后的元素左移 */
*(p - 1) = *p;
L->length--; /* 表长减1 */
return OK;
}
void ListTraverse(SqList L, void(*vi)(ElemType*))
{ /* 初始条件:顺序线性表L已存在 */
/* 操作结果:依次对L的每个数据元素调用函数vi() */
/* vi()的形参加'&',表明可通过调用vi()改变元素的值 */
ElemType *p;
int i;
p = L.elem;
for (i = 1; i <= L.length; i++)
vi(p++);
printf("\n");
}
这就是维基百科复制的,我们可以看到,中间有两个函数,一个是找前驱,一个是找后驱,在实际使用中,我们会吧顺序表进行排序,然后通过找某一个元素的前驱和后驱找到比他大或者小的最近(佳)的一个元素,这是其中一个用法吧。
链表
~~~~ 链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。
~~~~ 使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。
~~~~ 在计算机科学中,链表作为一种基础的数据结构可以用来生成其它类型的数据结构。链表通常由一连串节点组成,每个节点包含任意的实例数据(data fields)和一或两个用来指向上一个/或下一个节点的位置的链接(“links”)。链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体或磁盘上顺序,数据的访问往往要在不同的排列顺序中转换。而链表是一种自我指示数据类型,因为它包含指向另一个相同类型的数据的指针(链接)。链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表,双向链表以及循环链表。
~~~~ 链表可以在多种编程语言中实现。像Lisp和Scheme这样的语言的内建数据类型中就包含了链表的访问和操作。程序语言或面向对象语言,如C/C++和Java依靠易变工具来生成链表。
单向链表
~~~~
单向链表(又名单链表、线性链表)是链表的一种,其特点是链表的链接方向是单向的,对链表的访问要通过从头部开始,依序往下读取。
~~~~
一个单向链表的节点被分成两个部分。第一个部分保存或者显示关于节点的信息,第二个部分存储下一个节点的地址。单向链表只可向一个方向遍历。
动态单链表
链表中结点的分配和回收是由系统提供的标准函数malloc和free动态实现的,称之为动态链表。
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#define Status int
#define ElemType int // 以整型為例
#define OVERFLOW -1
#define ERROR 0
#define OK 1
// 線性表的單鏈表存儲结構
typedef struct LNode {
ElemType data;
struct LNode *next;
} LNode, *LinkList;
// 带有头结點的單鏈表的基本操作(12个)
void InitList(LinkList *L) {
// 操作结果:構造一个空的線性表L
*L = (LinkList)malloc(sizeof(struct LNode));
// 產生头结點,並使L指向此头结點
if (!*L) // 存儲分配失敗
exit(OVERFLOW);
(*L)->next = NULL; // 指针域為空
}
void DestroyList(LinkList *L) {
// 初始條件:線性表L已存在。操作结果:销毁線性表L
LinkList q;
while (*L) {
q = (*L)->next;
free(*L);
*L = q;
}
}
void ClearList(LinkList L) { // 不改变L
// 初始条件:线性表L已存在。操作结果:将L重置为空表
LinkList p, q;
p = L->next; // p指向第一个结点
while (p) { // 没到表尾
q = p->next;
free(p);
p = q;
}
L->next = NULL; // 头结点指针域为空
}
Status ListEmpty(LinkList L) {
// 初始条件:线性表L已存在。操作结果:若L为空表,则返回TRUE,否则返回FALSE
return L->next == NULL;
}
int ListLength(LinkList L) {
// 初始条件:线性表L已存在。操作结果:返回L中数据元素个数
int i = 0;
LinkList p = L->next; // p指向第一个结点
while (p) { // 没到表尾
i++;
p = p->next;
}
return i;
}
Status GetElem(LinkList L, int i, ElemType *e) {
// L为带头结点的单链表的头指针。当第i个元素存在时,其值赋给e并返回OK,否则返回ERROR
int j = 1; // j为计数器
LinkList p = L->next; // p指向第一个结点
while (p && j < i) { // 顺指针向后查找,直到p指向第i个元素或p为空
p = p->next;
j++;
}
if (!p || j > i) // 第i个元素不存在
return ERROR;
*e = p->data; // 取第i个元素
return OK;
}
int LocateElem(LinkList L, ElemType e, Status(*compare)(ElemType, ElemType)) {
// 初始条件: 线性表L已存在,compare()是数据元素判定函数(满足为1,否则为0)
// 操作结果: 返回L中第1个与e满足关系compare()的数据元素的位序
// 若这样的数据元素不存在,则返回值为0
int i = 0;
LinkList p = L->next;
while (p) {
i++;
if (compare(p->data, e)) // 找到这样的数据元素
return i;
p = p->next;
}
return 0;
}
Status PriorElem(LinkList L, ElemType cur_e, ElemType *pre_e) {
// 初始条件: 线性表L已存在
// 操作结果: 若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,
// 返回OK;否则操作失败,pre_e无定义,返回INFEASIBLE
LinkList q, p = L->next; // p指向第一个结点
while (p->next) { // p所指结点有后继
q = p->next; // q为p的后继
if (q->data == cur_e) {
*pre_e = p->data;
return OK;
}
p = q; // p向后移
}
return INFEASIBLE;
}
Status NextElem(LinkList L, ElemType cur_e, ElemType *next_e) {
// 初始条件:线性表L已存在
// 操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继,
// 返回OK;否则操作失败,next_e无定义,返回INFEASIBLE
LinkList p = L->next; // p指向第一个结点
while (p->next) { // p所指结点有后继
if (p->data == cur_e) {
*next_e = p->next->data;
return OK;
}
p = p->next;
}
return INFEASIBLE;
}
Status ListInsert(LinkList L, int i, ElemType e) {
// 算法2.9。不改变L
// 在带头结点的单链线性表L中第i个位置之前插入元素e
int j = 0;
LinkList p = L, s;
while (p && j < i - 1) { // 寻找第i-1个结点
p = p->next;
j++;
}
if (!p || j > i - 1) // i小于1或者大于表长
return ERROR;
s = (LinkList)malloc(sizeof(struct LNode)); // 生成新结点
s->data = e; // 插入L中
s->next = p->next;
p->next = s;
return OK;
}
Status ListDelete(LinkList L, int i, ElemType *e) {
// 算法2.10。不改变L
// 在带头结点的单链线性表L中,删除第i个元素,并由e返回其值
int j = 0;
LinkList p = L, q;
while (p->next && j < i - 1) { // 寻找第i个结点,并令p指向其前驱结点
p = p->next;
j++;
}
if (!p->next || j > i - 1) // 删除位置不合理
return ERROR;
q = p->next; // 删除并释放结点
p->next = q->next;
*e = q->data;
free(q);
return OK;
}
void ListTraverse(LinkList L, void(*vi)(ElemType)) {
// vi的形参类型为ElemType,与bo2-1.c中相应函数的形参类型ElemType&不同
// 初始条件:线性表L已存在。操作结果:依次对L的每个数据元素调用函数vi()
LinkList p = L->next;
while (p) {
vi(p->data);
p = p->next;
}
printf("\n");
}
静态单链表
有些高级语言中没有“指针”数据类型,只能用数组来模拟线性链表的结构,数组元素中的指针“域”存放的不是元素在内存中的真实地址,而是在数组中的位置。这样的链表称为静态链表。
// 线性表的静态单链表存储结构
#define MAX_SIZE 100 // 链表的最大长度
typedef struct {
ElemType data; //此處的ElemType可以自由代換(如int/float等)
int cur;
} component, SLinkList[MAX_SIZE];
// 一个数组只生成一个静态链表的基本操作(11个)
#define DestroyList ClearList // DestroyList()和ClearList()的操作是一样的
void InitList(SLinkList L) {
// 构造一个空的链表L,表头为L的最后一个单元L[MAX_SIZE-1],其余单元链成
// 一个备用链表,表头为L的第一个单元L[0],“0”表示空指针
int i;
L[MAX_SIZE - 1].cur = 0; // L的最后一个单元为空链表的表头
for (i = 0; i < MAX_SIZE - 2; i++) // 将其余单元链接成以L[0]为表头的备用链表
L[i].cur = i + 1;
L[MAX_SIZE - 2].cur = 0;
}
void ClearList(SLinkList L) {
// 初始条件:线性表L已存在。操作结果:将L重置为空表
int i, j, k;
i = L[MAX_SIZE - 1].cur; // 链表第一个结点的位置
L[MAX_SIZE - 1].cur = 0; // 链表空
k = L[0].cur; // 备用链表第一个结点的位置
L[0].cur = i; // 把链表的结点连到备用链表的表头
while (i) { // 没到链表尾
j = i;
i = L[i].cur; // 指向下一个元素
}
L[j].cur = k; // 备用链表的第一个结点接到链表的尾部
}
Status ListEmpty(SLinkList L) {
// 若L是空表,返回TRUE;否则返回FALSE
if (L[MAX_SIZE - 1].cur == 0) // 若为空表
return TRUE;
else
return FALSE;
}
int ListLength(SLinkList L) {
// 返回L中数据元素个数
int j = 0, i = L[MAX_SIZE - 1].cur; // i指向第一个元素
while (i) { // 没到静态链表尾
i = L[i].cur; // 指向下一个元素
j++;
}
return j;
}
Status GetElem(SLinkList L, int i, ElemType *e) {
// 用e返回L中第i个元素的值
int l, k = MAX_SIZE - 1; // k指向表头序号
if (i < 1 || i > ListLength(L))
return ERROR;
for (l = 1; l <= i; l++) // 移动到第i个元素处
k = L[k].cur;
*e = L[k].data;
return OK;
}
int LocateElem(SLinkList L, ElemType e) { // 算法2.13(有改动)
// 在静态单链线性表L中查找第1个值为e的元素。若找到,则返回它在L中的
// 位序,否则返回0。(与其它LocateElem()的定义不同)
int i = L[MAX_SIZE - 1].cur; // i指示表中第一个结点
while (i && L[i].data != e) // 在表中顺链查找(e不能是字符串)
i = L[i].cur;
return i;
}
Status PriorElem(SLinkList L, ElemType cur_e, ElemType *pre_e) {
// 初始条件:线性表L已存在
// 操作结果:若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,
// 否则操作失败,pre_e无定义
int j, i = L[MAX_SIZE - 1].cur; // i指示链表第一个结点的位置
do { // 向后移动结点
j = i;
i = L[i].cur;
} while (i && cur_e != L[i].data);
if (i) { // 找到该元素
*pre_e = L[j].data;
return OK;
}
return ERROR;
}
Status NextElem(SLinkList L, ElemType cur_e, ElemType *next_e) {
// 初始条件:线性表L已存在
// 操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继
// 否则操作失败,next_e无定义
int j, i = LocateElem(L, cur_e); // 在L中查找第一个值为cur_e的元素的位置
if (i) { // L中存在元素cur_e
j = L[i].cur; // cur_e的后继的位置
if (j) { // cur_e有后继
*next_e = L[j].data;
return OK; // cur_e元素有后继
}
}
return ERROR; // L不存在cur_e元素,cur_e元素无后继
}
Status ListInsert(SLinkList L, int i, ElemType e) {
// 在L中第i个元素之前插入新的数据元素e
int l, j, k = MAX_SIZE - 1; // k指向表头
if (i < 1 || i > ListLength(L) + 1)
return ERROR;
j = Malloc(L); // 申请新单元
if (j) { // 申请成功
L[j].data = e; // 赋值给新单元
for (l = 1; l < i; l++) // 移动i-1个元素
k = L[k].cur;
L[j].cur = L[k].cur;
L[k].cur = j;
return OK;
}
return ERROR;
}
Status ListDelete(SLinkList L, int i, ElemType *e) {
// 删除在L中第i个数据元素e,并返回其值
int j, k = MAX_SIZE - 1; // k指向表头
if (i < 1 || i > ListLength(L))
return ERROR;
for (j = 1; j < i; j++) // 移动i-1个元素
k = L[k].cur;
j = L[k].cur;
L[k].cur = L[j].cur;
*e = L[j].data;
Free(L, j);
return OK;
}
void ListTraverse(SLinkList L, void(*vi)(ElemType)) {
// 初始条件:线性表L已存在。操作结果:依次对L的每个数据元素调用函数vi()
int i = L[MAX_SIZE - 1].cur; // 指向第一个元素
while (i) { // 没到静态链表尾
vi(L[i].data); // 调用vi()
i = L[i].cur; // 指向下一个元素
}
printf("\n");
}
双向链表
~~~~
双向链表,又称为双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。
实现:
// 线性表的双向链表存储结构
typedef struct DuLNode {
ElemType data;
struct DuLNode *prior, *next;
} DuLNode, *DuLinkList;
// 带头结点的双向循环链表的基本操作(14个)
void InitList(DuLinkList *L) {
// 产生空的双向循环链表L
*L = (DuLinkList)malloc(sizeof(DuLNode));
if (*L)
(*L)->next = (*L)->prior = *L;
else
exit(OVERFLOW);
}
void DestroyList(DuLinkList *L) {
// 操作结果:销毁双向循环链表L
DuLinkList q, p = (*L)->next; // p指向第一个结点
while (p != *L) { // p没到表头
q = p->next;
free(p);
p = q;
}
free(*L);
*L = NULL;
}
void ClearList(DuLinkList L) { // 不改变L
// 初始条件:L已存在。操作结果:将L重置为空表
DuLinkList q, p = L->next; // p指向第一个结点
while (p != L) { // p没到表头
q = p->next;
free(p);
p = q;
}
L->next = L->prior = L; // 头结点的两个指针域均指向自身
}
Status ListEmpty(DuLinkList L) {
// 初始条件:线性表L已存在。操作结果:若L为空表,则返回TRUE,否则返回FALSE
if (L->next == L && L->prior == L)
return TRUE;
else
return FALSE;
}
int ListLength(DuLinkList L) {
// 初始条件:L已存在。操作结果:返回L中数据元素个数
int i = 0;
DuLinkList p = L->next; // p指向第一个结点
while (p != L) { // p没到表头
i++;
p = p->next;
}
return i;
}
Status GetElem(DuLinkList L, int i, ElemType *e) {
// 当第i个元素存在时,其值赋给e并返回OK,否则返回ERROR
int j = 1; // j为计数器
DuLinkList p = L->next; // p指向第一个结点
while (p != L && j < i) { // 顺指针向后查找,直到p指向第i个元素或p指向头结点
p = p->next;
j++;
}
if (p == L || j > i) // 第i个元素不存在
return ERROR;
*e = p->data; // 取第i个元素
return OK;
}
int LocateElem(DuLinkList L, ElemType e, Status(*compare)(ElemType, ElemType)) {
// 初始条件:L已存在,compare()是数据元素判定函数
// 操作结果:返回L中第1个与e满足关系compare()的数据元素的位序。
// 若这样的数据元素不存在,则返回值为0
int i = 0;
DuLinkList p = L->next; // p指向第1个元素
while (p != L) {
i++;
if (compare(p->data, e)) // 找到这样的数据元素
return i;
p = p->next;
}
return 0;
}
Status PriorElem(DuLinkList L, ElemType cur_e, ElemType *pre_e) {
// 操作结果:若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,
// 否则操作失败,pre_e无定义
DuLinkList p = L->next->next; // p指向第2个元素
while (p != L) { // p没到表头
if (p->data == cur_e) {
*pre_e = p->prior->data;
return TRUE;
}
p = p->next;
}
return FALSE;
}
Status NextElem(DuLinkList L, ElemType cur_e, ElemType *next_e) {
// 操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继,
// 否则操作失败,next_e无定义
DuLinkList p = L->next->next; // p指向第2个元素
while (p != L) { // p没到表头
if (p->prior->data == cur_e) {
*next_e = p->data;
return TRUE;
}
p = p->next;
}
return FALSE;
}
DuLinkList GetElemP(DuLinkList L, int i) { // 另加
// 在双向链表L中返回第i个元素的地址。i为0,返回头结点的地址。若第i个元素不存在,
// 返回NULL
int j;
DuLinkList p = L; // p指向头结点
if (i < 0 || i > ListLength(L)) // i值不合法
return NULL;
for (j = 1; j <= i; j++)
p = p->next;
return p;
}
Status ListInsert(DuLinkList L, int i, ElemType e) {
// 在带头结点的双链循环线性表L中第i个位置之前插入元素e,i的合法值为1≤i≤表长+1
// 改进算法2.18,否则无法在第表长+1个结点之前插入元素
DuLinkList p, s;
if (i < 1 || i > ListLength(L) + 1) // i值不合法
return ERROR;
p = GetElemP(L, i - 1); // 在L中确定第i个元素前驱的位置指针p
if (!p) // p=NULL,即第i个元素的前驱不存在(设头结点为第1个元素的前驱)
return ERROR;
s = (DuLinkList)malloc(sizeof(DuLNode));
if (!s)
return OVERFLOW;
s->data = e;
s->prior = p; // 在第i-1个元素之后插入
s->next = p->next;
p->next->prior = s;
p->next = s;
return OK;
}
Status ListDelete(DuLinkList L, int i, ElemType *e) {
// 删除带头结点的双链循环线性表L的第i个元素,i的合法值为1≤i≤表长
DuLinkList p;
if (i < 1) // i值不合法
return ERROR;
p = GetElemP(L, i); // 在L中确定第i个元素的位置指针p
if (!p) // p = NULL,即第i个元素不存在
return ERROR;
*e = p->data;
p->prior->next = p->next; // 此处并没有考虑链表头,链表尾
p->next->prior = p->prior;
free(p);
return OK;
}
void ListTraverse(DuLinkList L, void(*visit)(ElemType)) {
// 由双链循环线性表L的头结点出发,正序对每个数据元素调用函数visit()
DuLinkList p = L->next; // p指向头结点
while (p != L) {
visit(p->data);
p = p->next;
}
printf("\n");
}
void ListTraverseBack(DuLinkList L, void(*visit)(ElemType)) {
// 由双链循环线性表L的头结点出发,逆序对每个数据元素调用函数visit()
DuLinkList p = L->prior; // p指向尾结点
while (p != L) {
visit(p->data);
p = p->prior;
}
printf("\n");
}
~~~~ 由于另外储存了指向链表内容的指针,并且可能会修改相邻的节点,有的时候第一个节点可能会被删除或者在之前添加一个新的节点。这时候就要修改指向首个节点的指针。有一种方便的可以消除这种特殊情况的方法是在最后一个节点之后、第一个节点之前储存一个永远不会被删除或者移动的虚拟节点,形成一个下面说的循环链表。这个虚拟节点之后的节点就是真正的第一个节点。这种情况通常可以用这个虚拟节点直接表示这个链表,对于把链表单独的存在数组里的情况,也可以直接用这个数组表示链表并用第0个或者第-1个(如果编译器支持)节点固定的表示这个虚拟节点。
循环链表
~~~~
循环链表是一种链式存储结构,它的最后一个结点指向头结点,形成一个环。因此,从循环链表中的任何一个结点出发都能找到任何其他结点。循环链表的操作和单链表的操作基本一致,差别仅仅在于算法中的循环条件有所不同。
~~~~
在一个 循环链表中, 首节点和末节点被连接在一起。这种方式在单向和双向链表中皆可实现。要转换一个循环链表,你开始于任意一个节点然后沿着列表的任一方向直到返回开始的节点。再来看另一种方法,循环链表可以被视为“无头无尾”。这种列表很利于节约数据存储缓存, 假定你在一个列表中有一个对象并且希望所有其他对象迭代在一个非特殊的排列下。
指向整个列表的指针可以被称作访问指针。
结构和单链表类似称为单项循环链表:
// 设立尾指针的单循环链表的12个基本操作
void InitList(LinkList *L) { // 操作结果:构造一个空的线性表L
*L = (LinkList)malloc(sizeof(struct LNode)); // 产生头结点,并使L指向此头结点
if (!*L) // 存储分配失败
exit(OVERFLOW);
(*L)->next = *L; // 指针域指向头结点
}
void DestroyList(LinkList *L) { // 操作结果:销毁线性表L
LinkList q, p = (*L)->next; // p指向头结点
while (p != *L) { // 没到表尾
q = p->next;
free(p);
p = q;
}
free(*L);
*L = NULL;
}
void ClearList(LinkList *L) /* 改变L */ { // 初始条件:线性表L已存在。操作结果:将L重置为空表
LinkList p, q;
*L = (*L)->next; // L指向头结点
p = (*L)->next; // p指向第一个结点
while (p != *L) { // 没到表尾
q = p->next;
free(p);
p = q;
}
(*L)->next = *L; // 头结点指针域指向自身
}
Status ListEmpty(LinkList L) { // 初始条件:线性表L已存在。操作结果:若L为空表,则返回TRUE,否则返回FALSE
if (L->next == L) // 空
return TRUE;
else
return FALSE;
}
int ListLength(LinkList L) { // 初始条件:L已存在。操作结果:返回L中数据元素个数
int i = 0;
LinkList p = L->next; // p指向头结点
while (p != L) { // 没到表尾
i++;
p = p->next;
}
return i;
}
Status GetElem(LinkList L, int i, ElemType *e) { // 当第i个元素存在时,其值赋给e并返回OK,否则返回ERROR
int j = 1; // 初始化,j为计数器
LinkList p = L->next->next; // p指向第一个结点
if (i <= 0 || i > ListLength(L)) // 第i个元素不存在
return ERROR;
while (j < i) { // 顺指针向后查找,直到p指向第i个元素
p = p->next;
j++;
}
*e = p->data; // 取第i个元素
return OK;
}
int LocateElem(LinkList L, ElemType e, Status(*compare)(ElemType, ElemType)) { // 初始条件:线性表L已存在,compare()是数据元素判定函数
// 操作结果:返回L中第1个与e满足关系compare()的数据元素的位序。
// 若这样的数据元素不存在,则返回值为0
int i = 0;
LinkList p = L->next->next; // p指向第一个结点
while (p != L->next) {
i++;
if (compare(p->data, e)) // 满足关系
return i;
p = p->next;
}
return 0;
}
Status PriorElem(LinkList L, ElemType cur_e, ElemType *pre_e) { // 初始条件:线性表L已存在
// 操作结果:若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,
// 否则操作失败,pre_e无定义
LinkList q, p = L->next->next; // p指向第一个结点
q = p->next;
while (q != L->next) { // p没到表尾
if (q->data == cur_e) {
*pre_e = p->data;
return TRUE;
}
p = q;
q = q->next;
}
return FALSE; // 操作失败
}
Status NextElem(LinkList L, ElemType cur_e, ElemType *next_e) { // 初始条件:线性表L已存在
// 操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继,
// 否则操作失败,next_e无定义
LinkList p = L->next->next; // p指向第一个结点
while (p != L) { // p没到表尾
if (p->data == cur_e) {
*next_e = p->next->data;
return TRUE;
}
p = p->next;
}
return FALSE; // 操作失败
}
Status ListInsert(LinkList *L, int i, ElemType e) /* 改变L */ { // 在L的第i个位置之前插入元素e
LinkList p = (*L)->next, s; // p指向头结点
int j = 0;
if (i <= 0 || i > ListLength(*L) + 1) // 无法在第i个元素之前插入
return ERROR;
while (j < i - 1) { // 寻找第i-1个结点
p = p->next;
j++;
}
s = (LinkList)malloc(sizeof(struct LNode)); // 生成新结点
s->data = e; // 插入L中
s->next = p->next;
p->next = s;
if (p == *L) // 改变尾结点
*L = s;
return OK;
}
Status ListDelete(LinkList *L, int i, ElemType *e) /* 改变L */ { // 删除L的第i个元素,并由e返回其值
LinkList p = (*L)->next, q; // p指向头结点
int j = 0;
if (i <= 0 || i > ListLength(*L)) // 第i个元素不存在
return ERROR;
while (j < i - 1) { // 寻找第i-1个结点
p = p->next;
j++;
}
q = p->next; // q指向待删除结点
p->next = q->next;
*e = q->data;
if (*L == q) // 删除的是表尾元素
*L = p;
free(q); // 释放待删除结点
return OK;
}
void ListTraverse(LinkList L, void(*vi)(ElemType)) { // 初始条件:L已存在。操作结果:依次对L的每个数据元素调用函数vi()
LinkList p = L->next->next; // p指向首元结点
while (p != L->next) { // p不指向头结点
vi(p->data);
p = p->next;
}
printf("\n");
}
当然也可以和双链表类似,称为双向循环链表,这种实际使用较少,不做介绍。
块状链表
~~~~ 块状链表本身是一个链表,但是链表储存的并不是一般的数据,而是由这些数据组成的顺序表。每一个块状链表的节点,也就是顺序表,可以被叫做一个块。
~~~~ 块状链表通过使用可变的顺序表的长度和特殊的插入、删除方式,可以在达到 O ( n ) {\displaystyle O({\sqrt {n}})} O(n)的复杂度。块状链表另一个特点是相对于普通链表来说节省内存,因为不用保存指向每一个数据节点的指针。
其它扩展
~~~~ 根据情况,也可以自己设计链表的其它扩展。但是一般不会在边上附加数据,因为链表的点和边基本上是一一对应的(除了第一个或者最后一个节点,但是也不会产生特殊情况)。不过有一个特例是如果链表支持在链表的一段中把前和后指针反向,反向标记加在边上可能会更方便。
~~~~ 对于非线性的链表,可以参见相关的其他数据结构,例如树、图。另外有一种基于多个线性链表的数据结构:跳表,插入、删除和查找等基本操作的速度可以达到O(nlogn),和平衡树一样。