【数据结构】线性表

1、线性表定义和基本操作

1.1 线性表定义

定义:是具有相同数据元素的n(n>=0)个数据元素的有限序列,记为L。线性有序的逻辑结构
在这里插入图片描述
其中n为表长,当n=0时是一个空表
a1为表头元素,只有一个直接后继;an是表尾元素只有一个直接前驱;其他元素都有一个直接前驱,一个直接后继

特征:
元素的个数有限
元素具有逻辑上的顺序性,表示元素有其先后次序
元素都是数据元素,每个元素都是单个元素
元素的数据类型相同(占用相同大小的存储空间)
元素具有抽象性,即仅讨论元素间的逻辑关系,而不考虑元素究竟表示什么内容

线性表是一种逻辑结构,表示元素之间一对一的相邻关系
顺序表和链表是指存储结构
二者属于不同层面的概念

1.2 基本操作

初始化:InitList(&L)
求表长:Length(L)
按值查找:LocateElem(L,e)
按位查找:GetElem(L,i)
插入:ListInsert(&L,i,e)
删除:ListDelete(&L,i,&e)
打印:PrintList(L)
判空:Empty(L)
销毁:DestroyList(&L)

2、线性表的实现

2.1 顺序实现(顺序存储)

2.1.1 顺序表定义

顺序表:线性表的顺序存储。使用一组地址连续的存储单元依次存储线性表中的数据元素,表中元素的逻辑顺序于其物理顺序相同

i为元素ai的位序
起始位置位LOC(A),sizeof(ElemType)是每个数据元素所占存储空间大小,表L对应的顺序存储:在这里插入图片描述

线性表顺序存储类型:

// 静态分配:
#define MaxSize 50
typedef struct{
	ElemType data[MaxSize];
	int length;
}SqList;


//动态分配
#define InitSize 100
typedef struct{
	ElemType *data;
	int MaxSize,length;
}SqList;

SqList L;
L.data = new ElemType[InitSize];
L.MaxSize = InitSize;
L.length = 0;

特点:
任一元素都可随机存取,通过首地址和元素序号在时间O(1)内找到指定元素。即线性表是一种随机存取的存储结构
存储密度高,每个节点只存储数据元素
逻辑上相邻的元素物理上也相邻,所以插入删除需要移动大量元素

2.1.2 基本操作实现

(1)插入操作
最好情况:在表尾插入(即i=n+1),元素后移语句将不执行,时间复杂度为O(1)
最坏情况:在表头插入(即i= 1),元素后移语句将执行n次,时间复杂度为O(n)
平均情况:假设pi(pi= 1/(n+1))是在第i个位置上插入一个结点的概率,则在长度为n的线性表中插入一个结点时,所需移动结点的平均次数为在这里插入图片描述因此线性表插入算法平均时间复杂度为O(n)

