【玩转顺序表】顺序表接口代码讲解+Leetcode刷题

一、前言

读完这篇博客你可以学到什么:

  • 什么是顺序表
  • 顺序表增删查改接口的实现
  • 关于顺序表Leetcode题的解题技巧

如果觉得上面的内容会对你会有帮助,那就继续看下去吧。

二、顺序表基本介绍

①什么是顺序表

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。

 顺序表和数组看起来没有也什么区别,那既然有了数组为什么还需要在数据额结构中引入顺序表的概念呢?试想,我们要实现一个图书管理系统,那数组要开多大合适呢,如果数组满了又怎么办呢?显然数组存在其弊端,而顺序表可以解决这一问题。

②顺序表与线性表的关系

什么是线性表:线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

 总而言之,顺序表只是线性表的一种而已,其他的线性表的C语言实现我们会在之后的博客里讲解。虽然在C++语法里有现成的线性表接口,但我们通过C语言对它的底层实现可以加深我们的理解,何乐而不为呢?

③静态/动态顺序表

静态顺序表和数组相比没有什么优点,所以用到的场景也不多。

#define N 10
typedef int SeqListData;   
struct SeqList
{
	int arr[N];				//定长的数组
	int sz;					//有效数据的个数
};

动态顺序表可以进行动态内存管理,使用更为广泛,我们接下来也会使用动态顺序表进行功能接口的实现。

typedef int SeqListData;   
struct SeqList
{
	SeqListData* arr;	//指向动态开辟出来的数组
	size_t sz;				//有效数据的个数
	size_t capacity;		//数组的容量
};

三、增删查改插口的实现

我们即将实现的功能如下,大家可以根据需要点超链接快速跳转

①初始化
void SeqListInit(SeqList* ps)
{
	assert(ps);
	ps->arr = NULL;
	ps->capacity = 0;
	ps->sz = 0;
};
②容量检查
void SeqListCheckCapacity(SeqList* ps)
{
	assert(ps);
	if (ps->sz == ps->capacity)
	{
		int Newcapccity = ps->capacity == 0 ? 1 : 2 * ps->capacity;//(1)
		SeqListData* tmp = realloc(ps->arr, Newcapccity * sizeof(int)); //(2)
		if (tmp != NULL) //(3)
		{
			ps->arr = tmp;
			ps->capacity = Newcapccity;
		}
		else
		{
			printf("realloc失败\n");
			exit(1);
		}
	}
}

(1)注意初始化capacity为0,×2一直还是0,所以需要额外判断
(2)realloc函数第一个参数为NULL时功能相当于malloc
(3)realloc有可能扩容失败,所以不能直接让ps->arr接收realloc的返回值

③尾插
void SeqListPushBack(SeqList* ps, SeqListData x)
{
	assert(ps);
	SeqListCheckCapacity(ps);  //(1)
	ps->arr[ps->sz++] = x;
};

(1)进行插入操作前都需要先检查容量

④尾删
void SeqListPopBack(SeqList* ps)
{
	assert(ps);
	if (ps->sz > 0)  //(1)
	{
		ps->sz--;
	}
};

(1)删除操作需要避免删过头,如果sz为0继续减就会发生越界错误

说到越界给大家看一个有趣的现象,我们将sz > 0的判断删除:
在这里插入图片描述
很显然上面的代码发生了越界错误,但程序为什么没有崩溃呢?

1.越界报错就像是酒驾抽查,不一定都能检查出来并且报警告。
2.动态开辟的内存是在free的时候检测是否发生越界

⑤头插
	void SeqListPushFront(SeqList* ps, SeqListData x)
{
	assert(ps);
	SeqListCheckCapacity(ps);
	int end = ps->sz - 1;
	while (end >= 0)
	{
		ps->arr[end + 1] = ps->arr[end];
		end--;
	}
	ps->sz++;
};
⑥头删
void SeqListPopFront(SeqList* ps)
{
	assert(ps);
	if (ps->sz > 0)
	{
		int start = 0;
		while (start < (int)ps->sz - 1) //(1)
		{
			ps->arr[start] = ps->arr[start + 1];
			start++;
		}
		ps->sz--;
	}
};

(1)注意到ps->sz的类型是size_t,即无符号类型,所以比较之前先发生强制类型准换

⑦pos位置插入

先来看看下面的代码有什么不妥:

void SeqListInsert(SeqList* ps, size_t pos, SeqListData x)
{
	assert(ps);
	if (pos < 0 || pos >= ps->sz)
	{
		printf("pos范围错误\n");
		exit(1);
	}
	SeqListCheckCapacity(ps);
	int end = ps->sz - 1;
	while (end >= pos)
	{
		ps->arr[end + 1] = ps->arr[end];
		end--;
	}
	ps->arr[pos] = x;
	ps->sz++;
};

(1)输入范围错误就直接exit,结束整个工程显得太暴力了,用return更佳
(2)pos可以等于ps->sz,相当于是尾插
(3)⭐当pos等于0时,上面的while语句中会发生下标越界。这是一个很隐晦的错误,我们注意到end的类型是int,而sz的类型是size_t(即无符号类型),当运算符两边的数据类型不同时会发生整形提升,将数据范围小的类型向数据范围大的类型准换,所以end将整形提升为无符号类型,当end从0减到-1时其实在无符号的类型看来是一个非常大的数字。

对while越界错误的改法有很多,下面给出最简单的一种

	void SeqListInsert(SeqList* ps, size_t pos, SeqListData x)
{
	assert(ps);
	if (pos < 0 || pos > ps->sz)
	{
		printf("pos范围错误\n");
		return;
	}
	SeqListCheckCapacity(ps);
	size_t end = ps->sz;
	while (end > pos)
	{
		ps->arr[end] = ps->arr[end - 1];
		end--;
	}
	ps->arr[pos] = x;
	ps->sz++;
};
⑧pos位置删除
void SeqListErase(SeqList* ps, size_t pos)
{
	assert(ps);
	if (pos < 0 || pos >= ps->sz)
	{
		printf("输入pos范围错误\n");
		return;
	}
	if(ps->sz > 0)
	{
		size_t start = pos;
		while (start < ps->sz - 1)
		{
			ps->arr[start] = ps->arr[start + 1];
			start++;
		}
		ps->sz--;
	}
};

当然还有顺序表的遍历查找以及顺序表的销毁,因为很简单就不细说了。

三、双指针法

对于数组相关的方法,最常用的解题技巧是双指针法,大家可以参考这篇博客,本文不再赘述
[零基础算法入门①] 双指针法

  • 25
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 19
    评论
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罅隙`

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值