【数据结构】顺序表

目录

1. 顺序表的概念及结构

1.1 线性表

1.2顺序表和数组

1.3 顺序表的结构

2.动态顺序表实现

2.1 顺序表初始化

 2.2 顺序表的销毁

2.3 顺序表的插入

2.3.1 申请空间

2.3.2 尾插

2.3.3 头插

2.3.4 打印

2.3.5 指定位置之前插入

2.4顺序表的删除

2.4.1 尾删

2.4.2 头删

2.4.3 指定位置删除

2.5 顺序表的查找


数据结构是计算机存储、组织数据的方式,简单来说,数据结构就是把数据“管理”起来,以什么方式“管理”起来呢?本篇就介绍了“管理”方式之一,顺序表。

1. 顺序表的概念及结构

1.1 线性表

说顺序表就不得不说线性表

线性表是n个具有相同特性的数据元素的有限序列,是一种广泛使用的数据结构,它在逻辑结构上是线性的,也就是连续的一条线,而在物理结构上不一定是连续的,可以像下图这样理解

7e14ca45fa2a4c1c9ccccfd6f88e0ced.png常见的线性表:顺序表、链表、栈、队...

顺序表是线性表的一种,所以顺序表在逻辑结构上也是连续的,而且在物理结构上也是连续的

1.2顺序表和数组

顺序表底层就是数组,顺序表在数组的基础上对数据进行增加、删除、查找、改变的操作,成为一个“多功能的数组”

1.3 顺序表的结构

顺序表有两种:静态顺序表、动态顺序表

静态顺序表:底层是定长数组

struct SeqList
{
	int arr[100];//申请的空间  是固定值
	int size; //记录顺序表当前有效的数据个数
};

两个结构体成员,首先申请空间,第一个成员假设申请了100个整型空间,这100个空间就固定了,但是不一定这100个空间都存放数据,所以还要有第二个成员size来记录顺序表的有效数据个数

动态顺序表:按需申请

struct SeqList
{
	int* arr;//动态申请空间  大小可调整
	int size;//记录顺序表当前有效的数据个数
	int capacity;//记录空间大小
};

三个结构体成员,第一个成员,动态内存管理相关的函数来按需求灵活的开辟空间,开辟成功接受空间的地址,第二个成员size来记录顺序表的有效数据个数,因为空间不是定长,所以要第三个成员记录开辟了多大空间

(结构体相关知识请看【C语言】结构体详解-CSDN博客)

我们更推荐使用动态顺序表

2.动态顺序表实现

首先我们要新建一个头文件(.h),一个源文件(.c)

fb6d6657e8294f3996e37944abc162dc.png

4ecf2ca1a9204cbd9194777cc4fa20c6.png

为什么要这样呢?比如说我们在读一本书的时候会发现书的前面有目录,在这个顺序表的实现中,头文件就起到了这样一个作用

我们写好之后还要测试我们写的代码是否正确,所以还要一个负责测试的源文件(.c)

5bdc1130f00f41d8b42c940b2fd5f29c.png

00fa07388bba41f5b4316b1b049eb9e6.png

好的,我们先从头文件开始

点开头文件,在这个文件里面我们要定义顺序表结构,这里我们选择用动态顺序表 

//定义顺序表结构
struct SeqList  //动态顺序表结构
{
	int* arr;
	int size;  //有效个数
	int capacity;  //空间大小
};

但是呢,这个顺序表不一定就是存放整型的数组,并且我觉得创建结构体数组变量的时候太麻烦了,所以这里我们做一个修改,给类型取个别的名字,给结构体也取个名字,如下

typedef int Type;
typedef struct SeqList  //动态顺序表结构
{
	Type* arr;
	int size;  //有效个数
	int capacity;  //空间大小
}SL;

假如以后要改数据的类型,只需要将typedef int Type;中的int改为别的就好了,就不要整个代码一处一处改

2.1 顺序表初始化

头文件中,进行函数的声明,首先我们要进行初始化

//顺序表初始化
void SLInit(SL* ps);

SeqList.c中进行函数的实现

#include "SeqList.h"  //包含头文件
//顺序表的初始化
void SLInit(SL* ps) 
{
	ps->arr = NULL; 
	ps->size = 0;
	ps->capacity = 0;
}

代码写好之后,我们要在test.c中进行测试,一定要边写边测试,不然到最后错误一大堆,不知道从哪改起

#include "SeqList.h" //也要包含头文件
void SLtest1()
{
	SL s1;
	SLInit(&s1); //传地址
}
int main()
{
	SLtest1();
	return 0;
}

我们打开调试窗口来看,初始化成功

