数据结构——线性表

写在文章前:

此文章是个人对数据结构知识的整理,适合纯小白阅读,大佬勿入。如果有错误,请指正。

文章整数知识点借用了浙大陈越老师写的《数据结构》的例子和内容,有兴趣的可以一起阅读提升

正文:

在数据的逻辑结构中,有种常见且简单的结构——线性结构,即数据元素之间构成一个有序的序列。下面先看一个例子:

一元多项式的存储问题:

f(x) = a0 + a1x + a2x^{2} + ... + anx^{n}

方法一:采用顺序存储结构直接表示一元多项式

例如:4x^{5} + 3x^{2} + 1

下标i012345...
a[i]103004

...

利用数组a存储多项式的相关数据,数据分量a[i]表示系数,下标表示对应的指数,数组中非零项分量的个数就是多项式的项数。

对于这种方法,我们很好地存储和表示了多项式。然而,但多项式比较稀疏的时候(1+x^{2000}),我们必须创建一个大数组去存储很少的信息,这个数组中绝大部分数据为0,空间效率非常的低,因此,我们引入了第二种方法来存储多项式。

方法二:采用顺序存储结构表示多项式的非零项

对于多项式来说,只有非零项是有信息价值的,所以我们只需要存储顺序表的非零项。对于每个非零项,有两个信息,一个是系数ai,一个是指数i。我们可以把多项式看成一个(ai,i)二元组的集合,为了方便计算,我们还可以按指数下降的顺序组织这个二元组{(an,n),(an-1,n-1),...,(a0,0)}

例如:1+x^{2000}+3x^{4000}

数组下标i012...
系数113...
指数020004000...

当然,方法二也不是完全比方法一好。当要实现多项式相加的时候,方法二显然要比方法一复杂一些。我们可以从头比较多项式的每一项,当指数不相同的时候,就要把指数大的那一项“拷贝”到结果多项式中,如果它们的指数一样且系数不为0,就在结果多项式中新增一个系数为它们之和的新项。

方法一和方法二的共同点是都用顺序结构(数组)表示多项式。然而,数组表示的一个问题是灵活性不够。由于无法事先知道多项式的非零项,我们只能根据可能的最大值确定数组的大小,当实际非零项数比较小的时候,空间利用率仍然很低。怎么办呢?

用链式结构!

方法三:用链式结构来存储多项式的非零项

用链表表示多项式时,每个链表结点存储多项式的一个非零项,包括系数和指数两个数据域和一个指针域,指向下一个结点。其结点结构可以表示为:

CoefExponNext

对于方法二中的多项式,可以表示为:

(p1,p2,p3对应结点1,结点2,结点3)

10

p2

12000p3
34000null

如果要实现多项式的相加,和方法二很类似,在这不细讲

不过,我们还有一种更复杂的多项式,那就是多元多项式!那又该怎么表示呢?

运用广义表,我们能很好地解决这个问题!

对于二元多项式9x^{12}y^{2}+4x^{12}+15x^{8}y^{3}-x^{8}y+3x^{2},我们可以把它看成关于x的一元多项式:\left ( 9y^{2}+4 \right )x^{12}+(15y^{3}-y)x^{8}+3x^{2},用如下方法表示:

上图就是一种广义表,也就是说一个结点可以有多个指针域,指向不同的结点。主线上是关于x的结点,而从主线发展出来的结点则是x系数的结点(包含y的多项式)。

由上述这个例子,我们将多项式问题抽象为由系数和指数所构成的二元组有序序列的存储和操作问题。现实中这一类共性的问题有很多:班级学生管理,银行等候队列的管理等等。如何将这种问题抽象为更一般的处理方法呢?我们要引入我们的主题——线性表!

线性表

线性表是由同一类型的数据元素构成的有序序列的线性结构。其抽象数据类型描述为:

类型名称:线性表

数据对象集:线性表是n个元素构成的有序序列,元素之间有着一对一的邻接逻辑关系

操作集:对于一个具体的线性表L\epsilonList,一个表述位序的整数i,一个元素x\epsilonElementType,有如下的基本操作:

(1)List MakeEmpty()  初始化一个新的线性表

(2)ElementType FindKth(List L, int i)  根据指定位序i,返回L中相应的元素ai

(3)Position Find(List L, ElementType x)  由指定元素x,返回线性表中第一个与元素x相同的元素位置

(4)bool Insert(List L, ElementType x, int i)  在位序i前插入元素

(5)bool Delete(List L, int i)  删除位序i的元素

(6)int Length(List L)  返回线性表的长度

线性表的顺序存储结构:

