【数据结构】顺序表详解

一、线性表的概念

线性表是最基本、最简单、也是最常用的一种数据结构。线性表(linear list)是数据结构的一种,一个线性表是n个具有相同特性的数据元素的有限序列。

线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的(注意,这句话只适用大部分线性表,而不是全部。比如,循环链表逻辑层次上也是一种线性表(存储层次上属于链式存储,但是把最后一个数据元素的尾指针指向了首位结点)。

概念:线性表(Linear list)是n个具有相同特性的数据元素的有限序列,线性表是一种在实际中广泛使用的数据结构,常见的线性表有:顺序表、链表、栈、队列、字符串等

线性表在逻辑上是线性结构,呈现出一条线性,他们在逻辑上是挨着的。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

二、顺序表

01 顺序表的概念

顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表中的各个元素、使得线性表中在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系,采用顺序存储结构的线性表通常称为顺序表。顺序表是将表中的结点依次存放在计算机内存中一组地址连续的存储单元中。

概念:顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。顺序表一般可以分为静态顺序表和动态顺序表:

静态顺序表:使用定长数组存储元素

动态顺序表:使用动态开辟的数组存储

顺序表的本质是什么?

顺序表的本质就是数组,但是在数组的基础上它还要求数组是从头开始存,并且是连续存储的,不能跳跃间隔。换言之,顺序表既然叫顺序表,那么数据一定必须是挨着挨着存的。

02 静态顺序表

使用定长数组存储元素:

复制代码
#define N 8
typedef int SLDataType
 
typedef struct SeqList 
{
    SLDataType array[N];  //定长数组
    size_t size;          //有效数据的个数
} SeqList;

静态顺序表的特点:如果满了就不让插入。

静态顺序表的缺点:静态顺序表只适用于确定知道需要存多少数据的场景。这个往往难以斟酌,N 给小了不够用,N 给大了又浪费空间。

03 动态顺序表

使用动态开辟的数组存储元素:

复制代码
typedef int SLDataType;
 
typedef struct SeqList 
{
    SLDataType* array;    //指向动态开辟的数组
    size_t size;          //有效数据的个数
    size_t capacity;      //容量空间的大小
} SeqList;

三、接口实现

01 Q & A

为什么使用动态顺序表?

静态顺序表只适用于确定知道存储多少数据的情况,如果 N 定大了,就会造成空间浪费,如果 N 定小了又不够用。所以基本都是使用动态顺序表,可以根据需要分配空间大小。

什么是接口函数?

接口函数是在数据进行操作时进行调用的函数,通过调用数据结构的接口来帮助你完成一系列操作。

02 基本增删查改接口

cpp 复制代码
/* 接口函数 */
// 初始化
void SeqListInit(SeqList* psl);
// 检查是否需要增容
void SeqListCheckCapacity(SeqList* psl);
// 尾插
void SeqListPushBack(SeqList* psl, SLDataType x);
// 尾删
void SeqListPopBack(SeqList* psl);
// 头插
void SeqListPushFront(SeqList* psl, SLDataType x);
// 头删
void SeqListPopFront(SeqList* psl);
// 查找
int SeqListFind(SeqList* psl, SLDataType x);
// 指定位置插入
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x);
// 指定位置删除
void SeqListErase(SeqList* psl, size_t pos);
// 销毁
void SeqListDestory(SeqList* psl);
// 打印
void SeqListPrint(SeqList* psl);

03 顺序表初始化(SeqListInit)

SeqList.h:

cpp 复制代码
#pragma once
#include <stdio.h>
#include <stdlib.h>

typedef int SLDataType;

/* 动态顺序表 */
typedef struct SeqList
{
	SLDataType* array;	// 指向动态开辟的数组
	size_t size;		// 有效元素的个数 
	size_t capacity;	// 数组实际能存数据的空间容量
}SeqList;

/* 接口函数 */
// 初始化
void SeqListInit(SeqList* psl);