f3101145065242cc93f4ab7195132791.png

 2.2 顺序表的销毁

因为我们在顺序表中会用到动态内存函数malloc、calloc、realloc,所以要释放空间,但是因为简单所以我们先说这一步,先提前写好代码

头文件SeqList.h中,进行函数声明

//顺序表的销毁
void SLDest(SL* ps);

SeqList.c中进行函数的实现

void SLDest(SL* ps) //顺序表的销毁
{
	if (ps->arr)//如果申请的有空间
	{
		free(ps->arr);//销毁
	}
	ps->arr = NULL;//置空
	ps->size = ps->capacity = 0;//三个参数都还原
}

不要忘了在test.c中测试

void SLtest1()
{
	SL s1;
	SLInit(&s1);//初始化
	//增、删、查、改
	//.....
	SLDest(&s1);//使用完销毁
}

2.3 顺序表的插入

要在顺序表里插入数据有三种情况,从最前面插入,叫头插,从最后面插入,叫尾插,还有指定位置插入

2.3.1 申请空间

在插入数据之前我们要先看空间够不够

头文件SeqList.h中,进行函数声明

//申请空间
void SLFind(SL* ps);

SeqList.c中进行函数的实现

void SLFind(SL* ps)
{
	if (ps->size == ps->capacity) //两者相等时说明空间不够了
	{
		//申请空间
	}
}

那么问题来了,申请多大的空间呢?一次增容增多大呢? 增容通常来说是成倍数增加,一般是原空间的2倍或者3倍 

void SLFind(SL* ps)
{
	if (ps->size == ps->capacity) //两者相等时说明空间不够了
	{
		//申请空间
		ps->arr = (Type*)realloc(ps->arr, ps->capacity * 2 * sizeof(Type));
	}
}

现在问题又来了,这个capacity我们初始化为0了,怎么办呢?那我们再创建一个变量判断一下capacity是否为0,为0就默认给4个空间大小,不为0就直接变成原来的2倍,然后把ps->capacity*2换成newcapacity

void SLFind(SL* ps)
{
	if (ps->size == ps->capacity) //两者相等时说明空间不够了
	{
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;//判断
		ps->arr = (Type*)realloc(ps->arr, newcapacity * sizeof(Type));//申请空间
	}
}

当然,直接在初始化的时候就给一定的空间也是可以的,只要不出错就行。

因为realloc如果空间申请失败返回NILL,严谨起见我们再把代码优化一下

void SLFind(SL* ps)
{
	if (ps->size == ps->capacity) //两者相等时说明空间不够了
	{
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		Type* temp = (Type*)realloc(ps->arr, newcapacity * sizeof(Type));//申请空间
		if (temp == NULL)//空间申请失败
		{
			perror("realloc");
			return;
		}
		else //空间申请成功
		{
			ps->arr = temp;
			ps->capacity = newcapacity;
		}
	}
}

测试的话和后面的尾插一起测试 

2.3.2 尾插

也就是从数组的最后面插入数据

还是一样,在头文件SeqList.h中,进行函数声明

//尾部插入
void SLPushBack(SL* ps, Type x);

第一个参数就是顺序表,第二个参数是要插入的数据,而这个数据的类型Type就是前面我们改名了的类型,在这里其实就是int。

SeqList.c中进行函数的实现

先分析一下,假如下面的情景

8264479c34564da191173b69ad708ace.png

 我们要尾插一个5,就插在下标为size的位置,此时size要+1

abd1e97b5a87479a84913818c3f578a4.png

代码为

void SLPushBack(SL* ps, Type x) //顺序表的尾插
{
	assert(ps);//ps不能为NULL
	SLFind(ps); //调用刚刚写的申请空间的函数
	ps->arr[ps->size] = x; //尾插
	ps->size++;
}

不要忘了在test.c中测试

我们尾插5个数据,结果成功

#include "SeqList.h"
void SLtest1()
{
	SL s1;
	SLInit(&s1);//初始化
	SLPushBack(&s1, 1);
	SLPushBack(&s1, 2);
	SLPushBack(&s1, 3);
	SLPushBack(&s1, 4);
	SLPushBack(&s1, 5);
	SLDest(&s1);//使用完销毁
}

int main()
{
	SLtest1();
	return 0;
}

空间申请也成功

7b25d098ce4645a69cb494329c7775a8.png

小总结:

到目前为止,SeqList.h内代码如下

131c7809643a4f6b94f689356e226a60.png

 SeqList.c内代码如下

27161dd465294e8cb139f9f94025ce9e.png

4678139ed73246ff850dfe693cc941b2.png

 test.c内代码如下 

