顺序表的实现

顺序表的实现

顺序表的实现_第1张图片
P.S.由于本期是一个工程项目的实现,所以我们分了SeqList.h文件、SeqList.c文件和test.c文件。
这是我的代码:
github SeqList

前言:博主今天开始正式进入到数据结构的学习中啦,会继续以不错的更新频率来和大家分享自己的所学所思和所想,希望大家继续多多点赞关注呀!

文章目录

  • 顺序表的实现
    • 一、背景知识介绍——线性表
    • 二、顺序表
      • 1.静态顺序表
      • 2.动态顺序表
    • 三、动态顺序表的实现
      • 1.接口函数的实现
      • 2.一些思考

一、背景知识介绍——线性表

顺序表是线性表的一种是n个具有相同特性的数据元素的有限序列。常见的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
如果在数据的存储结构上也是线性的,那就是顺序表。
如果在数据的存储结构上不是线性的,那就是链表。

二、顺序表

顺序表的本质就是数组,依靠一段在物理上连续的空间存储数据,使用数组来完成增删查改。

1.静态顺序表

依靠定长数组来完成,所以也很明显,他的适用范围较小,只适合确定元素个数的数据处理工作。

#define N 7
typedef int SLDataType;
typedef struct SeqList
{
	TypeData arr[N];
	int size;//有效数据的个数
}Sq;

2.动态顺序表

使用动态开辟的数组空间存储

typedef int SLDataType;
typedef struct SeqList
{
	SLDataType* p;
	int size;//有效数据个数
	int capacity;//容量大小,字节数
}Sq;

为什么这个动态顺序表就需要多设置一个参数呢,大家可以这个角度想,在进行扩容操作时,我们的数据个数没有变,那么size的值不能变,但是其实有效空间已经扩大了,所以我们必须再设置一个量来存储容量大小。
为什么要使用结构体呢,因为我们这些都是在工程中使用,在工程中,我们不能说我扩一次容就设置一个变量来++,我们把该用的数据都放在结构体里,在调用时也很方便清晰。

三、动态顺序表的实现

typedef int SLDataType;//可以在后续更改变量类型的时候,直接在这换int就行

typedef struct SeqList
{
	TypeData* p;
	int size;
	int capacity;
}SL;

注意:不需要更改变量类型的变量不需要用typedef定义,比如size和capacity就不需要。

1.接口函数的实现

如果我们需要对这个顺序表进行操作,我们一定要依赖于函数来实现我们的操作,下面就让我们来看看如何实现这些接口的操作。

初始化顺序表

void SLini(SL* psL)//声明
{
	psL-> p = NULL;
	psL-> size = 0;
	psL -> capacity = 0;  
}

打印顺序表

void SLprint(SL* psL)
{
for (int i = 0; i < size; i++)
{
	printf("%d ", psL->p[i]);
}
	printf("\n");
}

销毁顺序表

void SLdestroy(SL* psL)
{
	free(psL -> p);
	psL -> p = NULL;
	psL -> size = 0;
	psL -> capacity = 0;
}

检查顺序表空间是否够用

void SLcheck(SL* psL)
{
	if ((psL -> size) == (psL -> capacity)
	{
		int newcapacity = (psL -> capacity) == 0 ? 4 : 2 * psL ->capacity;
		SLDataType* p1 = (SLDataType*)realloc(psL->p, sizeof(SLDataType) * nemcapacity);
		if (p1 == NULL)
		{
			perror("realloc");
			return;
		}
		psL->p = p1;
		psL->capacity = newcapacity;
	}
}

可能在此大家会有疑问,为什么要这么麻烦的初始化,并且初始化后数组空间为0,还要判断呢?
其实这是为了整个工程考虑,初始化数组是因为我们可能根本不知道我们需要处理多少数组,所以设置为0再调整反而是最合理的。其次,判断空间其实不完全是因为初始化后是0,更是因为我们不知道需要处理的数据个数,只有通过这样来判断后,才能安全扩容和安全使用。

后插

void SLpushback(SL* psL, SLDataType x)
{
	SLcheck(psL);
	psL->p[psL->size] = x;
	psL -> size++;
}

前插

void SLpushfront(SL* psL, SLDatatype x)
{
	SLCheckCapacity(psl);
	// 挪动数据
	int end = psl->size - 1;
	while (end >= 0)
	{
		psl->a[end + 1] = psl->a[end];
		--end;
	}

	psl->a[0] = x;
	psl->size++;
}

后删

void SLPopBack(SL* psl)
{
	// 温柔的检查
	/*if (psl->size == 0)
	{
		return;
	}*/

	// 暴力检查
	assert(psl->size > 0);
	psl->size--;//打印的时候打不出来,在后插等操作时还可以给他的值覆盖了。
}

前删

void SLPopFront(SL* psl)
{
	assert(psl->size > 0);
	int begin = 1;
	while (begin < psl->size)
	{
		psl->a[begin - 1] = psl->a[begin];
		++begin;
	}
	psl->size--;
	//切记,不要写成psL->size - 1;
}

中间某位置插入

void SLinsert(SL* psL, SLDataType x, int pos)
{
	SLcheck(psL);
	assert((pos >= 0) && (pos <= (psL->size)));
	int tmp = (psL->size) - 1;
	while (tmp >= pos)
	{
		psL->p[tmp + 1] = psL->p[tmp];
		--tmp;
	}
	psL->p[pos] = x;
	psL->size++;
}

中间某位置删除

void SLerase(SL* psL, int pos)
{
	assert((pos >= 0) && (pos <= (psL->size)));
	int tmp = pos;
	while (tmp < (psL->size) - 1)
	{
		psL->p[tmp] = psL->p[tmp + 1];
		++tmp;
	}
	psL->size--;
}

查找某元素下标

int SLfind(SL* psL, TypeData x)
{
	int i = 0;
	for (i = 0; i < psL->size; i++)
	{
		if (x == psL->p[i])
		{
			return i;
		}
	}
	return -1;
}

2.一些思考

其实刚才的代码也可以继续优化,比如我们在每一个接口里都加上一个assert(psL),可以当我们传入一个空地址时进行报错。
而其实经过这一趟下来大家不难发现,其实顺序表也是有一些弊端的,例如在实现前插、前删、中插和中删时,时间复杂度都是o(n),有点小麻烦。
而且,在扩容的过程中,扩大了浪费,扩小了不够用,很难判断一个度。
所以这就要引出我们接下来要学习的链表了。
并且这个顺序表是依赖数组来实现的,只能存储一种数据类型。
大家敬请期待吧。

你可能感兴趣的:(c语言,前端,开发语言,算法,数据结构)