线性表
线性表
线性表是具有相同特性的数据元素的一个有限序列; 是一种典型的线性结构
线性表的两种存储结构:
- 顺序存储结构
- 链式存储结构
抽象数据类型线性表
抽象数据类型线性表的定义:
ADT List{
数据对象:D=ai|ai属于Elemset,(i=0,1,2,3,...,n,n>0)}
数据关系:R=<ai-1,ai>|ai-1,ai属于D,(i=2,3,...,n)}
基本操作:
InitList(&L);
操作结果:创建一个空的线性表L
DestroyList(&L);
初始条件:线性表L已存在
操作结果:销毁线性表L
ClearList(&L);
初始条件:线性表L已存在
操作结果:将线性表L重置为空表
ListEmpty(L);
初始条件:线性表L已存在
操作结果:若线性表L为空表返回TRUE;否则返回FALSE
ListLength(L);
初始条件:线性表L已存在
操作结果:返回线性表L中的数据元素个数
GetElem(L,i,&e);
初始条件:线性表L已存在; 1<=i<=ListLength(L)
操作结果:用e返回线性表L中第i个数据元素的值
LocateElem(L,e,compare());
初始条件:线性表L已存在; compare()是数据元素判定函数
操作结果:返回L中第1个与e满足compare()数据元素的位序,若不存在则返回0
PriorElem(L,cur_e,&pre_e);
初始条件:线性表L已存在
操作结果:若,cur_e是L的数据元素且不是第一个,则用pre_e返回其前驱;否则操作失败pre_e无意义
NextElem(L,cur_e,&next_e);
初始条件:线性表L已存在
操作结果:若,cur_e是L的数据元素且不是最后一个,则用next_e返回其后继;否则操作失败next_e无意义
ListInsert(&L,i,e);
初始条件:线性表L已存在; 1<=i<=ListLength(L)+1
操作结果:在L的第i个位置之前插入新的元素e,L长度加一
ListDelete(&L,i,&e);
初始条件:线性表L已存在; 1<=i<=ListLength(L)
操作结果:删除L的第i个数据元素,并用e返回其值,L长度减一
ListTraverse(&L,visited());
初始条件:线性表L已存在
操作结果:依次对线性表中每个元素调用visited()
}ADT List
顺序表的定义
struct Book{
char id[20]; //ISBN
char name[50];
int price;
};
typedef struct{
Book *elem;
int length;
}SqList;
链表的定义
struct Book{
char id[20]; //ISBN
char name[50];
int price;
};
typedef struct Lnode{
Book data;
struct Lnode *next;
}LNode,*LinkList;
线性表的合并
将Lb表中无重复的元素依次插入La的表尾
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 100
typedef struct{
int elem[MAXSIZE];
int length;
}List;
int GetElem(List L, int i ,int e){
if(i<1||i>L.length) return 0;
e=L.elem[i-1];
return 1;
}
int LocateElem(List L,int e){
for (int i = 0; i <L.length ; i++) {
if(L.elem[i]==e) return i+1;
}
return 0;
}
int ListInsert_L(List *L,int i,int e){
if(i<1||i>L->length+1||L->length==MAXSIZE) return 0;
for (int j = L->length; j >=i-1 ; j--) {
L->elem[j+1]=L->elem[j];
}
L->elem[i-1]=e;
L->length++;
return 1;
}
void Union(List *La, List Lb){
int La_length=La->length;
int Lb_length=Lb.length;
int e=0;
for (int i = 1; i <=Lb_length ; i++) {
GetElem(Lb,i,e);
if (!LocateElem(Lb,e)){
ListInsert_L(La,++La_length,e);
}
}
} //时间复杂度为O(La.length)*(Lb.length)
int main(void)
{
}
单链表、循环链表、双向链表的时间效率比较
查找表头结点(首元结点) | 查找表尾结点 | 查找结点*p的前驱结点 | |
---|---|---|---|
带头结点的单链表L | O(1) | O(n) | 无法找到其前驱 |
带头结点仅设头指针L的循环单链表 | O(1) | O(n) | O(n) |
带头结点仅设尾指针R的循环单链表 | O(1) | O(1) | O(n) |
带头结点的双向循环链表L | O(1) | O(1) | O(1) |
顺序表、链表的比较
链式存储
- 优:
- 结点空间可以动态申请和释放
- 插入和删除时不需要移动大量元素,只需要修改指针
- 缺:
- 存储密度(结点数据本身占用的空间/结点占用的空间总量)小:每个结点的指针域需要额外占用存储空间;一般存储密度越大,存储空间利用率越高,显然顺序表的存储密度为1(100%)
- 链式存储结构是非随机存取结构,增加了算法的复杂度
比较项目 | 存储结构 | 顺序表 | 链表 |
---|---|---|---|
空 | 存储空间 | 预先分配,会导致空间闲置或溢出 | 动态分配,不会出现空间闲置或溢出 |
间 | 存储密度 | 存储密度=1 | 存储密度<1 |
时 | 存取元素 | 随机存取,按位置访问元素,时间复杂度为O(1) | 顺序存取,按位置访问元素,时间复杂度为O(n) |
间 | 插入删除 | 时间复杂度为O(n) | 不需要移动元素,时间复杂度为O(1) |
使用 | 情况 | 表长变化不大,且能事先确定变化范围;很少进行插入、删除操作;经常按元素位置序号访问数据元素 | 长度变化较大;频繁进行插入、删除 |