顺序存储结构是指在内存中用地址连续的一块存储空间存放线性表的各元素,在C语言中,我们用一维数组来表示这种数据区域。为了记录线性表最后一个元素在数组中的位置,我们增加一个变量Last来记录最后一个元素的位置

为了组织的整体性,我们用一个结构来封装数组和变量:

typedef struct LNode *PtrToLNode;
struct LNode{
	ElementType Data[MAXSIZE];
	Position Last;
};
typedef PtrToLNode List;

接下来,我们将一步步实现线性表的各种操作功能

1.初始化

List MakeEmpty(){
	List L;
	L = (List)malloc(sizeof(struct LNode));
	L->Last = -1;
	return L;
}

 2.查找

查找分为两类:按值查找和按位查找

线性表的按位查找很简单,就是根据位序返回数组对应位置的元素

对于按值查找,我们可以用一个指针i,一步一步的检查数组中的元素与要查找的元素是否相同,相同时再返回这个元素对应的位置

Position FindPos(List L, ElementType x){
	Position i = 0;
	while(i <= L->Last && L->Data[i] != x){
		i++;
	}
	if(i > L->Last) return ERROR;
	else return i;
}

3.插入

顺序表的插入方法是将要插入位置后面的元素往后移一位,然后将要插入的元素插入相应的位置。我们可以从表尾进行遍历,将元素后移,遍历到要插入元素位置的时候停止,再插入元素。

当然,必不可缺的一步是要检查线性表是否已经满和要插入位置的合法性!

bool Insert(List L, ElementType x, int i){
	Position j;
	if(L->Last == MAXSIZE-1){
		printf("表满");
		return false;
	}
	
	if(i<1 || i>L->Last+2){
		printf("插入位置不合法");
		return false;
	}
	
	for(j=L->Last; j >= i-1; j--){
		L->Data[j+1] = L->Data[j];
	}
	L->Data[i-1] = x;
	L->Last++;
	return true;
}

 4.删除

线性表的删除很简单,只需要把要删除元素后面的元素都往前移一位即可

bool Delete(List  L, int i){
	Position j;
	
	if(i<1 || i>L->Last+1){
		printf("位序%d不存在元素", i);
		return false;
	}
	
	for(j=i; j<=L->Last; j++){
		L->Data[j-1] = L->Data[j];
	}
	L->Last--;
	return true;
}

线性表顺序结构代码如下: 

#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#define MAXSIZE 100000
#define ERROR -1
#define OK 1
typedef int Status;
typedef int ElementType;

//线性表的顺序存储结构
typedef int Position;
typedef struct LNode *PtrToLNode;
struct LNode{
	ElementType Data[MAXSIZE];
	Position Last;
};
typedef PtrToLNode List;

//初始化
List MakeEmpty(){
	List L;
	L = (List)malloc(sizeof(struct LNode));
	L->Last = -1;
	return L;
}

//按值查找
Position FindPos(List L, ElementType x){
	Position i = 0;
	while(i <= L->Last && L->Data[i] != x){
		i++;
	}
	if(i > L->Last) return ERROR;
	else return i;
}

//插入
bool Insert(List L, ElementType x, int i){
	Position j;
	if(L->Last == MAXSIZE-1){
		printf("表满");
		return false;
	}
	
	if(i<1 || i>L->Last+2){
		printf("插入位置不合法");
		return false;
	}
	
	for(j=L->Last; j >= i-1; j--){
		L->Data[j+1] = L->Data[j];
	}
	L->Data[i-1] = x;
	L->Last++;
	return true;
}

//删除
bool Delete(List  L, int i){
	Position j;
	
	if(i<1 || i>L->Last+1){
		printf("位序%d不存在元素", i);
		return false;
	}
	
	for(j=i; j<=L->Last; j++){
		L->Data[j-1] = L->Data[j];
	}
	L->Last--;
	return true;
}

//按位查找
ElementType FindValue(List L, Position i){
	if(i<1 || i>L->Last+1) return ERROR;
	return L->Data[i-1];
}

//长度
int getLength(List L){
	return L->Last+1;
}

//输出线性表
Status Output(List L){
	int i;
	for(i=0; i<=L->Last; i++){
		printf("%d ", L->Data[i]);
	}
	printf("\n");
	return OK;
}