bool ListInsert(SqList &L,int i,ElemType e){
if(i<1||i>L.length+1) //判断i的范围是否有效
	return false;
if(L.length>=MaxSize) //当前存储空间已满,不能插入
	return false;
for(int j=L.length;j>=i;j--) //将第i个元素及之后的元素后移
	L.data[j1-L.data [j-1];
L.data[i-1]-e; //在位置i处放入e
L.length++; //线性表长度加1
return true;

(2)删除操作
最好情况:删除表尾元素(即i=n),无须移动元素,时间复杂度为O(1)
最坏情况:删除表头元素(即i=1),需移动除表头元素外的所有元素,时间复杂度为O(n)
平均情况:假设pi(pi =1/n)是删除第i个位置上结点的概率,则在长度为n的线性表中删除一个结点时,所需移动结点的平均次数为在这里插入图片描述因此,线性表删除算法的平均时间复杂度为O(n)

bool ListDelete(SqList &L,int i,ELemType &e){
	if(i<1 || i>L.length) //判断i的范围是否有效
		return false;
	e=L.data[i-1]; //将被删除的元素赋值给e
	for (int j=i; j<L.length; j++) //将第i个位置后的元素前移
		L.data[j-1] = L.data[j];
	L.length--; //线性表长度减1
	return true;
}

(3)按值查找
最好情况:查找的元素就在表头,仅需比较一次,时间复杂度为O(1)
最坏情况:查找的元素在表尾(或不存在)时,需要比较n次,时间复杂度为O(n)
平均情况:假设pi (pi =1/n)是查找的元素在第i (1<=i<=L.length)个位置上的概率,则在长度为n的线性表中查找值为e的元素所需比较的平均次数为在这里插入图片描述
因此,线性表按值查找算法的平均时间复杂度为O(n)

int LocateElem (sqList L, ElemType e){
	int i;
	for(i=0;i<L.length;i++)
		if(L.data[i]==e)
			return i+1; //下标为i的元素值等于e,返回其位序i+1
	return 0; //退出循环,说明查找失败
}

2.2 链式实现

链表:线性表的链式存储。通过一组任意的存储单元来存储线性表中的数据元素

2.2.1 单链表(指针实现)

每个节点除存放元素自身的信息外,还需要存放一个指向其后继元素的指针

单链表中结点类型的描述如下:

typedef struct LNode { //定义单链表结点类型
	ElemType data; //数据域
	struct LNode *next; //指针域
} LNode, *LinkList;

特点:
解决了循序表需要大量连续存储单元的缺点
附加指针域,浪费存储空间 单链表元素离散分布在存储空间,是非随机存取的存储结构

带头结点的单链表:在这里插入图片描述
头指针:不管带不带头结点,头指针始终指向链表的第一个结点
头结点:是带头结点的链表中的第一个结点,通常不存储数据信息
引入头结点的优点:
① 由于第一个数据结点的位置被存放在头结点的指针域中,因此在链表的第一个位置上的操作和在表的其他位置上的操作一致,无须进行特殊处理。
② 无论链表是否为空,其头指针都指向头结点的非空指针(空表中头结点的指针域为空),因此空表和非空表的处理也就得到了统一。

(1)头插法建立单链表
在这里插入图片描述
读入数据的顺序与生成的链表中的元素的顺序是相反的

LinkList List HeadInsert (LinkList &L){ //逆向建立单链表
	LNode *s; int x;
	L=(LinkList)malloc(sizeof(LNode)); //创建头结点
	L->next-NULL;//初始为空链表
	scanf ( "%d", &x);//输入结点的值
	while(x!=9999){ //输入9999表示结束
		s=(LNode*) malloc(sizeof (LNode)) ;//创建新结点
		s->data=x;
		s->next=L->next;L->next=s; //将新结点插入表中,工为头指针
		scanf ("%d", &x);
	)
	return L;
}

(2)尾插法建立单链表
结点次序于输入数据顺序一致
在这里插入图片描述

LinkList List_TailInsert (LinkList &L){ //正向建立单链表
	int X; //设元素类型为整型
	L-(LinkList) malloc(sizeof (LNode));
	LNode *s, *r=L; //r为表尾指针
	scanf ("%d", &x);//输入结点的值
	while(x!=9999){ //输入9999表示结束
		s=(LNode *) malloc(sizeof(LNode));
		s->data=x;
		r->next=s;r=s; //r指向新的表尾结点
		scanf ("%d", &x);
	}
	r->next-NULL; //尾结点指针置空
	return L;
}

(3)按序号查找结点值
时间复杂度为O(n)

LNode *GetElem (LinkList L,int i){
	int j=1; //计数,初始为1
	LNode *p=L->next; //头结点指针赋给p
	if(i==0)
		return L; //若i等于0,则返回头结点
	if(i<1)
		return NULL; //若i无效,则返回NULL
	while(p&&j<i){ //从第1个结点开始找,查找第i个结点
		p=p->next;
		j++;
	}
	return p;//返回第i个结点的指针,若i大于表长,则返回NULL
}

(4)按值查找表结点
时间复杂度为O(n)

LNode *LocateElem (LinkList L,ElemType e){
	LNode *p=L->next;
	while(p!=NULL&&p->data!=e)//从第1个结点开始查找data域为e的结点
		p=p->next;
	return p; //找到后返回该结点指针,否则返回NULL
}

(5)插入节点操作
一、后插
在这里插入图片描述
① p=GetElem (L,i-1); //查找插入位置的前驱结点,时间复杂度O(n)
② s->next=p->next; //对应图中步骤①
③p->next=s; //对应图中步骤②

二、前插
可以依旧采用后插操作然后交换p和s的data值
//将s结点插入到p之前的主要代码片段
s->next-p->next;//修改指针域,不能颠倒
p=>next-s;
temp=p->data; //交换数据域部分
p->data=s->data;
s->data=temp;

(6)删除结点操作
在这里插入图片描述
p=GetElem (L,i-1);//查找删除位置的前驱结点
g=p->next;//令q指向被删除结点
p->next=q->next//将*q结点从链中“断开”
free(q) ;//释放结点的存储空间R

(7)求表长
从第一个结点开始遍历,直到尾部
需要注意:单链表的长度是不包括头结点,因此不带头结点和带头结点的单链表在求表长操作上会略有不同。对不带头结点的单链表,当表为空时,要单独处理。

2.2.2 双链表(指针)

前驱指针prior,后继指针next
这里是引用

双链表中结点类型的描述如下:

typedef structDNode { //定义双链表结点类型
	ElemType data; //数据域
	struct  DNode *prior, *next; //前驱和后继指针
} DNode,*DLinklist;

双链表中按值查找和按位查找的操作于单链表相同,但插入删除不同
(1)双链表插入
在这里插入图片描述
在这里插入图片描述

(2)双链表的删除操作
在这里插入图片描述
在这里插入图片描述

2.2.3 循环链表(指针)

(1)循环单链表
在这里插入图片描述
在任何一个位置上的插入和删除操作都是等价的,无须判断是否是表尾
在单链表中只能从表头结点开始往后顺序遍历整个链表,而循环单链表可以从表中的任意一个结点开始遍历整个链表
有时对单链表常做的操作是在表头和表尾进行的,此时对循环单链表不设头指针而仅设尾指针,从而使得操作效率更高。其原因是,若设的是头指针,对表尾进行操作需要O(n)的时间复杂度,而若设的是尾指针r,r->next即为头指针,对表头与表尾进行操作都只需要O(1)的时间复杂度

(2)循环双链表
在循环双链表L中,某结点*p 为尾结点时,p->next==L
当循环双链表为空表时,其头结点的prior域和next域都等于L

在这里插入图片描述

2.2.4 静态链表(数组)

静态链表借助数组来描述线性表的链式存储结构
结点也有数据域data和指针域next
这里的指针是结点的相对地址(数组下标),又称游标
和顺序表一样,静态链表也要预先分配一块连续的内存空间
在这里插入图片描述在这里插入图片描述
静态链表以next==-1作为其结束的标志
静态链表的插入、删除操作与动态链表的相同,只需要修改指针,而不需要移动元素
静态链表没有单链表使用起来方便,但在一些不支持指针的高级语言(如 Basic)中,这是一种非常巧妙的设计方法

#define Maxsize 50
//静态链表的最大长度
typedef struct{ //静态链表结构类型的定义
	ElemType data; //存储数据元素
	int next; //下一个元素的数组下标
}SLinkList[Maxsize];

3、编程实现(部分)

顺序表编程实现
单链表编程实现

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值