【知识框架】
一、线性表的定义
线性表(List):零个或多个数据元素的有限序列。
线性表的数据集合为{a1,a2,…,an},假设每个元素的类型均为DataType。其中,除第一个元素a1外,每一个元素有且只有一个直接前驱元素,除了最后一个元素an外,每一个元素有且只有一个直接后继元素。数据元素之间的关系是一对一的关系。
在较复杂的线性表中,一个数据元素可以由若干个数据项组成。在这种情况下,常把数据元素称为记录,含有大量记录的线性表又称为文件
二、线性表的顺序存储结构
一、顺序表
1、顺序表的基本概念
概念:用一组地址连续的存储单元依次存储线性表的数据元素,这种存储结构的线性表称为顺序表。
特点:逻辑上相邻的数据元素,物理次序也是相邻的。
只要确定好了存储线性表的起始位置,线性表中任一数据元素都可以随机存取,所以线性表的顺序存储结构是一种随机存取的储存结构,因为高级语言中的数组类型也是有随机存取的特性,所以通常我们都使用数组来描述数据结构中的顺序储存结构,用动态分配的一维数组表示线性表。
2、顺序表存储结构
//头文件
#include<stdio.h>
#include<stdlib.h>
#define MAXSIZE 20 //线性表存储空间的初始分配量
#define OK 1 //成功标识
#define ERROR 0 //失败标识
typedef int Status; //Status是函数的类型,其值是函数结果状态代码,如OK等
typedef int ElemType; //ElemType的类型根据实际情况而定,这里假定为int
//顺序表数据结构
typedef struct
{
ElemType *elem;
int length;
}SqList;
3、构造一个空的顺序表L
Status InitList(SqList* L){
//构造一个空的线性表L
L -> elem = (ElemType *)malloc(MAXSIZE*sizeof(ElemType));
if(!L -> elem){
return ERROR;
}
L -> length = 0;
return OK;
}
4、顺序表的插入
/*
插入操作
初始条件:顺序表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->elem[k+1] = L->elem[k];
}
}
L->elem[i-1] = e; //将新元素插入
L->length++; //长度加1
return OK;
}
5、顺序表的删除
/*
删除操作
初始条件:顺序表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 -> elem[i-1];
if(i < L->length){ //如果删除位置不在最后位置
for(k = i;k < L->length;k++){
L->elem[k-1] = L->elem[k];
}
}
L->length--; //长度减1
return OK;
}
6、获取顺序表某一位置上的元素
/*
获取元素操作
初始条件:顺序表L已存在
操作结果:用e返回L中第i个数据元素的值
*/
Status GetElem(SqList L, int i, ElemType *e){
if(L.length == 0 || i<1 || i>L.length){
return ERROR;
}
*e = L.elem[i-1];
return OK;
}
7、读取顺序表所有元素
/*打印线性表中的所有元素*/
void OutPut(SqList L){
printf("当前顺序表的长度:%d\n", L.length);
for(int i = 0; i < L.length; i++){
printf("%d ",L.elem[i]);
}
printf("\n");
}
8、运行测试
int main()
{
SqList L;
printf("------构造一个空的线性表L------\n");
InitList(&L);
OutPut(L); //打印结果
printf("------测试插入10个数------\n");
for(int i = 1;i <= 10; i++){
ListInsert(&L,i,i);
}
OutPut(L); //打印结果
printf("------在第三位之前插入0------\n");
ListInsert(&L,3,0);
OutPut(L); //打印结果
printf("------删除第6位的数据------\n");
ElemType e;
ListDelete(&L,6,&e);
printf("删除的数据为:%d\n", e);
OutPut(L); //打印结果
printf("------获取元素操作------\n");
GetElem(L,5,&e);
printf("得到第5个元素:%d", e);
}
小结
1、顺序表时间复杂度
从以上代码可以很明显的看出,线性表的顺序存储结果在读、存数据是的时间复杂度是O(1),插入、删除操作的时间复杂度是O(n)。
2、顺序表的优缺点
优点:无须为表中元素之间的逻辑关系而增加额外的存储空间;可以快速的存取表中任一位置的元素。
缺点:插入和删除操作需要移动大量元素;当线性表长度较大时,难以确定存储空间的容量;造成存储空间的“碎片”。
线性表的链式存储结构
一、单链表
1、单链表的基本概念
在链式结构中,除了要存储数据元素的信息外,还要存储它的后继元素的存储地址。因此,为了表示每个数据元素ai与其直接后继元素ai+1之间的逻辑关系,对数据ai来说,除了存储其本身的信息之外,还需要存储一个指示其直接后继的信息(即直接后继的存储位置)。我们吧把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称做指针或链。这两部分信息组成数据元素ai的存储映像,称为结点(Node)。
n个结点(ai的存储映像)链结成一个链表,即为线性表(a1, a2, …, an)的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表。
把链表中第一个结点的存储位置叫做头指针.
有时为了方便对链表进行操作,会在单链表的第一个结点前附设一个节点,称为头结点,此时头指针指向的结点就是头结点。
空链表,头结点的直接后继为空。
假设p是指向线性表第i个数据元素的指针,p->data表示第i个位置的数据域,p->next表示第i+1个位置的指针域,则第p+i个数据元素可表示为:
2、单链表的存储结构
#define OK 1 //正确标识
#define ERROR 0 //失败标识
typedef int Status; //Status是函数的类型,其值是函数结果状态代码,如OK等
typedef int ElemType; //ElemType的类型根据实际情况而定,这里假定为int
/*线性表的单链表存储结构*/
//构造结点
typedef struct Node
{
ElemType data;
struct Node *next;
} Node;
//构造LinkList
typedef struct {
int lenght;
Node *next;
}*LinkList;
3、构造单链表
/*构造一个带头结点的单链表*/
Status InitList(LinkList *L){
//生成一个空的LinkList和一个新结点
LinkList p = (LinkList)malloc(sizeof(LinkList));
Node *q = (Node *)malloc(sizeof(Node)); //头结点
q->next = NULL; //头结点的后继指向null
p->next = q; //头指针指向头结点
p->lenght = 0; //初始长度为0
(*L) = p;
return OK;
}
4、单链表的插入
若将将节点s插入到节点p和几点p->next之间,如下图所示,其核心操作是:
s->next=p->next;
p->next=s;
具体代码如下:
/**
* 单链表插入操作
* 初始条件:线性表L已存在
* 操作结果:在L中第pos个位置之前插入新的数据元素e,L的长度增加1
*/
Status ListInsert(LinkList *L, ElemType elem, int pos){
if(pos<1 || pos > (*L)->lenght+1){
return ERROR;
}
//寻找第pos个结点
Node *p = (*L)->next; //头结点
for(int i=1; i<pos; i++){
p = p->next;
}
//生成一个新结点
Node *q = (Node *) malloc(sizeof(Node));
q->data = elem;
q->next = p->next; //将p的后继赋值给q的后继
p->next = q; //将q赋值给p的后继
(*L)->lenght += 1; //链表长度加1
return OK;
}
5、单链表的删除
/**
* 单链表删除操作
* 初始条件:线性表L已存在
* 操作结果:删除L的第pos个数据元素,并用e返回其值,L的长度减1
*/
Status ListDelete(LinkList *L, ElemType *elem, int pos){
if(pos < 1 || pos>(*L)->lenght){
return ERROR;
}
//寻找到第pos个结点
Node *p = (*L)->next, *q;
for(int i=1; i<pos; i++){
p=p->next;
}
q = p->next; //要删除的结点
p->next = q->next;
free(q);
(*L) -> lenght -= 1;
return OK;
}