1.线性表抽象数据类型
2.线性表顺序存储
3.线性表的链式存储结构
4.其他链表
1.线性表抽象数据类型
ADT 线性表(list)
Data
Operation
- *InitList(L): 初始化操作,建立一个新的线性表L
- ListEmpty(L): 若线性表为空,返回 true ,否则返回 false
- *ClearList(L): 将线性表清空
- *GetElem(L,i,e): 将线性表 L 中第 i 个位置元素值返回给 e
- LocateElem(L,e): 在线性表 L 中查找与给定值 e 相等的元素,如果查找成功,返回该元素在表中序号表示成功,否则返回 0 表示失败
- *ListInsert(L,i,e): 在线性表 L 中的第 i 个位置插入新元素 e
- ListDelete( L,i,e): 删除线性表 L 中第 i 个位置元素,用 e 返回其值
- ListLength(L): 返回线性表 L 的元素个数
/*将所有在线性表lb中但不在la中的数据元素插入到la中*/
void unionL(Sqlist *la,Sqlist lb)
{
int la_len,lb_len,i;
int e; //e 为la.lb相同元素
la_len = ListLength(*la);
lb_len = ListLength(lb);
for (i = 1;i < lb_len;i++)
{
GetElem(lb,i,&e); //取lb中第i个元素赋给e
if (!LocateElem(*la,e)) //如果la中不存在与e相同元素
{
ListInsert(la,++la_len,e); //将e插入la并增长la_len
}
}
}
```
2.线性表顺序存储
结构代码
#define MAXSIZE 20 //宏定义改变初始分配量
typedef int ElemType; //全局改变线性表存储类型
typedef struct
{
ElemType data[MAXSIZE]; //数组,存储数据元素
int lengh; //线性表的当前长度
}Sqlist;
第 i 个数据元素 ai 存储地址可由 ***a1***表示
LOC(ai) = LOC(a1) + (i-1) c*
获取数据元素 o(1)
获取第 i 个位置元素的值
/*初始条件:顺序线性表 L 已存在,1 <= i <=ListLength(L) */
/*操作结果:用 e 返回 L 中第 i 个元素的值, i 为位置,第 1 个位置是a[0]*/
int GetElem(Sqlist L,int i,int *e)
{
if(L.length == 0 || i < 1 || i > L.lengh) //if 获取的位置过小或过大或L的长度为0,返回0
{
return 0;
}
*e = L.data[i-1]; //返回第 i 个位置的值给 e
return 1;
}
插入操作 o(n)
在线性表第 i 个位置插入 e
- 插入位置不合理时抛出异常
- 线性表长度大于等于数组长度时抛出异常或动态增加容量
- 从最后一个元素开始向前遍历到第 i 个位置,分别将它们都向后移动一个位置
- 将要插入的元素填到位置 i 处
- 表长加1
/*初始条件:顺序线性表 L 已存在,1 <= i <= ListLength(L) */
/*操作结果:在 L 中第 i 个之前插入新的数据元素 e ,L 的长度加1 */
int ListInsert(Sqlist *L,int i,int e)
{
int k;
if (L->length == MAXSIZE) //顺序线性表已满
{
return 0;
}
if (i < 1 || i > L->length + 1) //插入位置小于第一个或大于最后一个的后一个
{
return 0;
}
if (i <= L->length) //若插入的位置不在表尾
{
for (k = L->length - 1;k >= i - 1;k--) //将要插入位置后的元素向后移一位
{
L->data[k+1] = L->data[k];
}
}
L->data[i-1] = e;
L->length++;
return 1;
}
删除操作 o(n)
删除第 i 个数据元素并用 e 返回其值
- 如果删除位置不合理,抛出异常
- 取出删除元素
- 从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置
- 表长减1
/*初始条件:L 已存在,1 <= i <= ListLength(L) */
/*操作结果:删除 L 的第 i 个数据元素,并用 e 返回其值,表长-1 */
int Listdelete(Sqlist *L,int i,int *e)
{
int k;
if (L->length == 0 || i < 1 || i > L->length)
{
return 0;
}
*e = L->data[i-1];
if (L->length) //如果删除的不是最后位置
{
for (k = i;k < L->length;k++) //将删除位置后继元素前移
{
L->data[k-1] = L->data[k];
}
}
L->length--;
return 1;
}
读取 o(1)
插入 o(n)
删除 o(n)
- 优点:
- 无须增加额外存储空间
- 快速存取表中任意位置元素
- 缺点:
- 插入和删除操作复杂度高
- 当线性表长度变化较大时,难以确定存储空间的容量
- 造成存储空间“碎片”
3.线性表的链式存储结构
因为线性表的顺序存储在插入与删除时需移动大量元素
所以引入线性表链式存储
- 头指针
- 是链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针
- 常用头指针冠以链表名字
- 无论链表是否为空,头指针均不为空,头指针是链表必需元素
- 头结点
- 为操作统一和方便设立的,放在第一元素之前,数据域一般无意义(可存储链表长度)
- 有头结点后,对第一元素结点前插入与删除操作就统一了
- 头结点不一定是链表必须元素
/*线性表单链表存储结构 */
typedef struct Node
{
int data;
struct Node *next;
}Node;
typedef struct Node *LinkList; //定义链表
单链表的读取 o(n)
获取链表第 i 个元素数据:
- 声明 p 指向第一个结点,j 初始化为1
- 当 j<i 时遍历链表,让 p 向后移动,j++
- 若到链表尾部 p 为空,则第 i 个结点不存在
- 否则为查找成功,返回 p->data
/*初始条件:线性表 L 已存在,1<=i<=ListLength(L) */
/*操作结果:用 e 返回 L 中第 i 个元素的值 */
int GetElem(LinkList L,int i,int *e)
{
int j;
LinkList p;
p = L->next; //让 p 指向 L 的第一个结点
j = 1;
while (p && j < i) //当 p 不为空且未遍历到第 i 个元素时
{
p = p->next;
j++;
}
if (!p || j > i)
{
return 0; //第 i 个元素不存在
}
*e = p->data;
return 1;
}
单链表的插入(头插) o(n)
将结点 s 插到 p 与 p->next 之间
s->next = p->next;
p->next = s;
给单链表第 i 个数据插入结点
- 声明指针 p 指向表头结点,j = 1
- 当 j < i 时,遍历链表,p 向后指,j++
- 若到末尾 ***p***为空,则第 i 个结点不存在
- 否则查找成功,生成 s 结点
- 将 e 赋值给 s->data
- 执行头插上述代码
/*初始条件:线性表 L 已存在,1<=i<=ListLength(L) */
/*操作结果:在 L 中第 i 个位置之前插入新的元素 e ,L长度++ */
int ListInsert(LinkList *L,int i,int e)
{
int j;
LinkList p,s;
p = *L; //操作统一
j = 1;
while (p && j < i)
{
p = p->next;
j++;
}
if (!p || j > i)
{
return 0;
}
s = (LinkList)malloc(sizeof(Node)); //生成新结点
s->data = e;
s->next = p->next;
p->next = s;
return 1;
}
单链表的删除 o(n)
p q/p->next q->next/p->next-next
a(i-1) ai a(i+1)
删除 存储ai 的 结点时,
q = p->next;
p->next = q->next;
删除单链表第 i 个结点的思路
- p 指向头结点,*** j = 1***
- 当 j < i 时,遍历链表,p 向后指,j++
- 若到末尾 ***p***为空,则第 i 个结点不存在
- 否则查找成功,将要删除的结点 p->next 赋给 q
- p->next = q->next
- 返回 q 结点数据给 e
- 释放 q
/*初始条件:线性表 L 已存在,1<=i<=ListLength(L) */
/*操作结果:删除 L 的第 i 个数据元素,并用 e 返回值,L 长度减 1 */
int ListDelete(LinkList *L,int i,int *e)
{
int j = 1;
LinkList p,q;
p = *L;
while(p->next && j < i)
{
p = p->next;
j++;
}
if (!(p->next) || j > i)
{
return 0;
}
q = p->next; //q 指向删除元素结点
p->next = q->next;
*e = q->data;
free(q);
return 1;
}
整表创建
/* 随机产生 n 个元素的值,建立带有头结点的单链表 L (头插法) */
void CreatListHead (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; //插入到表头
}
}
/*尾插法创建单链表 */
void CreatListhead (LinkList *L,int n)
{
LinkList p,r;
int i;
srand(time(0));
*L = (LinkList)malloc(sizeof(Node));
r = *L; //r 指向尾部的结点
for (i = 0;i < n;i++)
{
p = (Node*)malloc(Node); //生成新结点
p->data = rand()%100 + 1; //随机生成100以内随机数
r->next = p; //将表尾指针 r 指向新结点
r = p; //此时 p 为表尾结点,更新 r
}
r->next = NULL;
}
单链表整表删除
int ClearList(LinkList *L)
{
LinkList p,q;
p = (*L)->next; // p 指向第一个结点
while(p) //没到表尾时
{
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL; //第一个结点为空
}
1 2 3
() () () NULL
p q
2 3 NULL
() ()
p,q
2 3 NULL
() ()
p q
3 NULL
()
p,q
NULL
p,q
4.其他链表
静态链表
typedef struct{
int data;
int cur; //游标,为 0 时无指向
} Component,StaticLinkList[1000];
/* 初始化 */
/* 数组第一个与最后一个的数据域不存放数据*/
/* space[0].cur = 备用链表第一个结点的下标
链表满时==无备用链表,此时space[0].cur = 0
space[999].cur = 第一个有数值的元素的下标
每个元素的 cur 存放下一个元素的下标 若下一位置数据为空,则 cur = 0 */
int InitList(StaticLinkList space)
{
int i;
for (i = 0;i < 1000 - 1;i++)
{
space[i].cur = i + 1; //i = 0 时,space[0].cur = 1;(备用链表第一个结点下标为 1 )
} //i = 1 时,space[1].cur = 2;(通过访问space[1].cur的值找到下一个位置
// 数据的下标
space[999].cur = 0; //space[999].cur 存放第一个有数值的元素的下标,初始化时为 0
return 1;
}
静态链表的插入
/* 若备用链表非空,则返回分配的结点下标,否则返回 0 */
int Malloc_SSL(staticLinkList space)
{
int i = space[0].cur; //i 为备用链表第一个结点的下标
if (space[0].cur) //当还有备用链表时
{
space[0].cur = space[i].cur; //因为已经确认要用第 i 个下标,此时备用链表的第一个结点的下标时原来备用链表的 //第一个结点的游标的值
}
return i;
}
int ListInsert(StaticLinkList L,int i,int e)
{
int j,k,l;
k = MAXSIZE - 1; // K 为最后一个元素的下标
if ( i < 1 || i > ListLength(L) + 1)
{
return 0; //插入位置不合理
}
j = Malloc_SSL(L); //获得备用链表的下标
if (j) //存在备用链表时
{
L[j].data = e; //填充新结点
for ( l = 1;l <= i - 1;l++) //找到第 i 个元素之前的位置
{
k = L[k].cur;
}
L[j].cur = L[k].cur; //新结点的游标(下一个结点的下标) = 第 i 个元素之前的 cur
L[k].cur = j; //将新元素的下标赋给第 i 个元素之前元素的 cur
return 1;
}
return 0;
}
静态链表的删除
/* 删除在 L 中第 i 个元素 */
int ListDelete(StaticLinkList L,int i)
{
int j,k;
if ( i < 1 || i > ListLengeth(L))
{
return 0; //删除位置不合理
}
k = MAXSIZE - 1;
for ( j = 1;j <= i - 1;j++) //找到第 i 个元素之前的位置
{
k = L[k].cur;
}
j = L[k].cur; // j 为要删除的元素的下标
L[k].cur = L[j].cur; //跳过 j ,连接
Free_SSL(L,j); //释放 L 的第 j 个元素
return 1;
}
void Free_SSL(StaticLinkList space,int k)
{
space[k].cur = space[0].cur; // 第 k 个结点的游标为原来备用链表第一个元素下标
space[0].cur = k; //此时 k 为备用链表的第一个元素的下标
}
求静态链表的长度
/* 返回静待链表 L 元素的个数 */
int ListLength(StiticLinkList L)
{
int j = 0;
int i = L[MAXSIZE - 1].cur; // i 为链表第一个有元素的元素的下标
while(i) //没到链表尾部时
{
i = L[i].cur;
j++;
}
return j;
}
静态链表优缺点
- 优点
- 在插入和删除时只需要修改游标,不需移动元素
- 缺点
- 表长难以确定
- 无随机存储特性
循环链表
存在头结点,最后一个结点的下一个结点是头结点
- 头指针指向头结点时,访问到第一个结点 o(1) ,访问最后一个结点 o(n)
- 头指针指向尾结点时,访问到第一个结点 o(1) ,访问最后一个结点 o(1)
合并两个循环链表
/* rearA 指向 A 表尾结点
rearB 指向 B 表尾结点 */
p = rearA->next; //保存 A 表头结点
rearA->next = rearB->next-next; // A 表尾结点指向 B 表第一个数据结点
q = rearB->next; //保留 B 表头结点,不然等会删的时候找不到
rearB->next = p; //此时原 B 表的尾结点为合并表的尾结点,所以赋值下一个结点为整体的头结点
free(q); //删除原 B 表的头结点(一个链表只需一个头结点)
判断链表是否有环(快慢指针法)
int Hasloop(LinkList L)
{
LinkList p = L;
LinkList q = L;
while(p != NULL && q != NULL && q->next != NULL)
{
p = p->next;
q = q->next-next;
if(p == q)
{
return 1;
}
}
return 0;
}
双向链表
/* 线性表的双向链表存储结构 */
typedef struct DulNode
{
int data;
struct DulNode *prior; //直接前驱指针
struct DulNode *next; //直接后继指针
} DulNode,*DuLinkList;
双向循环链表
p->next-prior = p = p->prior->next;
双向链表的插入
/* 将结点 s(s->data = e) 插入到结点 p 与 p->next 之间 */
s->prior = p; // p 赋给 s 的前驱
s->next = p->next; // p 的后继赋给 s 的后继
p->next->prior = s; // s 赋给 p 的后继的前驱
p->next = s; // s 赋给 p 的后继
/* 先处理 s 的前驱与后继,再处理后结点的前驱,最后处理前结点的后继
双向链表的删除
/* 删除结点 p */
p->prior->next = p->next; // p 的前驱的后继是 p 的后继
p->next-prior = p->prior; // p 的后继的前驱是 p 的前驱
free(p);
----------------------------------线性表------------------------------------
顺序存储结构 ------------------------------链式存储结构----------------------------------
单链表-----------------静态链表----------------循环链表 -----------------双向链表