int main(){
	List L;
	L = MakeEmpty();
	int n, i, x;
	printf("请输入存取元素的个数:");
	scanf("%d", &n);
	for(i=1; i<=n; i++){
		scanf("%d", &x);
		Insert(L, x, i);
	}
	printf("此时线性表各元素如下:");
	Output(L);
	int e, j;
	printf("请输入要获取第几个元素:");
	scanf("%d", &e);
	j = FindValue(L, e);
	printf("该元素是:%d\n", j);
	int e2;
	printf("请输入要删除第几个元素:");
	scanf("%d", &e2);
	Delete(L, e2);
	printf("此时线性表各元素如下:\n");
	Output(L);
	printf("此时数组的长度是:%d\n", getLength(L));
	printf("请输入要插入的值和位置:");
	int v1, p;
	scanf("%d %d", &v1, &p);
	Insert(L, v1, p);
	printf("此时线性表各元素如下:");
	Output(L);
	int v2;
	printf("请输入要查找位置的值:");
	scanf("%d", &v2);
	printf("该值所在位置为:%d\n", FindPos(L, v2));
	return 0;
}

线性表的链式存储结构:

顺序结构实现了物理上的相邻,而链式结构实现了逻辑上的相邻,结点之间靠指针next相连!

为什么要用链式结构?

对于顺序结构,插入和删除都要移动大量的数据,影响了时间效率,而链式结构的插入和删除只在局部进行,时间复杂度为O(1),Amazing!

链式结构的每个结点由数据域和指针域构成:

typedef struct LNode *PtrToLNode;
struct LNode{
	ElementType Data;
	PtrToLNode Next;
}LNode;
typedef PtrToLNode Position;
typedef PtrToLNode List;

1.求表长

顺序存储结构求表长很容易,直接返回Last+1;而链式结构就比较复杂了,需要将线性表遍历一遍

int Length(List L){
	Position p;
	int cnt = 0;
	p = L;
	while(p){
		p = p->Next;
		cnt++;
	}
	return cnt;
}

 2.查找

按位查找:链式结构的按位查找虽比顺序结构复杂,但也很简单,只需要用一个指针,循环直到达到位置k,返回指针指向元素的值即可。

ElementType FindKth(List L, int k){
	Position p;
	int cnt = 0;
	p = L;
	while(p && cnt<k){
		p = p->Next;
		cnt++;
	}
	if(cnt==k && p){
		return p->Data;
	}else{
		return -1;
	}
}

按值查找:基本方法就是从头到尾遍历一般,找到相同的值就返回结点位置即可

Position Find(List L, ElementType x){
	Position p;
	p = L;
	while(p && p->Data!=x){
		p = p->Next;
	}
	
	if(p){
		return p;
	}else{
		return ERROR;
	}
}

 3.插入

链式结构的插入逻辑上要比顺序结构快得多!其方法如下:创建一个新结点,将新结点的指针指向要插入位置的下一个结点,然后将前一个结点的指针指向新结点,便完成了结点的插入。

bool Insert2(List L, ElementType x, int i){
	Position tmp, pre;
	int cnt = 0;
	
	pre = L;
	while(pre && cnt<i-1){
		pre = pre->Next;
		cnt++;
	}
	if(pre == NULL || cnt != i-1){
		printf("插入位置参数错误\n");
		return false;
	}else{
		tmp = (Position)malloc(sizeof(struct LNode));
		tmp->Data = x;
		tmp->Next = pre->Next;
		pre->Next = tmp;
		return true;
	}
}

4.删除

删除结点方法是:首先找到要删除结点的前一个结点,将该结点的指针指向删除结点的下一个结点,然后将删除结点的空间释放(important!)。

bool Delete(List L, int i){
	Position tmp, pre;
	int cnt = 0;
	
	pre = L;
	while(pre && cnt<i-1){
		pre = pre->Next;
		cnt++;
	}
	if(pre == NULL || cnt != i-1 || pre->Next == NULL){
		printf("删除位置参数错误\n");
		return false;
	}else{
		tmp = pre->Next;
		pre->Next = tmp->Next;
		free(tmp);
		return true;
	}
}

前面的代码中,我们都假设了线性表有一个空的“头结点”,真正的元素都在这个空结点之后。这样做的好处是:防止插入和删除的时候改变了线性表的头结点,这样会带来一些不必要的麻烦!

下面介绍一种构建链式表的方法——头插法

头插法

即在头结点和下一个结点的中间插入新的结点

List CreateListHead(List L, int m[], int n){
	List p;
	int i;
	L = (List)malloc(sizeof(LNode));
	L->Next = NULL;
	for(i=0; i<n; i++){
		p = (List)malloc(sizeof(struct LNode));
		p->Data = m[i];
		p->Next = L->Next;
		L->Next = p;
	}
	return L;
}

