【C数据结构】解决动态顺序表以及常见问题

一、顺序表的本质

  顺序表本质上就是数组,但在数组上加了两点,一是分为静态动态,二是要求数据从头开始存并且是连续存储,不能跳跃间隔。

  由于动态顺序表相较于静态顺序表,除了在空间开辟上更有优势,其他大体一样,所以在这里我们讨论动态的顺序表。


二、动态顺序表的实现


  动态顺序表与直接用数组实现的静态顺序表不同在于,动态顺序表用的是指针,动态顺序表通过指针变量储存的地址指向一个经过动态内存开辟的空间。对于这个空间,我们可以在它满的时候能给它扩大空间(也就是数组中的数存满了),所以为了知道这个空间什么时候存满,我们就需要知道它存储的有效数据数量(size),和当前能存的最大空间容量(capacity)

所以我们可以定义一个结构体变量,并且将它放在一个名为SqList.h的头文件中,这个头文件并且包括我们需要的库。

//SqList.h
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int DataType;//当用其他数据类型,只需要把int改了就行
typedef struct SeqList
{
	int size; //有效数据数量
	int capacity;//最大空间容量
	DataType* a;//指向数组空间的指针
}SL;


1、测试页与接口调用页

首先我们会创建一个名为Test.c的源文件,这个文件用于测试我们待实现的接口函数。

//Test.c
//测试页
#include"SqList.h"
void Test1()
{
	//生成结构体变量,并且传结构体地址进行初始化。
	SL ps;
	SeqListInit(&ps);
	
	//顺序表尾插1 2 3 2
	SeqListPushBack(&ps, 1);
	SeqListPushBack(&ps, 2);
	SeqListPushBack(&ps, 3);
	SeqListPushBack(&ps, 2);
	
	//1号位插入0
	SeqListInsert(&ps,1,0);
	//头插1
	SeqListPushFront(&ps, 1);
	//打印
	SeqListPrintf(&ps);
	//删除0位置的数
	SeqListErase(&ps, 0);
	SeqListPrintf(&ps);
	//寻找数字1,并返回下标位置
	int pos=SeqListFind(&ps, 1);
	while (pos != -1) 
	{
		//删除所有的1
		SeqListErase(&ps, pos);
		pos = SeqListFind(&ps, 1);
	}
	SeqListPrintf(&ps);
	
	//销毁顺序表
	SeqListDestroy(&ps);
}
int main()
{
	Test1();
	return 0;
}


SqList.h页面,包含所有的函数声明与其它声明。

//SqList.h
#include<stdio.h>
#include<stdlib.h> //malloc、realloc、free需要的
#include<assert.h>//assert()需要的
typedef int DataType;//当用其他数据类型,只需要把int改了就行
typedef struct SeqList 
{
	int size; //有效数据数量
	int capacity;//最大空间容量
	DataType* a;//指向数组空间的指针
}SL;

//接口
//初始化
void SeqListInit(SL* ps);

//检查最大容量
void SeqListCheckCapacity(SL* ps);

//尾插
void SeqListPushBack(SL* ps,DataType x);

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

//头插
void SeqListPushFront(SL* ps,DataType x);

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

//遍历打印
void SeqListPrintf(SL* ps);

//查找数据,返回位置
int SeqListFind(SL* ps, DataType x);

//指定位置指定数据插入
void SeqListInsert(SL* ps, size_t pos, DataType x);

//指定位置删除数据
void SeqListErase(SL* ps, size_t pos);

//删除顺序表
void SeqListDestroy(SL* ps);

//修改数据
void SLModify(SL* psl, size_t pos, DataType x);

而接下来的函数实现则需要放入SqList.c中,并且包括SqList.h

//SqList.c
#include"SqList.h"
...


2、初始化顺序表

接下来的函数实现我们将写在源文件为SqList.c的源文件中

//SqList.c
//初始化顺序表
void SeqListInit(SL* ps) 
{
	ps->a = NULL;
	ps->capacity = ps->size = 0;
}

  为什么要初始化?
  首先a是一个未初始化的指针,在使用未初始化的指针时,VS编译器是会出现读取错误的。
  而size是因为需要从0累计算的,容量capacity目前也没有,这些方便处理后期使用的数据。