解读:

为了避免同一个头文件被包含多次我们使用 #pragma once

为了方便后续修改数据类型,我们可以使用 typedef 定义一个新的数据类型,这里我们把它取名为 SLDataType

我们为了让定义后的结构体使用更方便,我们同样可以使用 typedef 将其定义为 SeqList(此时 SeqList = struct SeqList)

最后声明我们要实现的接口函数 ------ 顺序表初始化函数并取名为 SeqListInit,参数部分为 SeqList * psl,考虑到形参只是实参的临时拷贝,这里我们会使用址传递。所以,我们使用指针 psl 来接收。

SeqList.c:

cpp 复制代码
#include "SeqList.h"

/* 初始化 */
void SeqListInit(SeqList* psl)
{
	psl->array = NULL;
	psl->size = psl->capacity = 0;
}

解读:

首先引入我们创建的头文件 #include "SeqList.h",我们就可以开始实现顺序表初始化函数了。

首先通过 psl 指向 array,将数组置为空。因为是初始化,所以将有效数据的个数和数组实际能存数据的个数一并置为 0 。

Test.c:

cpp 复制代码
#include "SeqList.h"

void TestSeqList1()
{
	SeqList sl;
	SeqListInit(&sl);
}

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

解读:

测试代码部分,我们不在 main 函数内直接测试而是通过函数来测试,这样的好处是可以方便我们选择性的测试一部分的代码。

为了测试,TestSeqList1 函数中首先创建一个结构体,这里取名为 sl ,随后将 sl 址传递传入 SeqListInit 函数中进行测试。

我们运行代码发现代码可以成功运行,我们下面进入调试来看一下 SeqListInit 函数是否起作用了。

调试代码:

至此,SeqListInit 就写好了。

04 尾插(SeqListPushBack)

所谓的尾插,就是在后面进行插入。

出现三种情况:

① 第一种情况是顺序表压根就没有空间。

② 第二种情况就是我们创建的capacity空间满了。

③ 第三种情况是空间足够,直接插入数据即可。

SeqList.h:

cpp 复制代码
// 尾插
void SeqListPushBack(SeqList* psl, SLDataType x);

解读:SLDataType x 是要添加到顺序表的元素。

SeqList.c:

cpp 复制代码
/* 尾插 */
void SeqListPushBack(SeqList* psl, SLDataType x)
{
	// 首先判断有没有空间,没有空间或空间不足,那么就扩容
	if (psl->size == psl->capacity)
	{
		int new_capacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
		// 这里使用realloc,因为如果原空间为空,就等于malloc。
		// 调整为 new_capacity 个SLDataType大小的空间
		SLDataType* tmp_array = (SLDataType*)realloc(psl->array, new_capacity * sizeof(SLDataType));
		// 检查realloc是否成功
		if (tmp_array == NULL)
		{
			printf("扩容失败\n");
			exit(-1);
		}
		// 更新
		psl->array = tmp_array;
		psl->capacity = new_capacity;
	}
	// 插入数据
	psl->array[psl->size] = x;
	psl->size++;
}

解读:

根据我们刚才分析的三种情况,首先需要判断空间是否足够。思路如下:如果 size == capacity(有效的数据个数等于目前实际能存的最大数据个数),我们进行扩容操作。

如果空间不足,我们先创建一个变量 new_capacity 来存放"新的容量",int new_capacity = psl->capacity 首先把 capacity 的值赋值给这个"新的容量",因为考虑到第一次使用 capacity 大小为0,翻倍会出问题(0 乘以任何数都是 0 ),这里使用三目操作符:int new_capacity = psl->capacity == 0 ? 4 : psl->capacity * 2 , 如果 capacity 为 0 (第一次使用大小是0),就把 4 赋值给它;如果不为 0,就把 capacity 的值翻一倍(x 2)。

这里为什么要x 2(翻一倍)?

