初阶数据结构--顺序表

本文详细介绍了动态顺序表的概念,将其与静态线性表进行了对比,并深入探讨了动态线性表的主要缺陷——动态增容的性能损耗和头部插入时的时间复杂度。接着,通过C语言展示了动态顺序表的创建、初始化、销毁、扩容以及基本操作如插入、删除、查找的实现。文章还提到了realloc函数的工作原理,并指出在初始容量为0时扩容的注意事项。最后,讨论了如何优化头插和头删操作,以及顺序表的接口函数设计。
摘要由CSDN通过智能技术生成

1.顺序表的本质

顺序表本质上来说呢,就是数组,只不过它的空间可以动态增长,并且要求它里面的存储数据的方式必是从左往右连续的。它归属于线性表,因为它的逻辑结构是线性的,同时物理结构也是线性的。(这里提醒一点:线性表只是表示逻辑结构线性的,并不要求其物理结构连续,例如链表的物理结构就不是线性的)

线性表又分为静态线性表和动态顺序表:

(1) 静态顺序表:使用定长数组存储。
(2) 动态顺序表:使用动态开辟的数组存储。

那动态线性表那么好,又为啥开辟了那么多新的数据结构呢:答案是动态线性表肯定是有缺陷的。

2.动态线性表的缺陷:1.动态增容有性能损耗2.当需要头部插入一个数据时,需要将后面的数据一一往后挪,这将是一个时间复杂度为o(n)的操作,你可能认为才o(n),但对于积土成山,这终究不是什么好事,肯定会对效率产生一定影响。

3.动态顺序表的创建以及初始化:(1)对于创建首先要说明的是这个a指针是指向数组,为什么用指针,就是为了方便后续开辟更多空间,如果是数组那么空间固定,就是静态顺序表了。然后对于把int类型定义别名为SeqDataType,是为了后续便于改变顺序表中数据类型(就是只要再开头改一下即可)

typedef int SeqDataType;
typedef struct SeqList
{
	SeqDataType* a;//指向动态开辟的数组
	int size;//有效数据个数
	int capacity;//容量
}SeqList;

(2)初始化:这里我们讲一下assert函数,它是断言函数,assert(x)这个x语句放的是一个判断,如果判断出错,程序会停止,并告诉你在哪出错,这样方便于我们来找出错误,而不是从第一行傻傻的看代码。。

void SeqListInit(SeqList* pq)
{
	//assert(pq != NULL);这一句与下一句作用相同
	assert(pq);
	pq->a = NULL;
	pq->size = pq->capacity = 0;
}

(3)销毁:当然我们会初始化了,肯定还得会销毁。

这里我们强调一点,就是free函数,只是把指针指向的空间释放,并不是把指针指向空,所以我们之后还得让指针指向空。

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

(4)扩容:所谓动态,就是可以随着我们插入数据,改变我们数据结构的空间大小,那必然会牵扯到扩容。

void SeqCheckCapacity(SeqList* pq)
{//满了,需要增容
	assert(pq);
	if (pq->size == pq->capacity)
	{
		int newcapacity = pq->capacity * 2;
		SeqDataType* newA = realloc(pq->a, sizeof(SeqDataType)*newcapacity);
		if (newA == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		pq->a = newA;
		pq->capacity = newcapacity;
	}
}
void SeqCheckCapacity(SeqList* pq)
{//满了,需要增容
	assert(pq);
	if (pq->size == pq->capacity)
	{
		int newcapacity = pq->capacity == 0 ? 4 : pq->capacity * 2;
		SeqDataType* newA = realloc(pq->a, sizeof(SeqDataType)*newcapacity);
		if (newA == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		pq->a = newA;
		pq->capacity = newcapacity;
	}
}

关于以上两个代码,聪明的你们,肯定一眼就看出不同,就是如果初始容量为0,那么0*2还为0,这样不管乘多少次,它都达不到扩容的作用。这也是很多人忽视的一点。

关于realloc函数,我们讲一点:realloc函数会探测后续空间够不够,(1)如果够,那么直接在后面开辟空间(2)如果不够,就会重新找一块大的空间,将原来的数据拷贝,粘贴到新空间,并将原空间释放。

(5)打印顺序表

这个简单,就是将表遍历一遍即可。

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

(6)顺序表的接口函数:

接口函数有很多,我们先声明一下

void SeqListPushBack(SeqList* pq, SeqDataType x);//尾插
void SeqListPushFront(SeqList* pq, SeqDataType x);//头插
void SeqListPopBack(SeqList* pq);//尾删
void SeqListPopFront(SeqList* pq);//头删
int SeqListFind(SeqList* pq, SeqDataType x);//查找
void SeqListInsert(SeqList* pq,int pos, SeqDataType x);//插入
void SeqListErase(SeqList* pq, int pos);//删除
void SeqListModify(SeqList* pq, int pos, SeqDataType x);//更改数据

看到这么多函数是不是脑子又不够用啦,但细心的小伙伴又发现了,你看这底下删除函数和插入函数都有了,不是可以直接替代上面四个函数吗?我想说确实可以,但学知识是循序渐进的,先从简单的头插尾插,头删尾删开始,掌握规律,再来把它升华。

(1)因为和数组差不多,尾插尾删较为简单:上代码!!!

void SeqListPushBack(SeqList* pq, SeqDataType x)//尾插
{
	assert(pq);
	SeqCheckCapacity(pq);
	pq->a[pq->size] = x;
	pq->size++;
}
void SeqListPopBack(SeqList* pq)//尾删
{
	assert(pq);
	assert(pq->size > 0);
	pq->size--;
}

(2)头插头删:这里我们就需要挪动数据,头插是从后到前依次往后挪,头删是从前到后依次往前挪。废话少说上代码!!

void SeqListPushFront(SeqList* pq, SeqDataType x)//头插
{
	assert(pq);
	SeqCheckCapacity(pq);
	int end = pq->size - 1;
	while (end >= 0)
	{
		pq->a[end + 1] = pq->a[end];
		end--;
	}
	pq->a[0] = x;
	pq->size++;
}
void SeqListPopFront(SeqList* pq)//头删
{
	assert(pq);
	assert(pq->size > 0);
	int begin = 0;
	while (begin<pq->size-1)
	{
		pq->a[begin] = pq->a[begin + 1];
		begin++;
	}
	pq->size--;
}

(3)查找算法,其实较为简单,说到底就是一次数组遍历:

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

(4)插入删除算法:插入删除在学会头插头删挪动方法的基础,就也不难。

void SeqListInsert(SeqList* pq, int pos, SeqDataType x)
{
	assert(pq);
	assert(pos >= 0 && pos <= pq->size);//小于等于是为了方便尾插
	SeqCheckCapacity(pq);
	int end = pq->size - 1;
	while (end >= pos)
	{
		pq->a[end + 1] = pq->a[end];
		end--;
	}
	pq->a[pos] = x;
	pq->size++;

}
void SeqListErase(SeqList* pq, int pos)//删除
{
	assert(pq);
	assert(pos >= 0 && pos <= pq->size - 1);

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

(5)更改:与数组操作相似

void SeqListModify(SeqList* pq, int pos, SeqDataType x)
{
	assert(pq);
	assert(pos >= 0 && pos <= pq->size - 1);
	pq->a[pos] = x;
}

到这里关于顺序表的介绍就这么多了,希望对你有所收获。如有收获,请给点赞。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值