顺序表及常见接口实现

顺序表

概念及结构:
顺序表是用一段 物理地址连续 的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般可以分为:
1. 静态顺序表:使用定长数组存储元素
2. 动态顺序表:使用动态开辟的数组存储。
静态顺序表依赖于C语言内置的数组类型,此处不做过多介绍。
下面主要介绍动态顺序表的实现。动态顺序表能够通过动态内存分配自由的开辟所需的空间,因此相较而言有更大的灵活性。
一.顺序表的实现
typedef int SLDateType;
typedef struct SeqList
{
	SLDateType* a;
	size_t size;
	size_t capacity; // unsigned int
}SeqList;

用结构体类型来实现顺序表及常见操作,其中:

变量a:指向所需类型的指针,可通过动态内存开辟来分配空间。

变量size:已经存储有效数据的个数。

变量capacity:已经通过动态开辟获取的空间大小,若空间不够,则可继续开辟空间。

由于有效数据size和空间大小capacity均为非负数,此处用了size_t类型,当然int类型也可以。用size_t类型使,后方在循环条件的判断时,则需要注意代码的实现逻辑不能让size和capacity自减为-1,对于无符号类型而言,-1的补码是一个极大的数字,会导致-1>0的逻辑错误,需要注意。

为了方便对数据进行管理,我们需要实现常见接口来满足所需功能。

二.常见接口:
1.初始化

在定义一个此结构体类型的变量后,要想对其进行后续的操作,首先我们要对结构体进行初始化。

void SeqListInit(SeqList* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = 0;
}

如上,将指针初始化为NULL,有效数据个数和容量初始值均为0。其中assert为断言函数,为防止传入函数的指针为空。为了防止空指针传入,一般需要在函数开头进行断言。

2.内存释放

在已经不需要此顺序表的时候,最好能够将内存及时释放,防止其申请的空间被无效占用。由于内存开辟为动态分配,系统不会自动释放其内存,所以需要实现内存释放功能。

void SeqListDestroy(SeqList* ps)
{
	assert(ps);
	ps->size = 0;
	ps->capacity = 0;
	free(ps->a);
	ps->a = NULL;
}

如上,将动态内存开辟的空间释放,并将各变量置为初始值。

3.数据打印

将其内的数据内容进行打印,也便于后续函数的测试。