随便你,你想一次扩容多少就扩容多少,乘 2 只是一个比较折中的扩容容量方案。

此时,new_capacity 中就存放了扩容的新容量,确定好新的容量后,我们可以对数组动刀子了。为了防止翻车,我们先创建一个临时数组来扩容。这里要进行动态内存开辟,我们选择使用 realloc 而不是 malloc 函数,因为 realloc 可以调整并且如果是第一次用自带 malloc 的效果:

如果没有问题,就可以进行赋值操作了。将 new_capacity 交给 psl->capacity ,将 tmp_array 交给 psl->array

扩容成功后就可以插入了,插入相对来说就很很简单了

Test.c:

cpp 复制代码
#include "SeqList.h"

void TestSeqList1()
{
	SeqList sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
}

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

通过调试来看一下是否成功实现尾插功能了。

05 顺序表打印(SeqListPrint)

顺序表打印就是打印顺序表内数据,我们可以利用循环解决。

SeqList.h:

cpp 复制代码
// 打印
void SeqListPrint(SeqList* psl);

SeqList.c:

cpp 复制代码
/* 打印 */
void SeqListPrint(SeqList* psl)
{
	for (int i = 0; i < psl->size; ++i)
	{
		printf("%d ", psl->array[i]);
	}
	printf("\n");
}

解读:遍历整个顺序表,利用 for 循环就可以轻松解决,把他们都打印出来就可以了。

Test.c:

cpp 复制代码
#include "SeqList.h"

void TestSeqList1()
{
	SeqList sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);

	SeqListPrint(&sl);
}

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

运行结果如下:

06 销毁(SeqListDestory)

因为是动态开辟的,所以如果空间不用我们就需要销毁掉。如果不销毁会存在内存泄漏的风险,所以与之对应的我们写一个销毁的接口函数。

SeqList.h:

cpp 复制代码
// 销毁
void SeqListDestory(SeqList* psl);

SeqList.c:

cpp 复制代码
/* 销毁 */
void SeqListDestory(SeqList* psl)
{
	free(psl->array);
	psl->array = NULL;
	psl->size = psl->capacity = 0;
}

解读:首先使用 free 函数把动态开辟的内存空间释放掉,因为free 之后那块开辟的内存空间已经不在了,为了防止野指针,我们还需要手动把它置为空指针。最后再把 capacitysize 全部设为0,销毁部分就实现好了。

Test.c:

cpp 复制代码
void TestSeqList1() 
{
	SeqList sl;
	SeqListInit(&sl);
 
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
 
	SeqListPrint(&sl);
 
	SeqListDestory(&sl);
}

07 尾删(SeqListPopBack)

如果我们想把倒数第一个数据删除,最简单的思路是把要尾删的地方置为 0,然后再把 size-- 即可实现。这就是尾删:

SeqList.h:

cpp 复制代码
// 尾删
void SeqListPopBack(SeqList* psl);

SeqList.c:

cpp 复制代码
/* 尾删 */
void SeqListPopBack(SeqList* psl)
{
	psl->array[psl->size - 1] = 0;
	psl->size--;
}

解读:首先 psl->array [psl ->size - 1 ] 这里 -1 ,是因为下标的原因,减1才可以对的上。这里把末尾的元素设为了0,此时再 size-- 让实际有效个数减1。

Test.c:

cpp 复制代码
void TestSeqList1()
{
	SeqList sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl);

	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPrint(&sl);

	SeqListDestory(&sl);
}

运行结果如下:

不知道你有没有发现,其实我们只需要 psl -> size-- 就可以了...... 找到尾部的目标然后把他置为0其实没有意义。 size 标识了我们存入了多少个有效数据,比如有5个数据,size-- 后就只有前4个数据有意义了。所以,尾删我们只需要 size-- ,就可以达到目的:

cpp 复制代码
/* 尾删 */
void SeqListPopBack(SeqList* psl)
{
	// psl->array[psl->size - 1] = 0;
	psl->size--;
}

