数据结构 - 线性表
参考文档:
Linux 内核代码 include/list.h 源文件分析
线性表定义(List):
线性表的数据对象集合为{a1, a2, a3, a4, ...an}
除第一个元素a1外,每个元素都有一个直接前驱元素
除最后一个元素an外,每个元素都有一个直接后继
数据元素之间的关系是一 一对应的。
ADT
数据对象:D = {ai | ai 属于 ElemSet, i = 1, 2, 3, ... n, n>=0}
数据关系:除第一个元素a1外,每个元素都有直接前驱,除最后一个元素外,每个元素都有一个直接后继。
一般操作: 共13个操作,
初始化、
销毁、
清空、
判断是否为空、
获取值,
插入、
删除、
遍历、
查找、
逆转
// 初始化操作,建立一个空的线性表L
InitList(*L)
// 初始条件:线性表已存在
// 操作结果: 销毁线性表
DestoryList(*L)
// 初始条件: 线性表已存在
// 操作结果: 将线性表清空
ClearList(*L)
// 初始条件: 线性表已存在
// 操作结果: 若线性表为空,返回true,否则返回false。
ListEmpty(L)
// 初始条件: 线性表L已存在, i >= 1 && i <= ListLength(i)
// 操作结果: 将线性表L中第i个元素的值返回给e.
GetElem(L, i, *e)
// 初始条件: 线性表L已存在, compare()是数组判定
// 操作结果: 在线性表L中查找与给定e相等的元素,如果查找成功,返回该元素在表中的序列号,否则返回0表示失败。
LocateElem(L, e, compare())
// 初始条件: 线性表L已经存在
// 操作结果: 在L中第i个位置之前插入新的数据元素e, L的长度加1
ListInsert(*L, i, e)
// 线性表L存在且非空, i >= 1 && i <= ListLength(L)
// 操作结果: 删除L的第i个元素,并用e返回其值, L的长度减1
ListDelete(*L, i, *e)
// 初始条件: 线性表已经存在
// 操作结果: 返回L中数据元素的个数
ListLength(L) 返回线性表L的元素个数
// 初始条件: 线性表L已存在
// 操作结果: 依次对L的每个元素调用函数visit(), 一旦visit()失败,则操作失败。
ListTraverse(*L, visit())
// 初始条件: 线性表L已经存在
// 操作结果: 若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,否则操作失败,pre_e无定义
PriorElem(L, cur_e, &pre_e)
// 初始条件: 线性表已经存在
// 操作结果: 若cure_e是L的线性元素,且不是最后一个,则用next_e返回它的后继,否则操作失败,next_e无定义
NextElem(L, cur_e, &next_e)
线性表有两种实现方式
* 顺序表
* 链表
实现一:顺序表的表示
线性表的顺序存储结构定义三要素:
- 存储空间的起始位置
- 线性表的最大存储容量
- 线性表当前长度
// sqlist.h // ----- 线性表的动态分配顺序存储结构 ------ #define LIST_INIT_SIZE 10 /* 线性表存储空间的初始分配量 */ #define LISTINCREMENT 2 /* 线性表存储空间的分配增量 */ typedef int ElemType; typedef struct { ElemType *elem; /* 存储空间基址 */ int length; /* 当前长度 */ int listsize; /* 当前分配的存储容量(以sizeof(ElemType)为单位) */ } SqList;
顺序表操作实现
操作一:顺序表初始化操作函数
/* * 操作: 初始化一个顺序表 * 前提条件: L 指向一个顺序表 * 后置条件: 该顺序表被初始化 * 返回值: 初始化成功返回1, 否则返回0 */ Status InitList(SqList *L) { // (1)初始化分配内存 (*L).elem=(ElemType * )malloc(LIST_INIT_SIZE * sizeof(ElemType)); // (2)判断内存是否分配成功 if(!(*L).elem) exit(OVERFLOW); // (3)将链表长度初始化为0 (*L).length=0; // (4)将链表容量初始为初始大小 (*L).listsize=LIST_INIT_SIZE; // (5)成功返回 return OK; }
操作二:销毁顺序表
1 /* 2 * 操作: 销毁顺序表 3 * 前提条件: L 指向一个顺序表 4 * 后置条件: 释放为顺序表分配的内存,顺序表置为0 5 * 返回值: 释放成功返回1, 否则返回0 6 */ 7 Status DestroyList_Sq(SqList *L) 8 { 9 // (1)释放线性表L中的内存 10 free((*L).elem); 11 12 // (2)将线性表elem数据指向NULL 13 (*L).elem=NULL; 14 15 // (3)将线性表长度初始化为0 16 (*L).length=0; 17 18 // (4)将容量初始化为0 19 (*L).listsize=0; 20 21 // (5)成功返回 22 return OK; 23 }
操作三:清空线性表
/* * 操作: 清空线性表 * 前提条件: L 指向一个顺序表 * 后置条件: 清空线性表中的值 * 返回值: 释放成功返回1, 否则返回0 */ Status ClearList(SqList *L) {
// (1)将线性表长度置为0 (*L).length=0;
// (2)成功返回 return OK; }
操作四:判断线性表是否为空
/* * 操作: 判断线性表是否为空 * 前提条件: L 指向一个顺序表 * 返回值: 为空表返回1, 否则返回0 */ Status ListEmpty(SqList *L) {
// (1) 判断如果线性表长度为0,则线性表为空,否则为0 if(L.length==0) return TRUE; else return FALSE; }
操作五:获取线性表的长度
/* * 操作: 返回线性表长度 * 前提条件: L 指向一个顺序表 * 返回值: 返回线性表长度 */ int ListLength(SqList *L) {
// (1) 返回线性表中长度值 return L.length; }
操作六:获取线性表L中第i个位置元素的值
/* * 操作: 获取线性表L中第i个位置元素的值 * 前提条件: L 指向一个顺序表 * 返回值: 返回L中第i个位置元素的值 */ Status GetElem(SqList L,int i,ElemType *e) {
// (1)判断位置信息是否正确 if(i < 1 || i > L.length) exit(ERROR);
// (2)将线性表中的值赋值给e *e = *(L.elem+i-1);
// (3)成功返回值 return OK; }
操作七:顺序线性表插入操作的实现
/*
* 插入算法的思路:
* 如果插入位置不合理,抛出异常
* 如果线性表长度大于等于数据长度,抛出异常或动态增加容量
* 从最后一个元素开始向前遍历到第i个位置,分别将它们都向后移动一个位置
* 将要插入元素填入位置i处
* 表长加1
*
* 初始条件: 顺序线性表L已存在, 1<= i <= ListLength(L)
* 操作结果: 在L中第i个位置之前插入新的数据元素e,L的长度加1
*/
Status ListInsert(SqList *L, int i, ElemType e)
{
int k;
// (1)插入数据前判断线性表是否满
if(L->length == MAXSIZE)
return ERROR;
// (2)判断位置信息是否在线性表中
if(i<1 || i>L->length+1)
return ERROR;
// (3)如果插入的数据不是在表尾
if(i<=L->length)
{
// (3.1)从要插入的位置到表尾所有数据往向移动一位
for(k=L->length-1; k>=i-1; k--)
L->data[k+1] = L->data[k];
}
//(4)将插入的数据e赋值给线性表中
L->data[i-1] = e;
//(5)线性表长度增1
L->length++;
// 成功返回
return OK;
}
操作八:顺序表删除操作实现
/*
* 线性表顺序存储结构删除元素
* 如果删除位置不合理,抛出异常
* 取出删除元素
* 从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置
* 表长度减1
*
* 初始条件:顺序线性表L已存在, 1<= i <= ListLength(L)
* 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1
*/
status ListDelete(SqList *L, int i, ElemType *e)
{
int k;
// (1)判断线性表是否为空
if(L->length == 0)
return ERROR;
// (2)判断位置是否正确
if(i<1 || i>L->length)
return ERROR;
// (3)将要删除的元素赋值给e
*e = L->data[i-1];
// (4)如果删除元素不是最后一个元素
if(i < L-length)
{
// (4.1)将从位置后继元素向前移动
for(k=i; k<L-length; k++)
L->data[k-1] = L-data[k];
}
// (5)线表长度减1
L->length--;
// (6)成功返回
return OK;
}
实现二:链式表示与实现
解决线性表顺序存储结构中移动和删除中需要大量移动元素的问题。
线性表链式存储结构的组成:
- 数据域(存储数据元素)
- 指针域(存储直接后继的位置)
链表可以分为
* 线性单向链表
* 循环链表
* 双向循环链表
线性链表的ADT
数据定义
// 定义节点类型
typedef struct LNode { ElemType data; struct LNode * next; } *Link, *Position;
// 定义链表类型
typedef struct {
Link head, tail; // 分别指向线性链表中第一个节点与最后一个节点
int len; // 指示线性表中数据元素的个数
}
操作
// 分配由p指向的值为e的结点
Status MakeNode(Link &p, ElemType e);
// 释放p所有结点
void FreeNode(Link &p);
// 构造一个空的线性链表L
Status InitList(LinkList &L);
// 销毁线性表L, L不再存在
Status DestroyList(LinkList &L);
// 将线性链表L重置为空表,并释放原链表的结点空间
Status ClearList(LinkList &L);
// 已知h指向线性链表的头结点,将s所指结点插入在第一个结点之前
Status InsFirst(Link h, Link s);
// 已知h指向线性链表的头结点,删除链表中的第一个结点并以q返回
Status DelFirst(Link h, Link &q);
// 将指针s所指(彼此以指针相连)的一串结点链接在线性链表L的最后一个结点之后,并改变链表L的尾指针指向新的尾结点
Status Append(LinkList &L, Link s);
// 删除线性表L中的尾结点并以q返回,改变链表L的尾指针指向新的尾结点
Status Remove(LinkList &L, Link &q);
// 已知p指向线性
Status InsBefore(LinkList &L, Link &p, Link s);
//
Status InsAfter(LinkList &L, Link &p, Link s);
Status SetCurElem(Link &p, ElemType e);
1. 线性表的单链表结构
/*线性表的单链表存储结构*/
typedef struct Node
{
ElemType data;
struct Node * next;
} Node;
/*定义单列表LinkList*/
typedef struct Node * LinkList;
单链表的读取
/*
* 单链表的读取GetElem()
* 思路:获得链表第i个数据的
* 声明一个结点p指向链表第一个结点,初始化j从1开始
* 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累计加1
* 若到链表末尾p为空,则说明第i个元素不存在
* 否则查找成功,返回结点p的数据
*
* 初始条件:线性表L已存在, 1 <= i <= ListLength(L)
* 操作结果:用e返回L中第i个数据元素的值
*/
Status GetElem(LinkList L, int i, ElemType *e)
{
int j = 1; // j为计数器
LinkList p; // 声明一结点p
p = L->next; // 让p指向链表L的第一个结点
//(1)p不为空或者计数器j小于或没有等于i时,循环继续
while(p && j<i)
{
p = p->next; //让p指向下一个结点
++j;
}
// (2)判断元素i是否存在
if(!p || j>i)
return ERROR; //第i个元素不存在
*e = p->data; //取第i个元素的数据
return OK;
}
单链表的插入与删除
/*
* 单链表将数据插入到第i个结点
* 思路:
* 声明一个结点p指向链表第一个结点,初始化j从1开始
* 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累计加1
* 若到链表末尾p为空,则说明第i个元素不存在
* 否则查找成功,在系统中生成一个空结点s
* 将数据元素e赋值给s->data
* 单链表的插入语句: s->next = p->next; p->next = s;
* 返回成功
*
* 初始条件:线性表L已存在, 1 <= i <= ListLength(L)
* 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1
*/
Status ListInsert(LinkList *L, int i, ElemType e)
{
int j; // (1)定义位置
LinkList p, s; // (3)定义p 与 s 两个链表
p = *L; // (4)将链表L拷贝一份到p
j = 1; // (2)将位置初始化1
// (5)链表非空 且 位置小于要插入的位置
while(p && j < i)
{
p = p->next;
++j;
}
// (6)为要插入的节点分配内存空间
s = (LinkList)malloc(sizeof(Node));
// (7)将数据e赋值给新开的空间
s->data = e;
// (8)将新插入的链表指向下一个指针
s->next = p->next;
// (9)将s指向p
p->next = s;
// (10)成功返回
return OK;
}
/*
* 单链表第i个数据删除结点的算法思路:
* 声明一结点p指向链表第一个结点,初始化j从1开始;
* 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1
* 若到链表末尾p为空,则说明第i个元素不存在;
* 否则查找成功,将欲删除的结点p->next赋值给q;
* 单链表的删除标准语句 p->next = q->next;
* 将q结点中的数据赋值给e,作为返回;
* 释放q结点
* 返回成功
*
* 初始条件:顺序线性表L已存在, 1 <= i <= ListLength(L)
* 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1
*/
Status ListDelete(LinkList *L, int i, ElemType *e)
{
int j = 1;
LinkList p, q;
p = *L;
while (p->next && j < i) /*遍历寻找第i个元素*/
{
p = p->next;
++j;
}
if(!(p->next) || j > i)
return ERROR; /*第i个元素不存在*/
q = p->next;
p->next = q->next;
*e = q->date; /*将q结点中的数据给e*/
free(q);
return OK;
}
单链表的创建的思想
/*
* 单链表整表创建的算法思路:
* 声明一个结点p和计数器变量i;
* 初始化一个空链表L;
* 让L的头结点指针指向NULL,即建立一个带头结点的单链表
* 循环:
* 生成一新结点赋值给p;
* 随机生成一数字赋值给p的数据域p->data;
* 将p播入到头结点与前一新结点之间
*
* 随机产生n个元素的值,建立带表头结点的单链线性表L(头插法)
*/
void CreateListHead(LinkList * L, int n)
{
LinkList p;
int i;
srand(time(0)); //初始化随机数种子
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL; //先建立一个带头结点的单链表
for (i=0; i<n; i++)
{
p = (LinkList)malloc(sizeof(Node));
p->data = rand()%100 + 1; //随机生成100以内的数字
p->next = (*L)->next;
(*L)->next = p;
}
}
/* 随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法)*/
void CreateListTail(LinkList *L, int n)
{
LinkList p, r;
int i;
srand(time(0));
*L = (LinkList)malloc(sizeof(Node));
r = *L;
for(i=0; i<n; i++)
{
p = (Node *)malloc(sizeof(Node));
p->data = rand()%100 + 1;
r->next = p;
r = p;
}
r->next = NULL; //表示当前链表结束
}
单链表的删除操作
/*
* 单链表的整表删除思想:
* 声明一结点p和q;
* 将第一个结点赋值给p
* 循环:
* 将下一结点赋值给q;
* 释放p
* 将q赋值给p
*
* 初始条件:顺序线性表L已存在
* 操作结果:将L重置为空表
*/
Status ClearList(LinkList * L)
{
LinkList p, q;
p = (*L)->next;
while(p)
{
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL; //头结点指针为空
return OK;
}
应用: 实现两个线性表并集操作
/* 实现两个线性表A和B的并集操作, A=A U B * 思想:循环B中的每个元素,判断当前元素是否在A中 * 若不存在,则插入到A中即可。 */ void union(List *La, List Lb) { int La_len, Lb_len, i; ElemType e; //声明与La和Lb相同的数据元素e La_len = ListLength(La); //得到线性表A的元素个数 Lb_len = ListLength(Lb); //得到线性表B的元素个数 for(i = 1; i <= Lb_len; i++) { GetElem(Lb, i, e); //取线性表B中第i个数据元素赋值给e if(!LocateElem(La,e,equal)) //判断线性表A中不存在和e相同的元素 ListInsert(La, ++La_len, e); //将e插入到线性表A中最后一个位置上 } }
应用:
有两个线性表
LA = {3, 5, 8, 11}
LB = {2, 6, 8, 9, 11, 15, 20}
则
LC = {2, 3, 5, 6, 8, 8, 9,11, 11, 15, 20 }
void MergeList(List La, List Lb, List *Lc) { InitList(Lc); i = j = 1; k = 0; La_len = ListLength(La); Lb_len = ListLength(Lb); }
线性表存储结构优缺点:
无须为表示表中元素之间的逻辑关系而增加额外的存储空间
可以快速地存取表中任一位置的元素
插入和删除操作需要移动大量元素
当线性表长度变化较大时,难以确定存储空间的容量
造成存储空间的碎片
3. 双向循环链表

将单链表中终端结点的指针由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表
/*线性表的双向存储结构*/ typedef struct DulNode { ElemType data; struct DulNode * prior; //直接前驱指针 struct DulNode * next; //直接后继指针 } DulNode, *DuLinkList;
双向链表的插入操作中
/*双向链表的插入*/ s->prior = p; s->next = p->next; p->next->prior = s; p->next = s;
第一步:首先找到插入位置,节点 s 将插入到节点 p 之前
第二步:将节点 s 的前驱指向节点 p 的前驱,即 s->prior = p->prior;
第三步:将节点 p 的前驱的后继指向节点 s 即 p->prior->next = s;
第四步:将节点 s 的后继指向节点 p 即 s->next = p;
第五步:将节点 p 的前驱指向节点 s 即 p->prior = s;
/*双向链表删除*/ p->prior->next = p->next; p->next-prior = p->prior;
第一步:找到即将被删除的节点 p
第二步:将 p 的前驱的后继指向 p 的后继,即 p->prior->next = p->next;
第三步:将 p 的后继的前驱指向 p 的前驱,即 p->next->prior = p->prior;
第四步:删除节点 p 即 delete p;
逆转链表
递归的方式 typedef struct _node { int value; _node *next; } node, *pnode; node *reverse_list(node *h) { if(h == NULL || h->next == NULL) return h; node *head = reverse_list(h->next); h->next->next = h; h->next = NULL; return head; } 循环的方式 void reverse_list(node **head) { node *p, *q, *r; p = *head; q = p->next; while(q!=NULL) { //操作 r = q->next; q->next = p; //切换 p = q; q = r; } (*head)->next = NULL; *head = p; }
链表排序
// 链表的排序 void sort_list(node *head) { node *p, *q, *s; int t; p = head; while(p->next) { s = p; q = p->next; //用q去遍历p后面待排序的结点,找到一个最小值和p进行交换 while(q) { if (q->value < s->value) s = q; q = q->next; } if (s != p) { t = s->value; s->value = p->value; p->value = t; } p = p->next; } } // 以最高的效率删除结点 bool delete_node(node *&head, node *p) { if(!p || !head) return false; if(p->m_pNext != NULL) //不是尾指针 { node *del = p->m_pNext; p->m_nValue = del->m_nValue; p->m_pNext = del->m_pNext; delete del; del = NULL; } else if(head == p) //是尾指针,同时只有一个结点 { delete p; head = NULL; } else //是尾指针,同时有多个结点 { node *pre = head; while(pre&&pre->m_pNext != p) { pre = pre->m_pNext; } if(pre==NULL) return false; delete p; p = NULL; pre->m_pNext = NULL; } return true; }
判断单链表是否有环
low 走一步,fast 走两步, 相遇true, 否则false
bool check(const node* head) { if(head==NULL) return false; node *low=head, *fast=head->next; while(fast!=NULL && fast->next!=NULL) { low=low->next; fast=fast->next->next; if(low==fast) return true; } return false; }

浙公网安备 33010602011771号