bd7e60db88634bbb885b1f8160c60b83.png

2.3.3 头插

 在头文件SeqList.h中,进行函数声明

void SLPushhead(SL* ps, Type x);//头部插入

和尾插参数一样,第一个参数就是顺序表,第二个参数是要插入的数据,而这个数据的类型Type就是前面我们改名了的类型,在这里其实就是int。

SeqList.c中进行函数的实现

头插就是在下标为0的位置插入数据

db1fe250797249a08933e277e4a68c6b.png

所以我们要把顺序表中已有的数据整体往后挪动一位

41b6056ff18547c2a019836ad7097168.png

然后再一个一个往后挪

e380e5066c904b68bc49a906334ae324.png

移动完之后size+1 

286da8164f214b58aa54633587d1664c.png

分析完之后我们来看代码如何实现

void SLPushhead(SL* ps, Type x)//头部插入
{
	assert(ps);//ps不能为NULL
	SLFind(ps); //调用申请空间的函数
	for (int i = ps->size; i > 0; i--)//移动
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[0] = x;
	ps->size++;
}

不要忘了在test.c中测试

#include "SeqList.h"
void SLtest1()
{
	SL s1;
	SLInit(&s1);//初始化
	SLPushBack(&s1, 1);//尾插
	SLPushBack(&s1, 2);
	SLPushBack(&s1, 3);
	SLPushBack(&s1, 4);

	SLPushhead(&s1, 5);//头插
	SLPushhead(&s1, 6);

	SLDest(&s1);//使用完销毁
}

int main()
{
	SLtest1();
	return 0;
}

插入成功 

 4a89296f8e654ee588d32c1460ea6f95.png

2.3.4 打印

我们直接把数据打印出来看,就不在调试窗口看了

 在头文件SeqList.h中,进行函数声明

void SLPrint(SL* ps);//打印数据

SeqList.c中进行函数的实现

void SLPrint(SL* ps)//打印数据
{
	for (int i = 0; i < ps->size; i++)
		printf("%d ", ps->arr[i]);
	printf("\n");
}

test.c中测试

#include "SeqList.h"
void SLtest1()
{
	SL s1;
	SLInit(&s1);//初始化
	SLPushBack(&s1, 1);//尾插
	SLPushBack(&s1, 2);
	SLPushBack(&s1, 3);
	SLPushBack(&s1, 4);
	SLPrint(&s1);//打印
	SLPushhead(&s1, 5);//头插
	SLPushhead(&s1, 6);
	SLPrint(&s1);//打印
	SLDest(&s1);//使用完销毁
}

int main()
{
	SLtest1();
	return 0;
}

运行看结果

d1d29245024e488487e2cbe2b49f12de.png

2.3.5 指定位置之前插入

 在头文件SeqList.h中,进行函数声明

void SLInsert(SL* ps, int pos, Type x);//指定位置之前插入

第一个参数是顺序表,第二个参数是插入的下标的位置,pos不能是任意的,要大于等于0,小于等于size,第三个就是插入的数据

SeqList.c中进行函数的实现

5516f45510b64e02a194578e4748bbeb.png

要把pos的位置空出来,pos后面的数据都要往后移

ef4effacbfd341caa903d72f7e1e2d24.png

然后把x放入下标为2的位置,size+1

50bdf03553c547ff83afec38fc070969.png

 代码表示如下

void SLInsert(SL* ps, int pos, Type x)//指定位置之前插入
{
	assert(ps);//ps不能为NULL
	assert(pos >= 0 && pos <= ps->size);//pos有限制
	SLFind(ps);//检查是否需要增容
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	ps->size++;
}

 在test.c中测试

d427d042a4014348835dabf04f0919fe.png

2.4顺序表的删除

数据的删除有三种情况:尾删、头删、指定位置删除

2.4.1 尾删

 在头文件SeqList.h中,进行函数声明

void SLPopBack(SL* ps);//尾删

删除数据就行,所以只要一个参数

SeqList.c中进行函数的实现

删除最后面的那个数据其实只需要size-1就可以了

eb8cda13989f4578ac86864a30a8c488.png

我们对顺序表的控制只在size之前,让要删除的数据不在控制范围内,且这样操作不影响顺序表的增、删、查、改即可 

c24562b2633549b3a2fbddb32fff7923.png

当顺序表中的数据删完了,size=0时,就不能在执行删除操作

所以size也不能为0,代码如下

void SLPopBack(SL* ps)//尾删
{
	assert(ps);//ps不能为NULL
	assert(ps->size);//size不能为0
	ps->size--;
}

test.c中测试一下