尾删就这么简单?

当然不是,我们还需要考虑一些情况的发生。如果我们一直删,删到没有数据可以删了,如果此时

还继续执行尾删的操作,那么就会发生越界的危险。

解决方案:添加 SeqListPopBack 限制条件

方案一:儒雅随和的方式

cpp 复制代码
/* 尾删 */
void SeqListPopBack(SeqList* psl) 
{
	if (psl->size > 0) 
    {
		// psl->array[psl->size - 1] = 0;
		psl->size--;	
	}
}

方法二:使用断言

使用断言需引入 assert.h

cpp 复制代码
/* 尾删 */
void SeqListPopBack(SeqList* psl) 
{
	//if (psl->size > 0) {
	//	// psl->array[psl->size - 1] = 0;
	//	psl->size--;	
	//}
 
	assert(psl->size > 0);
	psl->size--;
}

08 头插(SeqListPushFront)和 检查扩容(SeqListCheckCapacity)

顺序表要求数据是连续存储的,且必须是从头开始存储。所以,对于顺序表而言如果要实现头插,就需要把数据往后挪动。不能从前往后挪,如果从前往后挪就挪就会把后面的数据覆盖掉。

SeqList.h:

cpp 复制代码
// 头插
void SeqListPushFront(SeqList* psl, SLDataType x);

SeqList.c:

cpp 复制代码
/* 头插 */
void SeqListPushFront(SeqList* psl, SLDataType x)
{
	int end = psl->size - 1;
	while (end >= 0)
	{
		psl->array[end + 1] = psl->array[end];
		end--;
	}
	psl->array[0] = x;
	psl->size++;
}

解读:首先创建一个 end 变量用来指向要移动的数据,因为指向的是数据的下标,所以 size 要减 1 。随后进入 while 循环,如果 end >= 0 说明还没有移动完,就会进入循环。循环体内利用下标,进行向后移动操作,移动结束后再 end-- ,进行下一个数据的向后移动。挪动数据成功后,就可以插入了。此时顺序表第一个位置就被腾出来了,就可以在下标0位置插入欲插入的数据 x 了。最后记得 size++ 。

Test.c:

cpp 复制代码
void TestSeqList2() 
{
	SeqList sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); 

	SeqListPushFront(&sl, -1);
	SeqListPushFront(&sl, -2);
	SeqListPushFront(&sl, -3);
	SeqListPrint(&sl); 

	SeqListDestory(&sl); 
}

运行结果如下:

我们是不是少了点什么?

我们还需要检查是否需要扩容,和尾插一样。

我们之前在完成 SeqListPushBack 尾插的时候就已经写好了,我们不妨把它抽取出来写成一个函数,方便以后多次调用

SeqList.c:

cpp 复制代码
/* 检查是否需要增容 */
void SeqListCheckCapacity(SeqList* psl)
{
	// 首先判断有没有空间,没有空间或空间不足,那么就扩容
	if (psl->size == psl->capacity)
	{
		int new_capacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
		// 这里使用realloc,因为如果原空间为空,就等于malloc。
		// 调整为 new_capacity 个SLDataType大小的空间
		SLDataType* tmp_array = (SLDataType*)realloc(psl->array, new_capacity * sizeof(SLDataType));
		// 检查realloc是否成功
		if (tmp_array == NULL)
		{
			printf("扩容失败\n");
			exit(-1);
		}
		// 更新
		psl->array = tmp_array;
		psl->capacity = new_capacity;
	}
}
cpp 复制代码
/* 尾插 */
void SeqListPushBack(SeqList* psl, SLDataType x)
{
	SeqListCheckCapacity(psl);

	// 插入数据
	psl->array[psl->size] = x;
	psl->size++;
}
cpp 复制代码
/* 头插 */
void SeqListPushFront(SeqList* psl, SLDataType x)
{
	SeqListCheckCapacity(psl);

	int end = psl->size - 1;
	while (end >= 0)
	{
		psl->array[end + 1] = psl->array[end];
		end--;
	}
	psl->array[0] = x;
	psl->size++;
}