3、扩充储存容量

首先回顾一下malloc和realloc,malloc是简单的分配内存空间,realloc是扩充空间返回新的地址,而realloc当指针为空的时候,作用是和malloc一样的,所以。

//SqList.c
//检查容量
void SeqListCheckCapacity(SL* ps)
{
	if (ps->size == ps->capacity)//当有效数据数量等于最大容量
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//新的容量大小就将扩大两倍,为0时容量就从4开始
		DataType* tmp = (DataType*)realloc(ps->a, sizeof(DataType) * newcapacity);// realloc需要用新的指针接收返回地址
		if (tmp == NULL)
		{
			printf("realloc fail");
			exit(-1);
		}
		//记得再返回给原来的指针和容量
		ps->a = tmp;
		ps->capacity = newcapacity;
	}
}

  • 在容量为0的时候,也能扩充。
  • 这个操作可以让我们在每次插入新的数据时,都可以直接调用它检查容量是否满了。
  • 当然每次容量扩充也不一定是2倍,开始分配的容量也不一定是4,这里是可以自由写的。

4、插入数据

//SqList.c
//指定位置插入
void SeqListInsert(SL* ps, size_t pos, DataType x)
{
	assert(ps);
	assert(ps->size >= pos);

	SeqListCheckCapacity(ps);
	
	// 方法1
	/*int end = psl->size - 1;
	while (end >= (int)pos)
	{
		psl->a[end + 1] = psl->a[end];
		--end;
	}*/
	
	// 方法2
	int end = ps->size;
	
	for (; end > pos; end--)
	{
		ps->a[end] = ps->a[end - 1];
	}
	ps->a[pos] = x;
	ps->size++;
}

值得注意的是: 在插入函数形参中,传的是 size_t 类型的 pos,如果使用的是方法1,需要将 pos强制转换为int类型,不然在pos为0的时候,–end会出现 -1 >= 0的情况,在这里-1会由int类型提升至size_t类型(unsigned int 类型),从而出现死循环。

而使用方法2,不需要考虑上述情况,相对简单。

再来聊聊 assert()----断言,当括号内为False时将会报错中止程序,当括号内为True时继续程序运行。

5、删除数据

//SqList.c
//指定位置删除
void SeqListErase(SL* ps, size_t pos)
{
	assert(ps);
	assert(ps->size > pos && ps->size > 0);

	int i = pos;
	for (i; i < ps->size - 1; i++)
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;

}

当然你也可以通过更改实参的数据pos-1来实现pos=1,删除a[0]的数据。

为什么在删除数据后,不需要缩容空间,以达到节省空间的效果?
如果节省空间,那么在下次添加数据的时候,又需要增容,这会影响效率,并且在现在这个环境下,我们对空间的要求不需要太苛刻,没必要以时间换空间。现在更注重的是效率,所以通过以空间换时间的方式更好。

6、查找数据

//SqList.c
//查找指定数据位置
int SeqListFind(SL* ps, DataType x)
{
	assert(ps && ps->size > 0);

	int i = 0;
	for (i = 0; i < ps->capacity; i++)
	{
		if (ps->a[i] == x)
		{
			return i;
		}
	}
	printf("没有找到\n");
	return -1;
}

没有数据返回-1。
配合删除数据,如果有多个数据相同,则可以在测试板块通过以下代码,来实现删除相同数据。

//Test.c
	int pos=SeqListFind(&ps, 1);
	while (pos != -1) 
	{
		SeqListErase(&ps, pos);
		pos = SeqListFind(&ps, 1);
	}

7、销毁顺序表

//SqList.c
//销毁顺序表
void SeqListDestroy(SL* ps)
{
	assert(ps);

	free(ps->a);
	ps->a = NULL;
	ps->capacity = ps->size = 0;
}

释放指针a指向空间,并将指针a置空,其他数据也赋为这时候的实际值。

三、总结

这是一个简单的动态顺序表,只有顺序表的原理以及具体操作代码的实现,这一块个人感觉简单的理解完后,最重要的还是多敲代码熟练一下。

以上后续随着我的知识面的提升,还会继续改进。
希望对你有帮助。

详细代码:
动态顺序表详细代码

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值