void SLtest1()
{
	SL s1;
	SLInit(&s1);//初始化

	SLPushBack(&s1, 1);//尾插
	SLPushBack(&s1, 2);
	SLPushBack(&s1, 3);
	SLPushBack(&s1, 4);
	SLPrint(&s1);//打印

	SLPushhead(&s1, 5);//头插
	SLPushhead(&s1, 6);
	SLPrint(&s1);//打印

	SLPopBack(&s1);//尾删
	SLPrint(&s1);//打印

	SLDest(&s1);//使用完销毁
}

 运行,尾删成功

abd851ce4af4431b88791170977e67f3.png

2.4.2 头删

 在头文件SeqList.h中,进行函数声明

void SLPopHead(SL* ps);//头删

SeqList.c中进行函数的实现

9463595fe33347d3ae74bdcfc6ec72b3.png

我们先把下标为1的数据放到下标为0的地方去

0ae2993c6dec43c9a84f2c1efd5618f5.png

依次向前移动,注意,只能访问到size-1处

377b9aff1a7049c9bd8e20548653c46b.png

 代码为

void SLPopHead(SL* ps)//头删
{
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
}

移动完之后,size-1

9bf04169ed674c30bbf82c51d990fddb.png

最终代码为

void SLPopHead(SL* ps)//头删
{
	assert(ps);//ps不能为NULL
	assert(ps->size);//size不能为0
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

 在test.c中测试

void SLtest1()
{
	SL s1;
	SLInit(&s1);//初始化

	SLPushBack(&s1, 1);//尾插
	SLPushBack(&s1, 2);
	SLPushBack(&s1, 3);
	SLPushBack(&s1, 4);
	SLPrint(&s1);//打印

	SLPushhead(&s1, 5);//头插
	SLPushhead(&s1, 6);
	SLPrint(&s1);//打印

	SLPopBack(&s1);//尾删
	SLPopHead(&s1);//头删
	SLPrint(&s1);//打印

	SLDest(&s1);//使用完销毁
}

运行,头删成功

4a1046631f8046319194a0a7c2d91689.png

2.4.3 指定位置删除

头文件SeqList.h中,进行函数声明

void SLErase(SL* ps, int pos);//指定位置删除

 第一个参数是顺序表,第二个参数是插入的下标的位置,pos不能是任意的,要大于等于0,小于size,这里不能等于size

SeqList.c中进行函数的实现

cd383608de1f4242be16994ae6752fd7.png

将pos后面的数据移到pos处,以此类推

6ffb73e9ad3d4493a690e2d14ddd328d.png

最后再size-1

683384de10bc48a6b05d90a4cfb5767b.png

 代码实现如下

void SLErase(SL* ps, int pos)//指定位置删除
{
	assert(ps);//ps不能为NULL
	assert(ps->size);//size不能为0
	assert(pos >= 0 && pos <= ps->size);//pos有限制
	for (int i = pos; i < ps->size-1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

test.c中测试

void SLtest1()
{
	SL s1;
	SLInit(&s1);//初始化

	SLPushBack(&s1, 1);//尾插
	SLPushBack(&s1, 2);
	SLPushBack(&s1, 3);
	SLPushBack(&s1, 4);
	SLPrint(&s1);//打印

	SLErase(&s1, 0);//指定位置删除
	SLPrint(&s1);//打印

	SLDest(&s1);//使用完销毁
}

测试结果

829c2b02649944df974d1c4d13c95058.png

2.5 顺序表的查找

 在头文件SeqList.h中,进行函数声明

int SLSeek(SL* ps, Type x);//查找

找到了就返回数据的下标,没找到就返回-1,参数一个是顺序表,一个是要查找到数据

SeqList.c中进行函数的实现

int SLSeek(SL* ps, Type x)//查找
{
	assert(ps);//ps不能为NULL
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == x)//找到了
		{
			return i;
		}
	}
	return -1; //没找到
}

test.c中测试

void SLtest2()
{
	SL s1;
	SLInit(&s1);//初始化

	SLPushBack(&s1, 1);//尾插
	SLPushBack(&s1, 2);
	SLPushBack(&s1, 3);
	SLPushBack(&s1, 4);
	SLPrint(&s1);//打印

	int seek = SLSeek(&s1, 4);
	if (seek == -1)
		printf("没找到\n");
	else
		printf("找到了,下标为%d\n", seek);
	SLDest(&s1);//使用完销毁

}


int main()
{
	//SLtest1();
	SLtest2();
	return 0;
}

顺序表基本就是这样了,本次分享就到这里,下次见! 

  • 47
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 18
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值