09 头删(SeqListPopFront)

如果我们想把第一个数据删除,用尾删的方法直接把 size-- 显然是没有用的了。因为顺序表数据是从头开始存且有顺序的, size-- 无效的也只是最后一个数据。所以要想实现头删,我们不得不把数据先往前挪动,然后再 --size 。

SeqList.h:

cpp 复制代码
// 头删
void SeqListPopFront(SeqList* psl);

SeqList.c:

cpp 复制代码
/* 头删 */
void SeqListPopFront(SeqList* psl)
{
	assert(psl->size > 0);

	int begin = 1;
	while (begin < psl->size)
	{
		psl->array[begin - 1] = psl->array[begin];
		begin++;
	}
	psl->size--;
}

解读:首先断言顺序表有数据,这个前面讲过了这里就不再赘述了。然后开始挪动数据,创建一个 begin 变量并赋值 1,然后进入循环。只要 size 大于 begin 就会进入循环。每次进入循环后将下标 begin 上的数据赋给下标 begin - 1 上的数据,这样就达到了右向左移动的目的。最后 begin++ 移动下一个(如果满足条件的话)。移动完毕后,第一个数据就被第二个数据覆盖掉了,而第二个数据被第三个数据覆盖掉了......最后多出来的一块我们再用 size-- 解决掉,实际容量减1 。此时,就实现了头删。

Test.c:

cpp 复制代码
void TestSeqList2() 
{
	SeqList sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); 

	SeqListPushFront(&sl, -1);
	SeqListPushFront(&sl, -2);
	SeqListPushFront(&sl, -3);
	SeqListPrint(&sl); 

	SeqListPopFront(&sl);
	SeqListPopFront(&sl);
	SeqListPrint(&sl);

	SeqListDestory(&sl); 
}

运行结果如下:

10 查找位置(SeqListFind)

查找顺序表中某值的位置,如果找到了就返回该值的下标,没有找到我们就返回 -1 。

SeqList.h:

cpp 复制代码
// 查找
int SeqListFind(SeqList* psl, SLDataType x);

SeqList.c:

cpp 复制代码
/* 查找 */
int SeqListFind(SeqList* psl, SLDataType x)
{
	for (int i = 0; i < psl->size; ++i)
	{
		if (psl->array[i] == x)
			return i;
	}
	return -1;
}

解读:首先遍历整个顺序表,如果 psl->array [i] ==x 就返回 i ,没找到就返回 -1。这里我们用的方法就是简单粗暴的 ***O(N)***Find 暴力求解,当然你还可以试着用二分查找做,排序一下,写一个二分查找的接口。

Test.c:

cpp 复制代码
void TestSeqList3() {
	SeqList sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); 

	int ret = SeqListFind(&sl, 3);
	if (ret != -1)
		printf("找到了,下标为%d\n", ret);
	else
		printf("没找到!\n");

	SeqListDestory(&sl); 
}

运行结果如下:

11 指定位置插入(SeqListInsert)

顺序表要求数据从第一个开始放并且数据是连续存储的,所以我们就要注意下指定的下标位置pos 的位置了!有些位置并不可以,所以我们需要进行一些检查。

SeqList.h:

cpp 复制代码
// 指定位置插入
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x);

SeqList.c:

cpp 复制代码
/* 指定位置插入 */
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x)
{
	assert(pos >= 0 && pos <= psl->size);
	SeqListCheckCapacity(psl);

	int end = psl->size - 1;
	while (end >= pos)
	{
		psl->array[end + 1] = psl->array[end];
		end--;
	}
	psl->array[pos] = x;
	psl->size++;
}

