第二章 线性表
2.1 线性表的概念及抽象数据类型定义
定义:线性表(Linear List)是由 n (n ≥ 0)个类型相同的数据元素a1, a2, …, an组成的有限序列, 记做( a1, a2…, ai-1, ai,ai+1, …, an) 。
解释:数据元素之间是一对一的关系, 即每个数据元素最多有一个直接前驱和一个直接后继 。
特点:
- 同一性:数据元素类型相同。
- 有穷性:有限。
- 有序性:线性表中相邻数据元素之间存在着序偶关系<ai, ai+1> 。
ADT:
ADT LinearList{
数据元素: D={ai| ai∈ D0, i=1, … , n, n ≥ 0, D0为某一数据对象}
关系: S ={ <ai, ai+1> | ai, ai+1∈ D0, i=1,2, …,n-1}
基本操作:
(1) InitList(L) 操作前提: L为未初始化线性表。
操作结果:将L初始化为空表。
(2) DestroyList(L) 操作前提:线性表L已存在。
操作结果:将L销毁。
(3) ClearList(L) 操作前提:线性表L已存在 。
操作结果:将表L置为空表。
………
}ADT LinearList
2.2 线性表的顺序存储
2.2.1 线性表的顺序存储结构
顺序存储结构的定义
线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表中的各个元素,使得线性表中在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中,通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系,采用顺序存储结构的线性表通常称为顺序表。(数组)
顺序存储结构的C语言定义
#define maxsize = 线性表可能达到的最大长度
typdef struct
{
Elemtype elem[maxsize];//线性表占用的数组空间
int last;//记录最后一个元素在数组elem中的位置(这里是个下标值,空表为-1)
}SeqList;
用图像表示:
2.2.2 线性表顺序存储结构上的基本运算
一、查找
- 按照序号查找
- 按照内容查找
三步:判断合法—找—返回值(或者没找到)
二、插入
找到插入的地方,之后的元素依次向后移动。
四步:判断插入是否合法—表是否满了—移动—插入
int InsList(SeqList * L,int i,ElemType e)
{
int k;
if( (i<1) || (i>L->last+2) ) /*首先判断插入位置是否合法*/
{
printf(“插入位置i值不合法” );
return(ERROR);
}
if(L -> last >= maxsize - 1)
{
printf(“表已满无法插入” );
return(ERROR);
}
for(k = L -> last; k >= i-1; k--) /*为插入元素而移动位置*/
L->elem[k+1]=L->elem[k];
L->elem[i-1] = e; /*在C语言中数组第i个元素的下标为i-1*/
L->last++;
return 0;
} /*算法时间复杂度为O(n),平均时间复杂度E(n) = n/2*/
三、删除操作
删了过后后面的前移,返回删除值
int DelList(SeqList *L,int i,ElemType *e)
/*在顺序表L中删除第i个数据元素, 并用指针参数e返回其值*/
{
int k;
if((i<1)||(i>L->last+1))
{
printf(“删除位置不合法! ” );
return(ERROR);
}
*e = L->elem[i-1]; /* 将删除的元素存放到e所指向的变量中*/
for(k=i;i<=L->last;k++)
{
L->elem[k-1] = L->elem[k]; /*将后面的元素依次前移*/
L->last--;
return(OK);
}/*时间复杂度为O(n),平均时间复杂度为E(n)=(n-1)/2
四、线性表合并操作
五、线性表顺序储存结构的优缺点
-
优点:
无需为表示节点间的逻辑关系而怎加额外的存储空间;
可以随机的存取表中任意一个元素。
-
缺点
插入删除不方便效率低
只能静态存储
2.3 线性表的链式存储
2.3.1单链表
基本思路图示:
定义一个单链表的存储结构描述:
typedef struct Node
{
ElemType data;
struct Node *next;
}Node, * LinkList;
//Node 是一个存储结构,LinkList是指针类型
2.3.2单链表上的基本运算
一、初始化单链表
InitList(LinkList *L)
{
*L = (LinkList) malloc(size of (Node));
(*L)->next=Null;
}
二、操作
1.头插法
Linklist CreateFromHead (LinkList L)
{
Node *s; int flag=1;//设置一个标志,当输入"$"时,flag改为0,建表结束。
char c;
L = (Linklist)malloc(sizeof(Node));//头节点分配空间
L->next = NULL;
while(flag)
{
c=getchar();
if(c != '$')
{
s = (Node*)malloc(sizeof(Node));
s->data = c;
s->next = L->next;
L->next = s;
}
else
flag = 0;
}
}
2.尾插法
Linklist CreateFromTail (LinkList L)
{
Node *s, *r;//r指向链表的表尾
int flag=1;//设置一个标志,当输入"$"时,flag改为0,建表结束。
char c;
L = (Linklist)malloc(sizeof(Node));//头节点分配空间
L->next = NULL;
while(flag)
{
c=getchar();
if(c != '$')
{
s = (Node*)malloc(sizeof(Node));
s->data = c;
r->next = s;
r = s;
}
else
{
flag = 0;
r->next = NULL;
}
}
}
3.查找
按照结点查找
Node* Get(LinkList L,int i)
{
Node *p;
p = L;
int j = 0; //扫描计数器
while((p = p->next != NULL) && (j < i))
{
p = p->next;
j++
}
if(i = j)
return p;
else
return NULL;
}
按照内容查找
Node* Get(LinkList L,ElemType key)
{
Node *p;
p = L->next;
while(p !=NULL)
if (p->data != key)
p = p->next;
else break;
retrun p;
}
4.插入
void InsList(LinkList L, int i, Elemtype e)
{
Node *pre, *s;
pre = L;
int k = 0;
//首先找到第i-1个元素的位置,并且用*pre指向它
while(pre != NULL && k < i-1)
{
pre = pre->next;
k++;
}
//然后插入
if(k != i-1)
{
printf("插入位置不可用!");
return ERROR;
}
s = (Node*)malloc(sizeof(Node));
s->data = e;
s->next = pre->next;
pre->next = s;
return OK;
}
5.删除
void Dellist(LinkList L, int i, ElemType *e)//删除第i个节点并且保存其值到*e中
{
Node *p, *r;
p = L;
int k = 0;
while (p->next != NULL && k < i-1)
{
p = p->next;
k++;
}
if(k != i-1)
{
printf("删除节点不合理");
return ERROR;
}
r = p->next;
p -> next = r->next;
free(r);
return OK;
}
6.求链表长度
遍历一遍搞个计数器就完事。
7.单链表合并
LinkList MergeLinkList(LinkList LA, LinkList LB)
{
Node *pa, *pb, *r;
LinkList LC;
//讲LC初始化为空表。pa,pb分别指向两个单链表LA,LB的第一个结点,r初始值为LC
pa = LA->next;
pb = LB->next;
LC = LA;
LC->next = NULL;
r = LC;
//产生新表
while(pa != NULL && pb != NULL)
{
if(pa->data <= pb->data)
{
r->next = pa;
r = pa;
pa = pa->next;
}
else
{
r->next = pb;
r = pb;
pb = pb->next;
}
}
//当某个表读完
if(pa)
r->next = pa;
else
r->next = pb;
//释放内存返回(注意:只用释放B表,LA的头节点还在使用)
free (LB);
return LC;
}
2.3.3循环链表
一、初始化循环链表
什么是循环链表
最后一个节点的指针域不是指向NULL而是指向头节点。这样子形成了一个圈圈。
初始化
InitCList(LinkList *CL)
{
*CL = (LinkList) malloc(size of (Node));
(*CL)->next = *CL;
}
二、操作
1.建立循环单链表
void CreateCLinkList(LinkList CL)//CL是已经初始化的头指针
{
Node *rear, *s;
char c;
rear = CL;
c = getchar();
while(c != "$")
{
s = (Node*)malloc(sizeof(Node));
s->data = c;
rear->next = s;
rear = s;
c = getchar();
}
rear->next = CL;//最后一个节点指向头节点
}
2.合并循环单链表
和合并链表相似,但是尾部不是指向NULL
而是头节点。
2.3.4双向链表
一、定义
typedef struct DNode
{
ElemType data;
struct DNode *prior, *next;
}DNode, *DoubleList;
两个相邻结点满足如下关系:
p->prior->next == p;
p == p->next->prior;
二、操作
1,插入
算法思想:
书上是以1,2,3,4的顺序插入的,然而我觉得1,3,2,4更加合理。
2,删除
算法思想:
利用p将两边指针修改然后free就好了
2.3.5静态链表
静态链表是提前申请一些空间,用游标(cursor)来标记位置,相当于用结构数组在模拟指针。一般用于不支持malloc(); free();
的语言。
2.4 一元多项式的表示及相加
2.5 顺序表与链表综合比较
1.基于空间考虑
- 顺序表的存储空间是静态分配的,太大浪费空间太小容易爆炸。
- 线性表长度变化大,难以估计其存储规模时,采用动态链表作为存储结构较好。
- 当线性表长度变化不大、易于事先确定其大小时,为了节约空间宜用顺序表。
2.基于时间考虑
- 线性表如果主要是查找,会比链表快。
- 频繁删除增加用链表。
- 插入删除在首位的可采用带守尾指针的单循环链表。
3.一个表格
2.6 总结与例题
2.6.1知识点
-
线性表特征
-
线性表存储方式
- 顺序储存(顺序表)
- 链式存储(链表)
-
链表操作特点
-
顺序链操作技术
p = L;
p = p->next;
-
指针保留
在对第i个结点插入、删除时,需要保留第i-1个结点的地址,pre
-
判断表尾
非循环链表:p->next == NULL;
循环链表:p->next == head;
-