还有一种方法是尾插法,即在表尾进行插入。其方法头插法略有不同,在头插法中,我们始终在头节点L后插入,在尾插法中,我们定义一个新指针r,让它始终指向表尾,每次插入的时候就在它后面插入结点,并且更新指针r,让它继续指向表尾。(请自己实现尾插法) 

线性表链式结构代码如下: 

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#define OK 1
#define ERROR NULL
typedef int Status;
typedef int ElementType;
typedef struct LNode *PtrToLNode;
struct LNode{
	ElementType Data;
	PtrToLNode Next;
}LNode;
typedef PtrToLNode Position;
typedef PtrToLNode List;

//创建链式表(头插法)
List CreateListHead(List L, int m[], int n){
	List p;
	int i;
	L = (List)malloc(sizeof(LNode));
	L->Next = NULL;
	for(i=0; i<n; i++){
		p = (List)malloc(sizeof(struct LNode));
		p->Data = m[i];
		p->Next = L->Next;
		L->Next = p;
	}
	return L;
}

//求长度
int Length(List L){
	Position p;
	int cnt = 0;
	p = L;
	while(p){
		p = p->Next;
		cnt++;
	}
	return cnt;
}

//按位查找
ElementType FindKth(List L, int k){
	Position p;
	int cnt = 0;
	p = L;
	while(p && cnt<k){
		p = p->Next;
		cnt++;
	}
	if(cnt==k && p){
		return p->Data;
	}else{
		return -1;
	}
}

//按值查找
Position Find(List L, ElementType x){
	Position p;
	p = L;
	while(p && p->Data!=x){
		p = p->Next;
	}
	
	if(p){
		return p;
	}else{
		return ERROR;
	}
}

//插入1
List Insert1(List L, ElementType x, int i){
	Position tmp, pre;
	tmp = (Position)malloc(sizeof(struct LNode));
	tmp->Data = x;
	if(i == 1){
		tmp->Data = x;
		return tmp;
	}else{
		int cnt = 1;
		pre = L;
		while(pre && cnt<i-1){
			pre = pre->Next;
			cnt++;
		}
		if(pre == NULL || cnt != i-1){
			printf("插入位置参数错误");
			free(tmp);
			return ERROR;
		}else{
			tmp->Next = pre->Next;
			pre->Next = tmp;
			return L;
		}
	}
}

//插入2 有头节点
bool Insert2(List L, ElementType x, int i){
	Position tmp, pre;
	int cnt = 0;
	
	pre = L;
	while(pre && cnt<i-1){
		pre = pre->Next;
		cnt++;
	}
	if(pre == NULL || cnt != i-1){
		printf("插入位置参数错误\n");
		return false;
	}else{
		tmp = (Position)malloc(sizeof(struct LNode));
		tmp->Data = x;
		tmp->Next = pre->Next;
		pre->Next = tmp;
		return true;
	}
}

//删除
bool Delete(List L, int i){
	Position tmp, pre;
	int cnt = 0;
	
	pre = L;
	while(pre && cnt<i-1){
		pre = pre->Next;
		cnt++;
	}
	if(pre == NULL || cnt != i-1 || pre->Next == NULL){
		printf("删除位置参数错误\n");
		return false;
	}else{
		tmp = pre->Next;
		pre->Next = tmp->Next;
		free(tmp);
		return true;
	}
}

//打印线性表
Status Output(List L){
	List p;
	p = L->Next;
	while(p){
		printf("%d ", p->Data);
		p = p->Next;
	}
	printf("\n");
	return OK;
}

int main(){
	List L;
	int n, i, m[1000];
	printf("请输入要存储元素的个数:");
	scanf("%d", &n);
	printf("请输入各元素的值:");
	for(i=0; i<n; i++){
		scanf("%d", &m[i]);
	}
	L = CreateListHead(L, m, n);
	printf("此时线性表各元素如下:\n");
	Output(L);
	int e;
	printf("请输入要获得第几个元素:");
	scanf("%d", &e);
	printf("该元素是:%d\n", FindKth(L,e));
	int p, v;
	printf("请输入要在第几位前插入一个元素:");
	scanf("%d %d", &p, &v);
	Insert2(L, v, p);
	printf("此时各元素如下:\n");
	Output(L);
	int p2;
	printf("请输入要删除哪一个位置的元素:");
	scanf("%d", &p2);
	Delete(L, p2);
	printf("此时各元素如下:\n");
	Output(L);
	return 0;
}

线性表链式结构还可以延生出广义表和多重链表...

线性结构的数据结构除了线性表,还有堆栈,队列...

有空继续完善补充。

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Starshine&~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值