C语言----顺序表(含代码)

        嘿嘿,今天看自己写的文章的时候,发现自己以前少写了一篇关于顺序表的博客。哈哈哈。所以今天就来补补以前欠的账。

        因为这是补充前面的知识哇,所以咧。我们这里就给大家写一些=写自己对实现顺序表的方法和一些看法。

顺序表的含义

       虽然我们是补充,但是我们不能坏了流程,所以还是按照惯性来讲讲顺序表的含义。顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组 上完成数据的增删查改。与我们前面学习过的链表是差不多的。就是认为的联系起来。

      然后我们创建顺序表的时候,有两种方法,一种是动态的一种的静态的。然后嘞我这里是用动态的。所谓动态,就是边创建边使用,静态就是一下子就创建好了内存。这样的话明显动态的优势更大。那么我们也不多说其他的了。我们直接就来写写如何实现顺序表了。

顺序表的实现

       我们已经学过链表的实现了,那么我们知道实现这些的第一步都是创建结构体。然后初始化接着实现那些其他的功能。我们也不废话了。直接来上手实现了。

结构体的创建

       我们都知道创建结构体简单,但是我们要知道的是结构体里面存放的成员是哪些。那我们想一想我们这个顺序表的结构体里面存放一些什么东西咧。

        我觉得大家首先想到的是内容,一个数组没内容那干什么啊。所以我们确定了一个要创建内容。然后就是我们前面说过我们是动态开辟的,那么我们是不是应该再创建一个表示内存大小的啊。其实到这里结构体的基本内容就没什么了。但是我们知道顺序表哇有时候要问我们顺序表的元素个数。如果到时候再遍历一遍的话,好麻烦啊。为什么我们不在最开始就创建一个数来记录元素个数咧。这样前面稍微麻烦一点,但是后面舒服啊。那大家是不是就知道我们这个动态开辟顺序表的结构体里面有哪些成员了。好接下来我们就直接写出代码来实现一下。

#pragma once
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>

typedef int SLDatatype;//确立数组内容

//改成动态开辟
typedef struct SeqList
{
	SLDatatype* a;//指向动态数组指针
	int size;       //数据个数
	int capacity;  //容量-空间大小
}SL;

        OK,大家看了上面的代码应该就清楚动态开辟结构体需要哪些成员了吧。当然也有人想着,我就先用静态开辟。可以,这确实可以,但是说实话,静态真的没有动态好用。有可能造成空间泄露也可能空间浪费。当然如果你事先就计算好了,需要的空间大小是多少的,那么用静态也是完全没问题的。那开始前的计算量也是太大了吧。

初始化 

        好,当我们创建好结构体后,肯定少不了的是初始化了吧。而且我们都知道初始化简单啊。毕竟什么都没有,直接置空就可以了。

//初始化函数
void SLInit(SL* ps)
{
	ps->a = NULL;
	ps->size = ps->capacity = 0;
}
扩容

       好好好,这个小节讲的就是我们的重中之重了。我们前面说过,我们个代码是 动态开辟的。所以我们的扩容是很重要的,那么就不多废话,直接来解释。首先,惯例我们检查一下传递过来的数组是否存在。然后咧我们扩容是很讲原则的,需要扩容我们才扩容。那么我们如何判断是否满了呀。我们前面是不是写了记录数组大小和元素个数的呀。那么是不是当这两个数相同,那么就是满了呀。但又有一个问题了,我们开始初始化了呀。都为0啊,这是不是要区分一下啊。因为我们扩容都是二倍扩嘛。那么我们如果开始都为0的话就等于没扩啊。那么这里我们是不是也要搞一下。然后就是基本操作了。那么我就在代码里面给大家详细的解释

//检查容量-扩容
void SLCheckCapacity(SL* ps)
{
	assert(ps);//确定数组没问题
	if (ps->size == ps->capacity)//如果相等那么就表示要扩容
	{
		int newcapacitv = ps->capacity == 0 ? 4 : ps->capacity * 2;
		//这是一个三目操作符。如果确定了内存为0.那么我就给他内存变为4.如果不是那就二倍。是不是就解决了。
		SLDatatype* tmp = (SLDatatype*)realloc(ps->a, newcapacitv * sizeof(SLDatatype));//用realloc直接初始化了,省了一步
		if (tmp == NULL)//判断是否申请空间成功
		{
			perror("relloc:");
			exit(-1);//结束程序
		}
		ps->a = tmp;//改变数据
		ps->capacity = newcapacitv;
	}
}

       如果大家对这个扩容不太了解的话,大家可以多看一下多思考一下。其实逻辑很简单就是先看是否需要扩,然后需要的话还要判断一下是不是第一次扩,如果是第一次扩的话,直接给一个空间,如果不是的话,就扩二倍。用三目操作符会简单很多。当然用if或者其他的也可以。只是三目会简单方便很多。