解读:首先添加 pos 位置的限定条件,可以根据自己的喜好选择是儒雅随和的处理方式,还是暴力处理方式。这里我的选择是简单粗暴地使用断言解决,限定 pos >= 0 并且 pos <= psl->size 从而保证 pos 合法。然后,因为是插入所以免不了要检查增容,直接调用之前写好的检查增容的函数即可。检查完后就可以开始移动了,和头插差不多,我们创建一个变量 end 记录最后一个的下标(psl->size-1),并通过它来指向要移动的数据。最后进入 while 循环,以 end >= pos 作为条件。移动完后,x 的位置就腾出来了,再把 x 插入进去,最后再 size++,就完成了。

Test.c:

cpp 复制代码
void TestSeqList3() {
	SeqList sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); 

	SeqListInsert(&sl, 2, 30);
	SeqListPrint(&sl); 

	int pos = SeqListFind(&sl, 4);
	if (pos != -1) 
	{
		SeqListInsert(&sl, pos, 40);
	}
	SeqListPrint(&sl); 

	SeqListInsert(&sl, 0, -10);
	SeqListPrint(&sl); 

	SeqListInsert(&sl, 8, 80);
	SeqListPrint(&sl); 


	SeqListDestory(&sl); 
}

运行结果如下:

头插函数和尾插函数的修改:

cpp 复制代码
/* 头插 */
void SeqListPushFront(SeqList* psl, SLDataType x)
{
	//SeqListCheckCapacity(psl);

	//int end = psl->size - 1;
	//while (end >= 0)
	//{
	//	psl->array[end + 1] = psl->array[end];
	//	end--;
	//}
	//psl->array[0] = x;
	//psl->size++;

	SeqListInsert(psl, 0, x);
}
cpp 复制代码
/* 尾插 */
void SeqListPushBack(SeqList* psl, SLDataType x)
{
//	SeqListCheckCapacity(psl);
//
//	// 插入数据
//	psl->array[psl->size] = x;
//	psl->size++;

	SeqListInsert(psl, psl->size, x);
}

12 指定位置删除(SeqListErase)

删除指定位置的数据,我们仍然要限制pos 的位置。限制条件部分和 SeqListInsert 不同的是,因为 psl->size 这个位置没有效数据,所以删除的位置不能是 psl->size

SeqList.h:

cpp 复制代码
// 指定位置删除
void SeqListErase(SeqList* psl, size_t pos);

SeqList.c:

cpp 复制代码
/* 指定位置删除 */
void SeqListErase(SeqList* psl, int pos)
{
	assert(pos >= 0 && pos < psl->size);

	int begin = pos + 1;
	while (begin < psl->size)
	{
		psl->array[begin - 1] = psl->array[begin];
		begin++;
	}
	psl->size--;
}

Test.c:

cpp 复制代码
void TestSeqList4() 
{
	SeqList sl;
	SeqListInit(&sl);
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); 

	SeqListPushFront(&sl, 1);
	SeqListPushFront(&sl, 2);
	SeqListPushFront(&sl, 3);
	SeqListPushFront(&sl, 4);
	SeqListPushFront(&sl, 5);
	SeqListPrint(&sl); 

	SeqListErase(&sl, 2);
	SeqListPrint(&sl); 

	SeqListDestory(&sl); 
}

运行结果如下:

头删尾删都可以复用 SeqListEarse 了

cpp 复制代码
/* 尾删 */
void SeqListPopBack(SeqList* psl)
{
	//if (psl->size > 0)
	//{
	//	// psl->array[psl->size - 1] = 0;
	//	psl->size--;
	//}

	//assert(psl->size > 0);
	//psl->size--;

	SeqListErase(psl, psl->size - 1);
}
cpp 复制代码
/* 头删 */
void SeqListPopFront(SeqList* psl)
{
	//assert(psl->size > 0);

	//int begin = 1;
	//while (begin < psl->size)
	//{
	//	psl->array[begin - 1] = psl->array[begin];
	//	begin++;
	//}
	//psl->size--;

	SeqListErase(psl, 0);
}

