C语言 超详细顺序表的模拟实现实例建议收藏

 更新时间:2022年03月25日 14:36:15   作者:鹿九丸  
程序中经常需要将一组数据元素作为整体管理和使用,需要创建这种元素组,用变量记录它们,传进传出函数等。一组数据中包含的元素个数可能发生变化,顺序表则是将元素顺序地存放在一块连续的存储区里,元素间的顺序关系由它们的存储顺序自然表示

概念及结构

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

顺序表一般可以分为:

静态顺序表:使用定长数组存储元素,元素的数目无法进行修改。

//顺序表的静态存储
#define N 7
typedef int SLDataType;
typedef struct SeqList
{
	SLDataType array[N];//定长数组
	size_t size;//有效数据的个数
}SeqList;

动态顺序表

//顺序表的动态存储
typedef struct SeqList
{
	SLDataType* array;//指向动态开辟的数组,空间不够可以增容
	size_t size;//有效数据的个数
	size_t capacity;//容量空间的大小
};

接口实现

静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪 费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。

1 顺序表的动态存储

typedef int SLDataType;//顺序表中存储的数据,此处假设是int型
typedef struct SeqList
{
	int* a;//指向动态开辟的数组空间,空间可以随时增容
	int size;//存储数据个数
	int capacity;//存储空间大小
}SL,SeqList;

2 顺序表初始化

void SeqListInit(SeqList* psl);//声明
void SeqListInit(SeqList* psl)
{
	assert(psl);//进行断言是因为当psl为NULL时,下面的操作将无法进行,因为空指针是无法进行解引用的。
	psl->a = NULL;
	psl->size = 0;
	psl->capacity = 0;
}//函数实现

注意:进行断言是因为当psl为NULL时,下面的操作将无法进行,因为空指针是无法进行解引用的,后面也是如此。

3 顺序表的销毁

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

注意:free后面括号中的指针必须是malloc开辟出来的那块空间,且不能有任何的偏差(即指针不能发生移动)。

下面进行举例:

image-20220313121909217

像上面这样使用是完全没有问题的,但是像下面这样进行使用就出现了问题:

image-20220313122035082

tmp进行自增操作后,就指向了下图所示位置:

image-20220313122258326

free的位置是tmp++后的位置,这不符合C语言的规定,且即使正常的释放掉了,前面的那一块int空间也将引起内存泄漏问题,即动态开辟的内存忘记释放。

4 顺序表的尾插

void SeqListPushBack(SeqList* psl,SLDataType x);//声明
void SeqListPushBack(SeqList* psl, SLDataType x)
{
	assert(psl);
	//如果满了,就进行扩容
	SeqListCheckCapacity(psl);
	psl->a[psl->size] = x;
	psl->size++;
}

image-20220313123214723

5 顺序表的尾删

void SeqListPopBack(SeqList* psl);
void SeqListPopBack(SeqList* psl)
{
	assert(psl);
	if(psl->size > 0)
	{
		psl->size -= 1;
	}
}

6 顺序表的头插

void SeqListPushFront(SeqList* psl, SLDataType x);
void SeqListPushFront(SeqList* psl, SLDataType x)
{
	assert(psl);
	SeqListCheckCapacity(psl);
	int end = psl->size - 1;
	while (end >= 0)
	{
		psl->a[end+1] = psl->a[end];
		--end;
	}
	psl->a[0] = x;
	psl->size++;
}

顺序表的头插会涉及到后续元素的移动,头插时要将顺序表中的元素从后面开始进行移动,因为从前面开始移动的话会出现覆盖现象。下面是图示:

image-20220313150759211

7 顺序表的头删

同理,如果想要元素不被覆盖,就只能从前向后进行移动。

void SeqListPopFront(SeqList* psl);
void SeqListPopFront(SeqList* psl)
{
	//挪出数据,腾出头部空间
	//方法一:从1开始移动
	/*assert(psl);
	if (psl->size > 0)
	{
		int begin = 1;
		while (begin < psl->size)
		{
			psl->a[begin - 1] = psl->a[begin];
			begin++;
		}
		psl->size--;
	}*/
	//方法二:从0开始移动
	assert(psl);
	if (psl->size > 0)
	{
		int begin = 0;
		while (begin < psl->size - 1)
		{
			psl->a[begin] = psl->a[begin + 1];
			begin++;
		}
		psl->size--;
	}
}

下图是两种移动方式的区别:

image-20220313152013487

问:为什么不可以直接将指针进行加1操作?

  • free释放空间时的指针和malloc开辟空间的指针必须相同
  • mallo开辟的空间存在浪费,即0的那块空间被浪费,无法进行使用,因为那块空间是被合法申请的。

8 顺序表容量的检查与扩容

void SeqListCheckCapacity(SeqList* psl);
void SeqListCheckCapacity(SeqList* psl)
{
	assert(psl);
	if (psl->capacity == psl->size)
	{
		size_t newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
		SLDataType* tmp = (SLDataType*)realloc(psl->a, sizeof(SLDataType) * newCapacity);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		else
		{
			psl->a = tmp;
			psl->capacity *= 2;
		}
	}
}

注意点1:此处考虑使用的是如果容量不够,就将容量扩容为原容量的两倍,但是最开始的容量是0,所以要考虑到最开始为0的情况。

注意点2:要对扩容是否成功进行检测,即判断刚申请的空间是否为空。