尾插

       对于尾插咧。大家都写过很多了。而且顺序表的尾插也算是尾插中简单的了,既然简单的话,我们就直接上代码。

//尾插
void SLPushBack(SL* ps, SLDatatype x)
{

	SLCheckCapacity(ps);//检查容量空间
	ps->a[ps->size] = x;//用元素个数相当于下标
	ps->size++;//下标加一,下一个元素插入的地方
}
尾删

       尾插都来了,那么尾删来一个也无伤大雅吧。反正尾删也不难。

//尾删
void SLPopBack(SL* ps)
{
	assert(ps->size);//确定数组没问题
	ps->size--;
}
头插

       头插就不像尾插那样简单了哦。只是没有那么简单了,毕竟尾插只需要判断一下然后就直接插。但是头插咧。我们既然是头插的话,那么我们要将数组的元素然后统一移动一位。才能给我们要插入的数据留出空间来。而且我们还要判断是否需要扩容,如果我们都不能确保我们头插的时候数组是有的话,那么我们还是要扩容一下。所以头插的话就是需要考虑一下,是否要扩容和统一向后移动一位。

//头插
void SLPushFront(SL* ps, SLDatatype x)
{
	SLCheckCapacity(ps);//判断是否扩容
	//从后向前挪动数据
	int end = ps->size - 1;
	while (end >= 0)
	{
		ps->a[end + 1] = ps->a[end];
		end--;
	}
	ps->a[0] = x;//插入数据。因为第一个嘛。那么下标一定为0
	ps->size++;
}
头删

     

//头删
void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size);
	int begin = 1;
	while (begin < ps->size)//整体向后挪动一位
	{
		ps->a[begin - 1] = ps->a[begin];
		begin++;
	}
	ps->size--;//个数减一
}

成双成对,既然尾删,尾插有了。头上我们也写了那么头删肯定也要来一个。那么头上就与头x是相反的,我们就只需要向前挪动一位,然后就将元素个数减一,那么就结束了。 

任意位置插入

       对于任意位置插入的话,因为我们的顺序表是连续的。所有我门要插入的话,肯定要知道它插入的位置是多少。然后我们要判断这个位置是否在我们的数组个数内,如果超了或者小于,那么我们就插入肯定是错误的。然后还有扩容,每一次都需要判断扩容。然后就是局部的向后移动了。可以理解为任意位置插入可以代替头插和尾插。毕竟头插尾插能做的是任意位置插入都可以做。

//任意插入
void SLInsert(SL* ps, int pos, SLDatatype x)
{
	assert(ps);
	//检查我们要插入的位置
	assert(pos >= 0 && pos <= ps->size);
	SLCheckCapacity(ps);//判断扩容
	int end = ps->size - 1;//确定什么时候结束
	while (end >= pos)//局部移动
	{
		ps->a[end + 1] = ps->a[end];
		end--;
	}
	ps->a[pos] = x;//插入
	ps->size++;//个数++
}
任意位置删除

        对于任意位置删除的话,我们还是需要判断删除的位置是否存在。其实和任意位置插入其实是差不多的,一个是向后移动,一个是向前移动,就这样子。唯一不同的就是任意位置删除,不需要判断是否需要扩容。

//任意删除
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	//注意边界的处理
	int begin = pos + 1;
	while (begin < ps->size)
	{
		ps->a[begin - 1] = ps->a[begin];
		begin++;
	}
	ps->size--;
}
查找数据

        接下来我们就是写的是查找某个数据。对于查找数据的话,我们首先自己也不知道是否这个数据在这个数组里内,所以我们无论如何都要遍历一遍才知道这个要查找的数据在我们的数组内吗?当我们寻找到了之后就返回下标,如果没寻找到就返回一个负一。

//查找
int SLFind(SL* ps, SLDatatype x)
{
	assert(ps);//数组是否存在
	for (int i = 0; i <= ps->size; i++)//直接轴=走一遍
	{
		if (ps->a[i] == x)
			return i;//找到了
	}
	return -1;//没找到

}
修改数据

       对于任意位置修改数据的就是很简单的,因为对于修改数值的话,我们首先只需要判断它这个下标是否存在,如果存在的话我们就修改,不存在的话就结束。然后就直接修改数据就可以了。