四、完整代码

SeqList.h:头文件以及函数申明

cpp 复制代码
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int SLDataType;

/* 动态顺序表 */
typedef struct SeqList
{
	SLDataType* array;	// 指向动态开辟的数组
	int size;		// 有效元素的个数 
	int capacity;	// 数组实际能存数据的空间容量
}SeqList;

/* 接口函数 */
// 初始化
void SeqListInit(SeqList* psl);
// 检查是否需要增容
void SeqListCheckCapacity(SeqList* psl);
// 尾插
void SeqListPushBack(SeqList* psl, SLDataType x);
// 尾删
void SeqListPopBack(SeqList* psl);
// 头插
void SeqListPushFront(SeqList* psl, SLDataType x);
// 头删
void SeqListPopFront(SeqList* psl);
// 查找
int SeqListFind(SeqList* psl, SLDataType x);
// 指定位置插入
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x);
// 指定位置删除
void SeqListErase(SeqList* psl, size_t pos);
// 销毁
void SeqListDestory(SeqList* psl);
// 打印
void SeqListPrint(SeqList* psl);

SeqList.c:接口实现

cpp 复制代码
#include "SeqList.h"

/* 初始化 */
void SeqListInit(SeqList* psl)
{
	psl->array = NULL;
	psl->size = psl->capacity = 0;
}

/* 检查是否需要增容 */
void SeqListCheckCapacity(SeqList* psl)
{
	// 首先判断有没有空间,没有空间或空间不足,那么就扩容
	if (psl->size == psl->capacity)
	{
		int new_capacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
		// 这里使用realloc,因为如果原空间为空,就等于malloc。
		// 调整为 new_capacity 个SLDataType大小的空间
		SLDataType* tmp_array = (SLDataType*)realloc(psl->array, new_capacity * sizeof(SLDataType));
		// 检查realloc是否成功
		if (tmp_array == NULL)
		{
			printf("扩容失败\n");
			exit(-1);
		}
		// 更新
		psl->array = tmp_array;
		psl->capacity = new_capacity;
	}
}

/* 尾插 */
void SeqListPushBack(SeqList* psl, SLDataType x)
{
//	SeqListCheckCapacity(psl);
//
//	// 插入数据
//	psl->array[psl->size] = x;
//	psl->size++;

	SeqListInsert(psl, psl->size, x);
}

/* 尾删 */
void SeqListPopBack(SeqList* psl)
{
	//if (psl->size > 0)
	//{
	//	// psl->array[psl->size - 1] = 0;
	//	psl->size--;
	//}

	//assert(psl->size > 0);
	//psl->size--;

	SeqListErase(psl, psl->size - 1);
}

/* 头插 */
void SeqListPushFront(SeqList* psl, SLDataType x)
{
	//SeqListCheckCapacity(psl);

	//int end = psl->size - 1;
	//while (end >= 0)
	//{
	//	psl->array[end + 1] = psl->array[end];
	//	end--;
	//}
	//psl->array[0] = x;
	//psl->size++;

	SeqListInsert(psl, 0, x);
}

/* 头删 */
void SeqListPopFront(SeqList* psl)
{
	//assert(psl->size > 0);

	//int begin = 1;
	//while (begin < psl->size)
	//{
	//	psl->array[begin - 1] = psl->array[begin];
	//	begin++;
	//}
	//psl->size--;

	SeqListErase(psl, 0);
}

/* 打印 */
void SeqListPrint(SeqList* psl)
{
	for (int i = 0; i < psl->size; ++i)
	{
		printf("%d ", psl->array[i]);
	}
	printf("\n");
}

/* 销毁 */
void SeqListDestory(SeqList* psl)
{
	free(psl->array);
	psl->array = NULL;
	psl->size = psl->capacity = 0;
}

