1.定义和特点
线性表是最基本、最简单、也是最常用的一种数据结构。线性表(linear list) 是数据结构的一种,一个线性表是n个具有相同特性的数据元素的有限序列。
对于非空的线性表和线性结构,其特点如下:
- 存在唯一的一个被称作"第一个"的数据元素
- 存在唯一的一个被称作"最后一个"的数据元素
- 除了第一个之外,结构中的每个数据元素均有一个前驱
- 除了最后一个之外,结构中的每个数据元素都有一个后继.
2.线性表的顺序表示与实现
2.1.顺序表示
线性表的顺序表示指的是一组地址连续的存储单元依次存储线性表的数据元素,这种表示也称为线性表的顺序存储结构或顺序映像. 通常,称这种存储结构的线性表为顺序表(Sequential List). 其特点是, 逻辑上相邻的数据元素,其物理次序也是相邻的.
#define MAXSIZE 100 //视情况而定
#define ElemType int //数据元素类型
//顺序表结构设计
typedef struct {
ElemType *data;//用于存储线性表中的数据元素的首地址
int length;//用来表示线性表中数据元素的个数
}Sqlist;
2.2.顺序表的基本操作
2.2.1. 初始化
Status InitList(Sqlist *L){
//为顺序表分配一个大小为MAXSIZE 的数组空间
L->data= malloc(sizeof(ElemType) *MAXSIZE);
//存储分配失败退出
if(!L->data)exit(ERROR);
//空表长度为0
L->length=0;
return OK;
}
2.2.2. 插入
/*
初始条件:顺序线性表L已存在,1≤i≤ListLength(L);
操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1
*/
Status ListInsert(Sqlist *L,int i,ElemType e){
//i值不合法判断
if((i<1) || (i>L->length+1)) return ERROR;
//存储空间已满
if(L->length==MAXSIZE) return ERROR;
//插入数据不在表尾,则先移动出空余位置
if(i <= L->length){
for(int j=L->length-1; j>=i-1;j--){
//插入位置以及之后的位置后移动1位
L->data[j+1] = L->data[j];
}
}
//将新元素e 放入第i个位置上
L->data[i-1] = e;
//长度+1;
++L->length;
return OK;
}
2.2.3. 顺序表取值
Status GetElem(Sqlist L,int i, ElemType *e){
//判断i值是否合理, 若不合理,返回ERROR
if(i<1 || i > L.length) return ERROR;
//data[i-1]单元存储第i个数据元素.
*e = L.data[i-1];
return OK;
}
2.2.3. 顺序表删除
/*
初始条件:顺序线性表L已存在,1≤i≤ListLength(L)
操作结果: 删除L的第i个数据元素,L的长度减1
*/
Status ListDelete(Sqlist *L,int i){
//判断线性表是否为空
if(L->length == 0) return ERROR;
//判断删除位置i的合法(合法值 1 <= i <=n),若不合法则返回ERROR
if((i<1) || (i>L->length+1)) return ERROR;
//将第 i+1个至第n个的元素依次向前移动一个位置
for(int j = i; j < L->length -1 ;j++){
//被删除元素之后的元素向前移动
L->data[j-1] = L->data[j];
}
//表长度-1;
L->length --;
return OK;
}
3.线性表的链式存储
3.1单链表
线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。
链式结构中,除了要存数据元素信息外,还要存储它的后继元素的存储地址。
我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称做指针或链。这两部分信息组成数据元素ai的存储映像,称为结点(Node)。
n个结点(ai的存储映像)链结成一个链表,即为线性表(a1,a2,...,an)的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表。单链表正是通过每个结点的指针域将线性表的数据元素按其逻辑次序链接在一起,如图所示。
有时,我们为了更加方便地对链表进行操作,会在单链表的第一个结点前附设一个结点,称为头结点。头结点的数据域可以不存储任何信息,也可以存储如线性表的长度等附加信息,头结点的指针域存储指向第一个结点的指针,如图所示
增加一个头结点的好处:
1.便于⾸元结点处理
2.便于空表和⾮空表的统⼀处理
3.2单链表的操作
基本定义
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OK 1
#define MAXSIZE 20 /* 存储空间初始分配量 */
typedef int Status;/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType;/* ElemType类型根据实际情况而定,这里假设为int */
//定义结点
typedef struct Node{
//数据域
ElemType data;
//指针域
struct Node *next;
}Node;
typedef struct Node * LinkList;
3.2.1初始化
Status InitList(LinkList *L){
//产生头结点,并使用L指向此头结点
*L = (LinkList)malloc(sizeof(Node));
//存储空间分配失败
if(*L == NULL) return ERROR;
//将头结点的指针域置空
(*L)->next = NULL;
return OK;
}
注意,头结点不是后期插入进去的,而是链表初始化时创建的。
3.2.2单链表的遍历
Status ListTraverse(LinkList L)
{
LinkList p=L->next;
while(p)
{
//依次对L的每个数据元素输出
printf("%d\n",p->data);
p=p->next;
}
printf("\n");
return OK;
}
3.2.3单链表的插入
/*
初始条件:顺序线性表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;
//寻找第i-1个结点
while (p && j<i) {
p = p->next;
++j;
}
//第i个元素不存在
if(!p || j>i) return ERROR;
//生成新结点s
s = (LinkList)malloc(sizeof(Node));
//将e赋值给s的数值域
s->data = e;
//将p的后继结点赋值给s的后继
s->next = p->next;
//将s赋值给p的后继
p->next = s;
return OK;
}
单链表的插入步骤就是先找到插入位置的前驱结点p, 将p的next指针赋值给要插入的结点s的next, 再将s赋值给p的next即可,其他的结点不受影响。
3.2.4单链表的头插法和尾插法
/*
初始条件:顺序线性表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)->next;
j = 1;
//查找第i-1个结点,p指向该结点
while (p->next && j<(i-1)) {
p = p->next;
++j;
}
//当i>n 或者 i<1 时,删除位置不合理
if (!(p->next) || (j>i-1)) return ERROR;
//q指向要删除的结点
q = p->next;
//将q的后继赋值给p的后继
p->next = q->next;
//将q结点中的数据给e
*e = q->data;
//让系统回收此结点,释放内存;
free(q);
return OK;
}
要删除单链表中指定位置i的元素,同插⼊入元素⼀一样, 首先应该找到该位置的前驱结点p; 然后将要删除的结点q的next赋值给p的next;最后释放q.
3.2.5单链表的插入
头插法
/* 随机产生n个元素值,建立带表头结点的单链线性表L(前插法)*/
void CreateListHead(LinkList *L, int n){
LinkList p;
//建立1个带头结点的单链表
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL;
//循环前插入随机数据
for(int i = 0; i < n;i++)
{
//生成新结点
p = (LinkList)malloc(sizeof(Node));
//i赋值给新结点的data
p->data = i;
//p->next = 头结点的L->next
p->next = (*L)->next;
//将结点P插入到头结点之后;
(*L)->next = p;
}
}
尾插法
/* 随机产生n个元素值,建立带表头结点的单链线性表L(后插法)*/
void CreateListTail(LinkList *L, int n){
LinkList p,r;
//建立1个带头结点的单链表
*L = (LinkList)malloc(sizeof(Node));
//r指向尾部的结点
r = *L;
for (int i=0; i<n; i++) {
//生成新结点
p = (Node *)malloc(sizeof(Node));
p->data = i;
//将表尾终端结点的指针指向新结点
r->next = p;
//将当前的新结点定义为表尾终端结点
r = p;
}
//将尾指针的next = null
r->next = NULL;
}
3.2.6单链表的读取
Status GetElem(LinkList L,int i,ElemType *e){
//j: 计数.
int j;
//声明结点p;
LinkList p;
//将结点p 指向链表L的第一个结点;
p = L->next;
//j计算=1;
j = 1;
//p不为空,且计算j不等于i,则循环继续
while (p && j<i) {
//p指向下一个结点
p = p->next;
++j;
}
//如果p为空或者j>i,则返回error
if(!p || j > i) return ERROR;
//e = p所指的结点的data
*e = p->data;
return OK;
}
4.链表结构与顺序存储结构优缺点对⽐
4.1存储分配⽅式
- 顺序存储结构⽤一段连续的存储单元依次存储线性表的数据元素;
- 单链表采⽤链式存储结构,⽤一组任意的存储单元存放线性表的元素;
4.2时间性能
- 查找: 顺序存储 O(1), 单链表O(n)
- 插⼊和删除:
- 存储结构需要平均移动⼀个表长一半的元素,时间O(n)
- 单链表查找某位置后的指针后,插入和删除为 O(1)
4.3空间性能
顺序存储结构需要预先分配存储空间,分太大,浪费空间;分⼩了,发⽣上溢出;
单链表不需要分配存储空间,只要有就可以分配, 元素个数也不受限制;