线性表(List):
零个或多个数据元素的有限序列。
线性表的抽象数据类型:
ADT 线性表(List) Data
线性表的数据对象集合{a1,a2,…,an},每个元素的类型均为DataType。其中除第一个元素a1外,每一个元素有且只有一个前驱元素,除了最后一个元素an外,每一个元素有且只有一个直接后继元素。数据元素之间的关系是一对一的关系。
Operation
InitList( *L ): 初始化操作,建立一个空的线性表L.
ListEmpty( L ): 若线性表为空,返回true,否则返回false。
ClearList( *L ): 将线性表清空。
GetElem( L,i,*e ): 将线性表L中第i个位置的元素值返回给e。
LocateElem(L,e): 在线性表L中查找与给定值e相等的元素,如果查找成功,返回true,否则返回false。
ListInsert( *L,i,e): 在线性表L中第i个位置插入新元素e。
ListDelete( *L,i,*e): 删除线性表L中第i个位置元素,并用e返回其值。
ListLength( L ): 返回线性表L的元素个数。
endADT
线性表的存储结构
顺序存储结构:用一段地址连续的存储单元依次存储线性表的数据元素。
用数组来实现线性表,通过下标就可以找到数组的下标,所以结构体中无需后继元素的地址。因为线性表中有插入和删除操作,所以我们需要及时的知道表的实际长度,故设置了length变量
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType; /* ElemType类型根据实际情况而定,这里假设为int */
typedef struct
{
ElemType data[MAXSIZE]; /* 数组,存储数据元素 */
int length; /* 线性表当前长度 */
}SqList;
- 链式存储结构:用链表的方式依次连接任意空闲的储存单元。
单链表实现线性表,除尾结点外,每个结点都需要知道下一个结点的位置,所以设置结点型的指针变量,去指向下一个结点。
typedef int Status;/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType;/* ElemType类型根据实际情况而定,这里假设为int */
typedef struct Node
{
ElemType data;
struct Node *next;
}Node;
typedef struct Node *LinkList; /* 定义LinkList */
链式结构中,存储数据信息的域称为数据域,存储直接后继位置的域称为指针域。
链式存储结构具有头指针指向第一个结点,为了方便操作设置了头结点。
链表中第一个结点的存储位置叫做头指针,头结点的指针域指向第一个结点的指针
头指针和头结点的异同:
头指针:
- 头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针。
- 头指针具有标识作用,所以常用头指针冠以链表的名字
- 无论链表是否为空,头指针均不为空。头指针是链表的必要元素。
- 头结点
- 头结点是为了操作的统一和方便而设立的,放在第一个元素的结点之前,其数据域一般无意义(也可存放链表的长度)。
- 有了头结点,对在第一元素结点前插入结点和删除第一结点其操作与其它结点的操作就统一了。
- 头结点不一定是链表必须要素。
初始化线性表:
顺序存储结构:
/* 初始化顺序线性表 */
Status InitList(SqList *L)
{
L->length=0;
return OK;
}
链式存储结构:
/* 初始化顺序线性表 */
Status InitList(LinkList *L)
{
*L=(LinkList)malloc(sizeof(Node)); /* 产生头结点,并使L指向此头结点 */
if(!(*L)) /* 存储分配失败 */
return ERROR;
(*L)->next=NULL; /* 指针域为空 */
return OK;
}
获取元素操作:
顺序存储结构:
/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) */
/* 操作结果:用e返回L中第i个数据元素的值,注意i是指位置,第1个位置的数组是从0开始 */
Status GetElem(SqList L,int i,ElemType *e)
{
if(L.length==0 || i<1 || i>L.length)
return ERROR;
*e=L.data[i-1];
return OK;
}
链式存储结构:
/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) */
/* 操作结果:用e返回L中第i个数据元素的值 */
Status GetElem(LinkList L,int i,ElemType *e)
{
int j;
LinkList p; /* 声明一结点p */
p = L->next; /* 让p指向链表L的第一个结点 */
j = 1; /* j为计数器 */
while (p && j<i) /* p不为空或者计数器j还没有等于i时,循环继续 */
{
p = p->next; /* 让p指向下一个结点 */
++j;
}
if ( !p || j>i )
return ERROR; /* 第i个元素不存在 */
*e = p->data; /* 取第i个元素的数据 */
return OK;
}
线性表的插入操作:
顺序存储结构:
表未满时,找到表尾后面一个元素的下标,对数组中的该元素赋值即可。
/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L), */
/* 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 */
Status ListInsert(SqList *L,int i,ElemType e)
{
int k;
if (L->length==MAXSIZE) /* 顺序线性表已经满 */
return ERROR;
if (i<1 || i>L->length+1)/* 当i比第一位置小或者比最后一位置后一位置还要大时 */
return ERROR;
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 OK;
}
链式存储结构:
链表无法和数组不同,链表的空间是随时用随时开的,所以我们需要找到待插入位置的前一个结点P的地址,将待插入结点S的的后继元素S->next赋值为p->next,再将p的next指针,指向s.
即 S->next = p->next; p->next = S; 注意不能弄反,否则将会使S结点的next指针指向自己。
/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L), */
/* 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 */
Status ListInsert(LinkList *L,int i,ElemType e)
{
int j;
LinkList p,s;
p = *L;
j = 1;
while (p && j < i) /* 寻找第i个结点 */
{
p = p->next;
++j;
}
if (!p || j > i)
return ERROR; /* 第i个元素不存在 */
s = (LinkList)malloc(sizeof(Node)); /* 生成新结点(C语言标准函数) */
s->data = e;
s->next = p->next; /* 将p的后继结点赋值给s的后继 */
p->next = s; /* 将s赋值给p的后继 */
return OK;
}
线性表的删除操作:
顺序存储结构:
/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) */
/* 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1 */
Status ListDelete(SqList *L,int i,ElemType *e)
{
int k;
if (L->length==0) /* 线性表为空 */
return ERROR;
if (i<1 || i>L->length) /* 删除位置不正确 */
return ERROR;
*e=L->data[i-1];
if (i<L->length) /* 如果删除不是最后位置 */
{
for(k=i;k<L->length;k++)/* 将删除位置后继元素前移 */
L->data[k-1]=L->data[k];
}
L->length--;
return OK;
}
链式存储结构:
找到第i个结点q前面的一个结点p,使p的next指针越过q结点,指向q后面的第一个结点,导致没有指针指向结点q。之后释放结点q。即 p->next = q->next;
/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) */
/* 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1 */
Status ListDelete(LinkList *L,int i,ElemType *e)
{
int j;
LinkList p,q;
p = *L;
j = 1;
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; /* 将q的后继赋值给p的后继 */
*e = q->data; /* 将q结点中的数据给e */
free(q); /* 让系统回收此结点,释放内存 */
return OK;
}
顺序和链式存储结构的差异:
- 存储分配方式:
- 顺序存储结构用一段连续的存储单元依次存储线性表的数据元素。
- 单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素。
- 时间性能
- 查找
- 顺序存储结构O(1)
- 单链表O(n)
- 插入和删除
- 顺序存储结构需要平均移动表长一半的元素,时间为O(n)
- 单链表在找出某位置的指针后,插入和删除时间仅为O(1)
- 空间性能
- 顺序存储结构需要预分配存储空间,分大了,浪费,分小了易发生溢出
- 单链表不需要分配储存空间,只要有就可以分配,元素个数也不受限制
- 查找
当我们的线性表需要频繁查找,很少使用插入和删除时,使用顺序存储结构比较好,反之,则使用单链表比较好。