//修改
void SLModify(SL* ps, int pos, SLDatatype x)  
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);//判断是否存在
 
	ps->a[pos] = x;//修改
 
}
打印

        很多时候我们都想看一看自己写的对不对。对于顺序表最直接的检验方法就是将它打印出来,然后呢对于打印的话其实就最简单的。因为我们在最开始中已经写过了他的元素个数,然后我们只需要按顺序打印出来就可以了。

//打印函数
void SLPrit(SL* ps)
{
	for (int i = 0; i < ps->size; i++)//直接打印
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}
销毁 

        最后一步也是最简单的一步就是销毁了。我们对于销毁就不需要换人,他是否存在的,因为他如果不存在的话,我们再销毁一遍其实是无所谓的。只需要将里面的内容free掉,然后在置为空就行了。

//销毁
void SLDestory(SL* ps)//动态开辟的内存越界使用有时
{                   
		free(ps->a);
		ps->a = NULL;
		ps->size = ps->capacity = 0;
}

总结 

       好了,以上就是个人对顺序表的一些理解和实现的方法。其实对于顺序版来说还算比较简单的,因为我们学到现在已经对c语言的很多方法和技巧已经了解了。我们更多的需要挖掘深层次的和进行延伸和扩展。

#pragma once
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>

typedef int SLDatatype;//确立数组内容

//改成动态开辟
typedef struct SeqList
{
	SLDatatype* a;//指向动态数组指针
	int size;       //数据个数
	int capacity;  //容量-空间大小
}SL;


//初始化函数
void SLInit(SL* ps)
{
	ps->a = NULL;
	ps->size = ps->capacity = 0;
}

//检查容量-扩容
void SLCheckCapacity(SL* ps)
{
	assert(ps);//确定数组没问题
	if (ps->size == ps->capacity)//如果相等那么就表示要扩容
	{
		int newcapacitv = ps->capacity == 0 ? 4 : ps->capacity * 2;
		//这是一个三目操作符。如果确定了内存为0.那么我就给他内存变为4.如果不是那就二倍。是不是就解决了。
		SLDatatype* tmp = (SLDatatype*)realloc(ps->a, newcapacitv * sizeof(SLDatatype));//用realloc直接初始化了,省了一步
		if (tmp == NULL)//判断是否申请空间成功
		{
			perror("relloc:");
			exit(-1);//结束程序
		}
		ps->a = tmp;//改变数据
		ps->capacity = newcapacitv;
	}
}
//尾插
void SLPushBack(SL* ps, SLDatatype x)
{

	SLCheckCapacity(ps);//检查容量空间
	ps->a[ps->size] = x;//用元素个数相当于下标
	ps->size++;//下标加一,下一个元素插入的地方
}

//尾删
void SLPopBack(SL* ps)
{
	assert(ps->size);//确定数组没问题
	ps->size--;
}

//头插
void SLPushFront(SL* ps, SLDatatype x)
{
	SLCheckCapacity(ps);//判断是否扩容
	//从后向前挪动数据
	int end = ps->size - 1;
	while (end >= 0)
	{
		ps->a[end + 1] = ps->a[end];
		end--;
	}
	ps->a[0] = x;//插入数据。因为第一个嘛。那么下标一定为0
	ps->size++;
}

//头删
void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size);
	int begin = 1;
	while (begin < ps->size)//整体向后挪动一位
	{
		ps->a[begin - 1] = ps->a[begin];
		begin++;
	}
	ps->size--;//个数减一
}


//任意插入
void SLInsert(SL* ps, int pos, SLDatatype x)
{
	assert(ps);
	//检查我们要插入的位置
	assert(pos >= 0 && pos <= ps->size);
	SLCheckCapacity(ps);//判断扩容
	int end = ps->size - 1;//确定什么时候结束
	while (end >= pos)//局部移动
	{
		ps->a[end + 1] = ps->a[end];
		end--;
	}
	ps->a[pos] = x;//插入
	ps->size++;//个数++
}

//任意删除
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	//注意边界的处理
	int begin = pos + 1;
	while (begin < ps->size)
	{
		ps->a[begin - 1] = ps->a[begin];
		begin++;
	}
	ps->size--;
}

//查找
int SLFind(SL* ps, SLDatatype x)
{
	assert(ps);//数组是否存在
	for (int i = 0; i <= ps->size; i++)//直接轴=走一遍
	{
		if (ps->a[i] == x)
			return i;//找到了
	}
	return -1;//没找到

}

//打印函数
void SLPrit(SL* ps)
{
	for (int i = 0; i < ps->size; i++)//直接打印
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

//销毁
void SLDestory(SL* ps)//动态开辟的内存越界使用有时
{                   
		free(ps->a);
		ps->a = NULL;
		ps->size = ps->capacity = 0;
}

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值