/* 查找 */
int SeqListFind(SeqList* psl, SLDataType x)
{
	for (int i = 0; i < psl->size; ++i)
	{
		if (psl->array[i] == x)
			return i;
	}
	return -1;
}

/* 指定位置插入 */
void SeqListInsert(SeqList* psl, int pos, SLDataType x)
{
	assert(pos >= 0 && pos <= psl->size);
	SeqListCheckCapacity(psl);

	int end = psl->size - 1;
	while (end >= pos)
	{
		psl->array[end + 1] = psl->array[end];
		end--;
	}
	psl->array[pos] = x;
	psl->size++;
}

/* 指定位置删除 */
void SeqListErase(SeqList* psl, int pos)
{
	assert(pos >= 0 && pos < psl->size);

	int begin = pos + 1;
	while (begin < psl->size)
	{
		psl->array[begin - 1] = psl->array[begin];
		begin++;
	}
	psl->size--;
}

Test.c:测试用例

cpp 复制代码
#include "SeqList.h"

void TestSeqList1()
{
	SeqList sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl);

	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPrint(&sl);

	SeqListDestory(&sl);
}

void TestSeqList2() 
{
	SeqList sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); 

	SeqListPushFront(&sl, -1);
	SeqListPushFront(&sl, -2);
	SeqListPushFront(&sl, -3);
	SeqListPrint(&sl); 

	SeqListPopFront(&sl);
	SeqListPopFront(&sl);
	SeqListPrint(&sl);

	SeqListDestory(&sl); 
}

void TestSeqList3() {
	SeqList sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); 

	SeqListInsert(&sl, 2, 30);
	SeqListPrint(&sl); 

	int pos = SeqListFind(&sl, 4);
	if (pos != -1) 
	{
		SeqListInsert(&sl, pos, 40);
	}
	SeqListPrint(&sl); 

	SeqListInsert(&sl, 0, -10);
	SeqListPrint(&sl); 

	SeqListInsert(&sl, 8, 80);
	SeqListPrint(&sl); 


	SeqListDestory(&sl); 
}

void TestSeqList4() 
{
	SeqList sl;
	SeqListInit(&sl);
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); 

	SeqListPushFront(&sl, 1);
	SeqListPushFront(&sl, 2);
	SeqListPushFront(&sl, 3);
	SeqListPushFront(&sl, 4);
	SeqListPushFront(&sl, 5);
	SeqListPrint(&sl); 

	SeqListErase(&sl, 2);
	SeqListPrint(&sl); 

	SeqListDestory(&sl); 
}

int main()
{
	//TestSeqList1();
	//TestSeqList2();
	//TestSeqList3();
	TestSeqList4();
	return 0;
}
相关推荐
小xin过拟合3 小时前
day20 二叉树part7
开发语言·数据结构·c++·笔记·算法
刘 大 望3 小时前
网络编程--TCP/UDP Socket套接字
java·运维·服务器·网络·数据结构·java-ee·intellij-idea
寻星探路4 小时前
数据结构青铜到王者第三话---ArrayList与顺序表(1)
数据结构
啟明起鸣4 小时前
【数据结构】B 树——高度近似可”独木成林“的榕树——详细解说与其 C 代码实现
c语言·开发语言·数据结构
这周也會开心4 小时前
数据结构-ArrayList
数据结构
nonono4 小时前
数据结构——线性表(链表,力扣中等篇,技巧型)
数据结构·leetcode·链表
hrrrrb4 小时前
【数据结构】栈和队列——队列
数据结构
XMZH030424 小时前
数据结构:单向链表的逆置;双向循环链表;栈,输出栈,销毁栈;顺序表和链表的区别和优缺点;0825
数据结构·链表·
KarrySmile6 小时前
Day8--HOT100--160. 相交链表,206. 反转链表,234. 回文链表,876. 链表的中间结点
数据结构·算法·链表·双指针·快慢指针·hot100·灵艾山茶府