void SeqListPrint(SeqList* ps)
{
	assert(ps);
	printf("All Data:  ");
	for (size_t i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

利用for循环控制打印,打印次数为有效数据ps->size的大小。由于此处前面将SLDateType定义为int类型,此处存放的数据也为int,用%d控制打印。

// 对数据的管理:增删查改

4.首插

即向数据表首部添加数据,由于是需要添加数据,所以需要对顺序表容量进行检查,若容量不够,则应该先进行扩容,再将数据进行添加。

由于往后的添加操作均需检查容量,故实现此函数为:

void Check_capacity(SeqList* ps)
{
	assert(ps);
	if (ps->size == ps->capacity)
	{
		size_t newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		SLDateType* tmp = (SLDateType*)realloc(ps->a, sizeof(SLDateType) * newCapacity);
		if (tmp == NULL)
		{
			printf("realloc error\n");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newCapacity;
	}
}

当有效数据个数==空间容量,则表示空间已满,需要进行扩容。此处扩容实现为扩大其空间为原来的2倍,但由于初始时capacity为0,所以对此种情况单独考虑,给其4个大小的初始空间。如果开辟失败,则返回错误信息,此时exit将程序终止。若开辟成功,则将容量大小capacity更新为新的大小。 

然后实现首插功能:

void SeqListPushFront(SeqList* ps, SLDateType x)
{
	assert(ps);

	Check_capacity(ps);

	size_t end = ps->size;
	while (end > 0)
	{
		ps->a[end] = ps->a[end - 1];
		end--;
	}
	ps->a[0] = x;
	ps->size++;
}

 

检查容量之后,由于需要将数据插入第一个元素,则需要先将所有元素后移一个空间,再将值放在下标为0处的空间。此处用循环实现将所有元素后移。插入完毕后ps->size++,记录有效数据变化后的个数。

5.首删

void SeqListPopFront(SeqList* ps)
{
	assert(ps);
	if (ps->size > 0)
	{
		size_t end = 1;
		while (end < ps->size)
		{
			ps->a[end - 1] = ps->a[end];
			end++;
		}
		ps->size--;
	}
	else
		printf("No valid data\n");

}

与上面类似,此处只需将原本首元素后面的所有元素向前移动一个空间,将首元素覆盖,然后将ps->size--,即有效数据个数减少1。

6.尾插

void SeqListPushBack(SeqList* ps, SLDateType x)
{
	assert(ps);

	Check_capacity(ps);
	ps->a[ps->size] = x;
	ps->size++;
	SeqListInsert(ps, ps->size, x);
}

尾插的实现比较简单,原本顺序表一共有ps->size个有效数据,向其末尾插入元素,则只需在下标尾ps->size处存放此元素即可,然后使size++。

7.尾删

void SeqListPopBack(SeqList* ps)
{
	assert(ps);
	ps->a[ps->size - 1] = 0;
	if (ps->size > 0)
	{
		ps->size--;
	}
	else
		printf("No valid data\n");

}

只需将ps->size--即可,有效数据个数减少1,则代表此元素被舍弃,也即被删除。为严谨,也可将需删除数据的空间先初始化为0,再进行删除。

8.指定位置插入

void SeqListInsert(SeqList* ps, size_t pos, SLDateType x)
{
	assert(ps);

	Check_capacity(ps);

	if (pos > ps->size)
	{
		printf("error to add\n");
		return;
	}
	else
	{
		size_t end = ps->size;
		while (end > pos)
		{
			ps->a[end] = ps->a[end - 1];
			end--;
		}
		ps->a[pos] = x;
		ps->size++;
	}
}

和首插实现类似,只需将指定位置下标往后的所有元素后移,再将目标元素插入此处即可。

9.指定位置删除

void SeqListErase(SeqList* ps, size_t pos)
{
	assert(ps);

	if (pos >= ps->size)      
	{
		printf("error to pop\n");
	}
	else
	{
		size_t begin = pos + 1;
		while (begin < ps->size)
		{
			ps->a[begin-1] = ps->a[begin];
			begin++;
		}
		ps->size--;
	}
}

同样,和首删类似,只需将指定下标后面的元素向前移动一个空间,并覆盖此处元素,即完成删除,最后size--代表有效数据减少1。

实现完指定位置插入和删除后,我们可以将首插,首删及尾插,尾删直接依靠这两个函数实现。即下标为0地方的元素插入和删除,下标为size地方的元素插入和下标为size-1地方元素的删除。

重新实现如下:

void SeqListPushFront(SeqList* ps, SLDateType x)//首插
{
	SeqListInsert(ps, 0, x);
}
void SeqListPopFront(SeqList* ps)//首删
{
	SeqListErase(ps, 0);
}
void SeqListPushBack(SeqList* ps, SLDateType x)//尾插
{
	SeqListInsert(ps, ps->size, x);
}
void SeqListPopBack(SeqList* ps)//尾删
{
	SeqListErase(ps, ps->size-1);
}

10.元素查找-返回第一个出现该元素位置的下标,若没有找到,返回-1。

int SeqListFind(SeqList* ps, SLDateType x)
{
	assert(ps);
	for (size_t i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == x)
			return i;
	}
	return -1;
}

遍历数组即可,若找到直接返回该处下标,否则跳出循环返回-1。

实现上述功能后,我们可以将其进行拼接组装,并用实现菜单函数提示相关信息,让我们能通过输入选择对应操作。

 简单实现后效果如上,此顺序表可自由存放数据,并通过接口实现基础功能。依靠此顺序表的思路,我们可以也实现通讯录。

顺序表内容介绍至此,以上介绍了基本的实现思路和代码,当然光看上述代码肯定是难以看懂的,不妨顺着思路动手操作。

最后,如有错误,欢迎指正。

El Phy Congroo

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值