9 顺序表任意位置的插入

void SeqListInsert(SeqList* psl,size_t pos,SLDataType x);
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x)
{
	assert(psl);
	//较为温和的检查方式
	/*if (pos > psl->size)
	{
		exit(-1);
	}*/
	assert(pos <= psl->size);//较为暴力的检查方式
	SeqListCheckCapacity(psl);
	size_t end = psl->size;
	while (end > pos)
	{
		psl->a[end] = psl->a[end-1];
		--end;
	}
	psl->a[pos] = x;
	psl->size++;
}

注意:

问:为什么end从psl->size开始?

答:此处需要注意的是pos和end的类型,为什么呢?因为两者都是无符号类型,所以尤其注意当end到了-1的时候,就会变成一个很大的数字,此时如果以此作为下标进行访问,一定会出现越界访问内存的情况,考虑一种极端情况,当pos为0的时候,end最小的时候就是为0,所以此时不会出现越界的情况,但是如果end是从psl->size - 1开始的话,那么while循环体内的语句就变成下面这样:

while(end > pos)
{
	psl->a[end+1] = psl->a[end];
	--end;
}

最后end的最小值会变成-1,但是因为end是size_t类型,所以会变成一个很大的数字,在whle()循环条件判定时条件始终是满足的,同时在进入循环体内之后,会出现越界访问内存的操作。所以两种情况的图如下所示:

image-20220311164610452

10 顺序表任意位置的删除

void SeqListErase(SeqList* psl, size_t pos);
void SeqListErase(SeqList* psl, size_t pos)
{
	assert(psl);
	assert(pos <= psl->size);
	size_t begin = pos+1;
	while (begin < psl->size)
	{
		psl->a[begin-1] = psl->a[begin];
		++begin;
	}
	psl->size--;
}

11 顺序表的打印

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

12 顺序表元素的查找

int SeqListFind(SeqList* psl,SLDataType x);
int SeqListFind(SeqList* psl, SLDataType x)
{
	assert(psl);
	for (int i = 0; i < psl->size; i++)
	{
		if (psl->a[i] == x)
			return i;//找到了对应元素,返回相应的下标
	}
	return -1;//说明没有找到对应的元素
}

13 顺序表元素的修改

void SeqListModify(SeqList* psl, size_t pos, SLDataType x);
void SeqListModify(SeqList* psl, size_t pos, SLDataType x)
{
	assert(psl);
	assert(pos < psl->size);
	psl->a[pos] = x;
}

到此这篇关于C语言 超详细顺序表的模拟实现实例建议收藏的文章就介绍到这了,更多相关C语言 顺序表内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • centos 7 vscode cmake 编译c++工程的教程详解

    centos 7 vscode cmake 编译c++工程的教程详解

    这篇文章给大家介绍了centos 7 使用vscode+cmake配置简单c++项目的方法,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2020-05-05
  • C++堆排序算法实例详解

    C++堆排序算法实例详解

    这篇文章主要介绍了C++堆排序算法,简单分析了堆排序算法的原理并结合实例形式分析了C++实现堆排序的具体操作技巧,需要的朋友可以参考下
    2017-08-08
  • MATLAB全网最全的colormap的使用教程详解

    MATLAB全网最全的colormap的使用教程详解

    众所周知,MATLAB中的colormap只有少得可怜的几种,有很多应用在很特殊的图形中的colormap几乎都没有,而每次写代码都要去找颜色的图属实太麻烦。所以本文将包全部集成了进来,终于有了这套包含200个colormap的工具函数,希望对大家有所帮助
    2023-02-02
  • Linux环境g++编译GDAL动态库操作方法

    Linux环境g++编译GDAL动态库操作方法

    下面小编就为大家带来一篇Linux环境g++编译GDAL动态库操作方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05
  • C语言实现顺序表的全操作详解

    C语言实现顺序表的全操作详解

    顺序表,全名顺序存储结构,是线性表的一种,线性表用于存储逻辑关系为“一对一”的数据,顺序表自然也不例外,不仅如此,顺序表对数据的物理存储结构也有要求,跟随下文来具体了解吧
    2022-04-04
  • C++11各种锁的具体使用

    C++11各种锁的具体使用

    本文主要介绍了C++11各种锁的具体使用,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • C程序读取键盘码的方法

    C程序读取键盘码的方法

    这篇文章主要介绍了C程序读取键盘码的方法,运行时可通过键盘按键获取其对应的键盘码,文章最后附带了键盘码与按键的对照表,需要的朋友可以参考下
    2014-09-09
  • C++中的string库函数常见函数的作用和使用方法

    C++中的string库函数常见函数的作用和使用方法

    这篇文章主要介绍了C++中的string库函数常见函数的作用和使用方法,库函数的灵活应用是程序员的一大重要技能,本文通过实例实例代码给大家讲解的非常详细,需要的朋友可以参考下
    2022-04-04
  • C语言实现打印数组以及打印注意事项说明

    C语言实现打印数组以及打印注意事项说明

    这篇文章主要介绍了C语言实现打印数组以及打印注意事项说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • 详解C++11中的类型推断

    详解C++11中的类型推断

    C++11中为了更好的支持泛型编程,提供了 auto和decltype两个关键词,目的就是提供编译阶段的自动类型推导,这篇文章主要介绍了C++11中的类型推断,需要的朋友可以参考下
    2023-01-01

最新评论