数据结构-顺序表的代码(重写)

顺序表 结构体的定义:

 静态数据结构的结构体

typedef int SLDataType;
#define N 100
//静态的顺序表结构体
struct SeqList
{
	SLDataType a[N];
	int size;   //数据个数
};

动态数据结构的结构体

typedef int SLDataType;

typedef struct SeqList
{
	SLDataType* a;  
	int size;      //有效数据个数  (当有效数据个数更空间大小一样的时候就可以进行扩容)
	int capacity;  //空间容量
}ST;

动态开辟和柔性数组的区别

 动态开辟是用一个结构体里的指针指向一块空间,然后这个结构体里面有关于这块空间的一些信息,在动态开辟中新开辟空间,用的是realloc函数来在后面这块空间中扩大,但是柔性数组就是一块空间,只是在这块空间的开始位置存入了size capacity这些数据。

 

 顺序表的管理 -- 增删查改

 顺序表的初始化函数:

//初始化函数
#define INIT_CAPACITY 10  //定义顺序表初始化的空间大小

void SLInit(ST* ps)
{
	ps->a = (SLDataType*)malloc(sizeof(SLDataType) * INIT_CAPACITY);   //开辟一块空间
	// 判断malloc函数是否开辟空间成功
	if (ps->a == NULL)
	{
		perror("malloc fail");
	}

	ps->size = 0;   
	ps->capacity = INIT_CAPACITY;
}


void textSLlist()  //初始化函数的运用
{
    SL s;
    SLInit(&s);
}

顺序表的删除函数

//删除顺序表的函数
void Destroy(ST* ps)
{
	free(ps->a);
	ps->capacity = 0;
	ps->size = 0;
}

顺序表的尾插函数

//尾插顺序表数据函数
void SLPushBack(SL* ps , int x)
{
	//判断这个顺序表是否是满的
	if (ps->size == ps->capacity)
	{
		// 顺序表来说一般一次扩容2倍是最合理的
	SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * ps->capacity * 2);   
		
        //判断realloc是否成功
		if (tmp == NULL)
		{
			perror("realloc fail");
		}
        ps->capacity *= 2;
	}
	ps->a[ps->size] = x; // size指向的位置就是顺序表中最后一位有效数据的后一位
	ps->size++;
}

 free 指针空间出错主要是两种可能:
首先是野指针,野指针中也分为两种情况,一是这个指针是空的,而是我们free这个的这个指针指向的空间不是开头,是中间位置,如下图所示:

 我们在free空间的时候必须从我们开辟空间的起始位置开始释放,从中间其他位置释放都是会报错的。

二是这个指针访问越界,这个和数组访问越界是一样的,都会报错

 顺序表的扩容函数

//扩容函数
void SLCheckCapacity(SL* ps)
{
	assert(ps);
	//判断这个顺序表是否是满的
	if (ps->size == ps->capacity)
	{
		// 顺序表来说一般一次扩容2倍是最合理的
		SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * ps->capacity * 2);
		//判断realloc是否成功
		if (tmp == NULL)
		{
			perror("realloc fail");
		}
	}

}

 顺序表有了扩容为什么没有缩容呢?因为计算机的内存不支持这样操作,我们在扩容是在原本的内存空间的基础之上进行扩容,扩容之后视为一块空间,但是free空间只能释放一块空间。

顺序表的尾删函数

//尾插函数
void SLPushBack(SL* ps,SLDataType x)
{
	assert(ps);  //断言

	SLCheckCapacity(ps);  // 扩容函数(函数内有判断该顺序表有没有满)

	int end = ps->size - 1;
	while (end >= 0)
	{
		ps->a[end + 1] = ps->a[end];
		end--;
	}

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

头插的时间复杂度:O(n^2)

尾插的时间复杂度:O(n)

顺序表的头插函数

 //头插函数

void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size > 0);  //当顺序表中没有数据的时候size不能在size--

	int begin = 1; //begin指向顺序表的第二个数据
	while (begin < ps->size)
	{
		ps->a[begin - 1] = ps->a[begin];
		begin++;
	}
	ps->size--;
}

头删和头插是一样的,n个数据的使用都是时间复杂度都是等差数列的叠加,都是O(n^2)。

指定位置插入数据 

//指定位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	assert((pos >= 0) && (pos <= ps->size));  //判断pos的输入是否符合条件

    SLCheckCapacity(ps);

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

	ps->[pos] = x;
	ps->size++;
}

指定位置删除数据

//指定位置删除数据
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert((pos >= 0) && (pos < ps->size));  //判断pos的输入是否符合条件
											//此处间接检查了顺序表为空的情况

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

	ps->size--;
}

查找数据

//查找数据
void SLFind(SL* ps, SLDataType x)
{
	assert(ps);

	int prev = 0;
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == x)
		{
			return i;    //找到了就返回这个数据对应的下标
		}
	}

	return -1;  //没有找到就返回-1
}

 realloc的原地扩容和异地扩容

 假设我们要在之前的空间基础之上扩容5个大小的空间,如果在原本空间的后面有足够大小的空间,那么就直接在原本空间后面直接原地扩容,原地扩容的相比于异地扩容的效率是很高的,如下图所示:

 

 而异地扩容是在内存的其他位置开辟一块更大的空间,这块空间可以容纳下原始空间和新开辟空间的所有数据。然后把原始空间里的数据复制到新的空间中,再把原来的空间释放掉,

 

当我们扩容之后不需要再使用这么大的空间了,那我们可以再次使用realloc来进行缩容(回收),如下所示:

//realloc 缩容

int main()
{
	int* ptr1 = (int*)malloc(sizeof(int) * 10);  //malloc一块新的空间
	printf("%s\n", ptr1);  //打印ptr这块空间的其实地址

	int* ptr2 = (int*)realloc(ptr1,sizeof(int) * 20); //把这块空间realloc扩大到二十的大小,
	printf("%s\n", ptr2); // 打印此时地址位置,如果与上面的地址不同,则是异地扩容,相同则是原地扩容

	int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 5); //realloc缩容操作,把这块空间缩容到5的大小
	printf("%s\n", ptr3);

	for (int i = 0; i <= 20; i++)
	{
		ptr3[i] = i;    //我们在缩容之后再去按照扩容的大小去访问这块空间,那么此时就会报错,数组访问越界
	}

	free(ptr3);
	return 0;
}

虽然realloc可以实现缩容,但是我们不建议这样做,因为假设我们把原本空间给缩容了,在之后的内存申请中,把这块空间后面的空间给申请了,那么我们之后再对这块空间进行扩容的时候就可能会使用realloc的异地扩容,而异地扩容相对于原地扩容来说效率低了不少,而且我们realloc大多数申请的空间占得空间也不会很多。

那么我们在顺序表中,就只扩不缩。

总结

 上述就是利用c语言实现顺序表基本操作函数,我们也可以对其进行优化,比如写一个菜单来使用这些操作函数,来更好的帮助我们来使用这些操作函数。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

chihiro1122

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

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

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

打赏作者

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

抵扣说明:

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

余额充值