线性表的概念及顺序存储
一、线性表的概念及运算
1. 定义
- 线性表(Linear List)是由n(n>=0)个类型相同的数据元素a1,a2,an组成的有限序列,记做(a1,a2,…,a1,ai,ai+1,…an)
- 数据元素之间是一对一的关系,即每个数据元素最多有一个直接前驱和一个直接后继。
- 逻辑结构图:o-o-o-o-o
2.特点
①同一性:
线性表由同类数据元素组成,每一个a必须属于同一数据对象。
②有穷性:
线性表由有限个数据元素组成,表长度就是表中数据元素的个数。
③有序性:
线性表中相邻数据元素之间存在着序偶关系<a,a+1>。
二、线性表的顺序存储
定义
- 是指用一组地址连续的存储单元依次存储线性表中的各个元素,使得线性表中在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中
- 采用顺序存储结构的线性表通常称为顺序表。
- 顺序表中数据元素的存储地址是其序号的线性函数,只要确定了存储顺序表的起始地址(即基地址),计算任意一个元素的存储地址的时间是相等的,具有这一特点的存储结构称为随机存取结构
- 假设线性表中每个元素占k个单元,第一个元素的地址为loc(a),则第i个元素的地址为:loc(ai)=loc(a)+(i-1)x k
C语言的定义
#define maxsize //线性表可能达到的最大长度
typedef struct
{
ElemType elem[maxsize];
int length; //表长
}SeqList;
SeqList L;或
SeqList *L;
注意区分元素的序号和数组的下标,如a1的序号为1,而其对应的数组元素L.elem[0]下标为0。或者不使用下标为0的单元,序号和下标保持一致, 多占用一个元素空间即可。
基本运算
- 查找操作
- 按序号查找GetData(L,i)
- 按内容查找Locate(L,x)
- 插入操作
#define OK 1
#define ERROR 0
int InsList(SeqList *L,int i,ElemType x)
{
int k;
if((i<1)||(i>L->length+1))
{
printf(“插入位置i值不合法”);
return(ERROR);
}
if(L->length>=maxsize-1)
printf(“表已满无法插入”);
return(ERROR);
}
for(k=L->length;k>=i;k--)
L->elem[k+1]=L->elem[k];
L->elem[i]=x;
L->length++;
return(OK);
}
# 时间复杂度:O(n)
- 删除操作
int DelList(SeqList *L,int i,ElemType *e){
int k;
if((i<1)||(i>L->length)){
printf(“删除位置不合法!”);
return(ERROR);
}
*e=L->elem [i];
for(k=i;k<=L->length-1;k++)
L->elem[k]=L->elem[k+1];
L->length--;
return(OK);
}
# 时间复杂度:O(n)
优点:
- 用数组存储数据元素,操作方法简单,容易实现。
- 无须为表示结点间的逻辑关系而增加额外的存储开销。
- 存储密度高。
- 顺序表可按元素位序随机存取结点。
缺点:
- 插入或删除运算不方便,除表尾的位置外,在表的其它位置上进行插入或删除操作都必须移动大量的结点,其效率较低;
- 由于顺序表要求占用连续的存储空间,存储分配只能预先进行静态分配。因此当表长变化较大时,难以确定合适的存储规模。
链表
定义:采用链式存储结构的线性表称为链表。
实现角度:静态链表、动态链表
链接方式:单链表、双向链表、循环链表
单链表的概念及基本操作
链表:通过一组任意的存储单元来存储线性表中的数据元素
链表中的每个结点只有一个指针域。
单链表没有特殊说明,都带头结点
单链表包括两个域
数据域:用来存储结点的数据值
指针域:用来存储数据元素的直接后继的地址
单链表存储结构描述
typedef struct Node
{
ElemType data;
struct Node *next;
}LNode,*LinkList;
存储空间的分配和释放
// ma11oc 函数
void malloc(unsigned int size):
// ca11oc 函数
void *calloc(unsigned n, unsigned size):
// free 函数
void free(void *p):
单链表的建立
- 头插法建表:在链表的头部插入结点建立单链表,简称“头插法”。建立思想:首先申请一个头结点,并且将头结点指针域置空(NULL);然后每读入一个数据元素则申请一个结点,并插在链表的头结点之后。
Linklist CreateFromHead(){
LinkList L;
LNode *S;
int x;
int flag=1;
L=(Linklist)malloc(sizeof(LNode));//创建头结点
L->next=NULL;
scanf("%d",&x);//输入要插入的数据,如果是-1则不插入
while(x!=-1){
s=(LNode*)malloc(sizeof(LNode));//创建新结点
s->data=x;//新结点数据域赋值
s->next=L->next;//新结点指向头结点的下个结点
L->next=s;//头结点的下个结点指向新结点
scanf("%d",&x);
}
return L;
}
# 时间复杂度:O(n)
- 尾插法建表:在单链表的尾部插入结点建立单链表,简称“尾插法”。由于每次是将新结点插入到链表的尾部,所以增加一个指针「来始终指向链表中的尾结点,以便能够将新结点插入到链表的尾部。
Linklist CreateFromTail(){
LinkList L;
LNode *r,*s;
int x;
L=(LinkList)malloc(sizeof(LNode));
L->next=NULL;
r=L;
scanf("%d",&x);
while(x!=-1){
s=(LNode*)malloc(sizeof(LNode));
s->data=x;
s->next =r->next
r->next=s;
r=s;
scanf("%d",&x);
}
r->next=NULL;
return L;
}
# 时间复杂度:O(n)
单链表的插入
void InsList(LinkList L,int i,ElemType e){
LNode *pre,*s;
int k=0;
pre=L;
while(pre!=NULL&&k<i-1){
pre=pre->next;
k=k+1;
}//pre从表头指向目标位置i之前i-1。
if(k!=i-1){
printf(“插入位置不合理!”);
return;
}//如果目标位置i>表的长度会执行
s=(Node*)malloc(sizeof(Node));
s->data=e;
s->next=pre->next;
pre->next=s;
}
# 时间复杂度:O(n)
单链表删除
void DelList(LinkList L,int i,ElemType *e)
{
LNode *pre,*r;pre=L;int k =0;
while(pre->next!=NULL&&k<i-1){
pre=pre->next;
k=k+1;
}
if(k!=i-1)
{
printf(“删除结点的位置不合理!”);
return ERROR;
}
r-pre->next;
pre->next=pre->next->next;
free(r);
return OK;
}
# 时间复杂度:O(n)
循环链表(Circular Linked List)
是一个首尾相接的链表。将单链表最后一个结点的指针域由NULL改为指向头结点或线性表中的第一个结点,就得到了单链形式的循环链表,并称为循环单链表。在循环单链表中,表中所有结点被链在一个环上。
特点:
- 从表中任一结点出发都能访问到表中所有结点。
- 循环表是对称的,为了判断起始位置,一般要设置头结点。
头结点的设置也可将空表和非空表的逻辑状态及运算统
起来。 - 循环表的运算和线性链表基本一致,有时可简化某些运算。
例:有两个带头结点的循环单链表LA、LB,编写一个算法,将两个循环单链表合并为一个循环单链表,其头指针为LA。
先找到两个链表的尾,并分别由指针D、q指向它们,然后将第一个链表的尾与第二个表的第一个结点链接起来,并修改第二个表的尾q,使它的链域指向第一个表的头结点。
LinkList merge_1(LinkList LA,LinkList LB){
LNode *p,*q;
p=LA;
q=LB:
while(p->next!=LA)
p=p->next;
while(q->next!=LB)
q=q->next;
p->next=LB->next;
free(LB);
q->next=LA;
return(LA);
}
# 时间复杂度:O(n)
LinkList merge_2(LinkList RA,LinkList RB)
{
LNode *p;
p=RA->next;
RA->next=RB->next->next;
free(RB->next);
RB->next=p;
return(RB);
}
# 时间复杂度:O(1)
双向链表
在单链表的每个结点里再增加一个指向其前趋的指针域pior。这样形成的链表中就有两条方向不同的链,我们称之为双(向)链表。
typedef struct DNode
ElemType data;
struct DNode *prior,*next;
}DNode,*DoubleList;
插入
int DlinkIns(DoubleList L,int i,ElemType e)
{
DNode *s,*p;
s=(DNode*)malloc(sizeof(DNode));
if (s)
{
s->data=e;
s->prior-p->prior;
p->prior->next=s;
s->next-p;
p->prior=s;
return 1;
}
else return 0;
}
删除
int DlinkDel(DoubleList L,int i,ElemType *e)
{
DNode *p;
*e=p->data;
p->prior->next=p->next;
p->next->prior-p->prior,
free(p);
return 1;
}
一元多项式的表示和运算
一个一元多项式Pn(X)可按升幂的形式写成:
可以用线性表存储:(P0,P1,P2,…Pn)
用单链表存储多项式的结点结构
typedef struct Polynode{
int coef;
int exp;
Polynode *next;
}Polynode,*Polylist;
输入多项式的系数和指数,用尾插法建立一元多项式的链表。
Polylist polycreate(){
Polynode *head,*rear,*s;
int c,e;
rear=head=(Polynode *)malloc(sizeof(Polynode));
scanf("%d,%d",&c,&e);
while(c!=O){
s=(Polynode*)malloc(sizeof(Polynode));
s->coef=c;
s->exp=e;
rear->next=s;
rear=s;
scanf("%d,%d",&c,&e);
}
rear->next=NULL;
return(head);
}
两个多项式相加
- 若p->exp < q->exp
则结点p所指的结点应是“和多项式”中的一项,令指针p后移; - 若p->exp = q->exp
则将两个结点中的系数相加,当和不为零时修改结点P的系数域,释放q结点;
若和为零,则和多项式中无此项,从A中删去p结点,同时释放p和q结点; - 若p->exp >q->exp
则结点q所指的结点应是“和多项式”中的一项,将结点q插入在结点p之前,且
令指针q在原来的链表上后移。
void polyadd(Polylist polya;Polylist polyb){
.../* p和q分别指向polya和polyb链表中的当前计算结点*/
./* rear指向和多项式链表中的尾结点*/
while p!=NULL &&q!=NULL){
if p->exp < q->exp)
{.../* 将p结点加入到和多项式中
删除q节点*/}
else if p->exp == q->exp)
{.../*若指数相等,则相应的系数相加。
若系数为0则删除p,q节点*/}
else
{../*将q结点加入到和多项式中
删除p节点*/}
}
/*将多项式polya或polyb中剩余的结点加入到和多项式中*/
}