顺序存储【C语言动态顺序表】

目录

线性结构

动态顺序表

SeqList结构体

SeqListInit(顺序表初始化)

SeqListCheckCapacity(容量检查)

SeqListPushBack(尾插数据)

SeqListPopBack(顺序表尾删)

SeqListPushFront(顺序表头插)

SeqListPopFront(顺序表头删)

SeqListFind(查找数据)

SeqListInsert(pos的位置插入元素)

SeqListErase(删除pos位置)

SeqListDestory(释放空间)

全代码:

顺序表优点和缺点


线性结构

线性结构顾名思义,在逻辑结构上每个数据都是连接起来的,像一条线一样。顺序表对应线性结构的顺序存储,顺序存储中,存储的每个元素的地址是连续的。其实也就是常说的数组,平时使用的数组就是一种静态的顺序表。

动态顺序表

动态顺序表的优势

静态顺序表在实际应用上没有什么意义,因为静态顺序表在使用时是把存储个数给写死了,如果空间给大了,空间用不完,给小了空间不够用。

动态的顺序表在一定程度上可以减少空间的浪费,当空间不够用时,动态顺序表可以扩容。

SeqList结构体

动态的顺序表,当空间满了应该增容,要判断存储的元素是否满了,应该要记录下当前顺序表可以存储多少个元素,也就是记录下当前顺序表容量。

为了顺序表还能存储其他类型的数据,最好是将数组的类型重命名,方便之后想存储其他类型数据时,能够方便修改。

typedef int SLDateType;
typedef struct SeqList
{
	SLDateType* a;
	size_t size;//已经存储的元素个数
	size_t capacity;//记录容量
}SeqList;

SeqListInit(顺序表初始化)

初始初始化没有什么讲究,唯一要注意的是capacity在初始化时,可以给他初始化成0,也可以一开始就给一些空间。

void SeqListInit(SeqList* ps)
{
    //检查空指针
	assert(ps);

	ps->a = NULL;
	ps->size = 0;
	ps->capacity = 0;
}

SeqListCheckCapacity(容量检查)

在插入数据前都要判断下容量是否为满,顺序表有三种插入方式,那么为了避免代码的冗余,可以把它独立出来封装出一个函数。

当数组还是空时,realloc是相当于一次malloc。realloc扩容有两种模式:

第一种是原地扩容,也就是在原来数组上增加一些空间,这是比较好的一种情况。

另一种情况是异地扩容,当数组后面的空间不够扩容的新空间大小时,数组会去堆区找一块能够容纳新空间大小的地方,将原来数组的数据拷贝到新空间位置,并释放掉原数组的空间,异地扩容会有一定的时间开销。

为了减少异地扩容带来的时间开销,我们应该尽量的避免频繁扩容。数组可能开大了用不完,开小了空间可能会不够用,折中考虑建议每次开二倍的空间,扩容后空间大小等于新空间大小。

注意,当我们的capacity在初始化时初始化为0,0乘以任何数都等于0,所以capacity = 0时,要先给点空间。

void SeqListCheckCapacity(SeqList* ps)
{
	assert(ps);
	//检查容量
	if (ps->size == ps->capacity)
	{
		//容量不能是0
		size_t newcapcity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		SLDateType* tmp = (SLDateType*)realloc(ps->a,sizeof(SLDateType) * newcapcity);
		if (tmp == NULL)
		{
			//扩容失败
			printf("%s\n", strerror(errno));
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newcapcity;
	}
}

SeqListPushBack(尾插数据)

有了SeqListCheckCapacity的支撑顺序表尾插就非常方便了。顺序表的ps->size就是新空间的位置,每次尾插一个数据让size向后走一步,顺序表的队尾元素永远都在size-1位置。

void SeqListPushBack(SeqList* ps, SLDateType x)
{
	assert(ps);
	SeqListCheckCapacity(ps);
	ps->a[ps->size] = x;
	ps->size++;
}

SeqListPopBack(顺序表尾删)

顺序表尾删也是十分方便的,先保证顺序表要有数据,然后直接让size--,最后一个元素就被删除掉了。当在插入一个元素会直接把那个位置的数据给覆盖掉。那要问那个被删除的位置还在那吗?答案是肯定的,它还在那个位置,但是他已经不是一个有效数据的范围了。被删除的位置无需置成某一个值,虽然可以,但没必要。假如将它置成0,如果我再插入一个新的元素,插入的元素就是0呢?那置0是不是就毫无意义,还不如就一个size--省事。

void SeqListPopBack(SeqList* ps)
{
	assert(ps);
    assert(ps->size > 0);
	ps->size--;
}

SeqListPushFront(顺序表头插)

因为是插入数据,所以判断数据是否要扩容也是必要的。顺序表的头插是比较麻烦的,因为数组是连续的(成也萧何败萧何),要头插数据就要把所有元素都先后移一位,为头插的数据腾出位置之后,才能头插,那么它挪动数据的时间复杂度是O(N)。挪动数据必须是从后往前挪,否则前面的数据会将顺序表的数据全部覆盖。

正确(√):

代码:

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

}

SeqListPopFront(顺序表头删)

删除数据首先数据不能为空,与头插一样,头删依旧需要移动数据。头删时只要让后一个数据覆盖到前一个数据的位置就完成头删了,挪动的次数除去第一个数据不需要挪动,也就是要挪动N-1次,那么除去常数项,顺序表头删的时间复杂度就是O(N)。与头插不同的是,如果还是从后往前挪数据,后面的数据会将前面的数据全部覆盖。

 正确(√):

代码:

void SeqListPopFront(SeqList* ps)
{
	assert(ps);
    assert(ps->size > 0);
	size_t end = 0;
	while (end < ps->size)
	{
		ps->a[end] = ps->a[end + 1];
		end++;
	}
	ps->size--;
}

SeqListFind(查找数据)

查找一个数据找他的下标,要找一个数据就要一个一个数据的去比较。最坏的情况就是找不到这个数据,也有可能这个数据在最后一个位置,因为在遍历顺序表,所以Find的时间复杂度就是O(N)

int SeqListFind(SeqList* ps, SLDateType x)
{
	for (int i = 0;i < ps->size;i++)
	{
		if (ps->a[i] == x)
			return i;
	}
	return -1;
}

SeqListInsert(pos的位置插入元素)

Insert通常会配合Find使用,但是不排除直接使用的,所以最好还是判断一下pos是否在顺序表数据的合法范围内。插入数据与头插时一样,需要将在pos位置之后的数据都后移一位。

// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, size_t pos, SLDateType x)
{
	assert(ps);
	if (pos > ps->size)
	{
		printf("pos 越界\n");
		return;
	}
	SeqListCheckCapacity(ps);
	size_t end = ps->size;
	while (end > pos)
	{
		ps->a[end] = ps->a[end - 1];
		end--;
	}
	ps->a[end] = x;
	ps->size++;
}

Insert也可以被头插和尾插复用,省时省力~~

void SeqListPushBack(SeqList* ps, SLDateType x)
{
	//assert(ps);
	//SeqListCheckCapacity(ps);
	//ps->a[ps->size] = x;
	//ps->size++;

	SeqListInsert(ps, ps->size, x);
}
void SeqListPushFront(SeqList* ps, SLDateType x)
{
	assert(ps);
	//SeqListCheckCapacity(ps);
	//size_t end = ps->size;
	//while (end > 0)
	//{
	//	ps->a[end] = ps->a[end-1];
	//	end--;
	//}
	//ps->a[0] = x;
	//ps->size++;

	SeqListInsert(ps, 0, x);
}

SeqListErase(删除pos位置)

为了防止pos越界应当判断下pos是否合法。当要删除pos位置同样需要挪动数据,让pos位置开始,依次将后面的数据依次覆盖,Erase同样可以被尾头删复用。

void SeqListErase(SeqList* ps, size_t pos)
{
	assert(ps);
	assert(ps->size > 0);
	if (pos > ps->size)
	{
		printf("pos 越界\n");
		return;
	}
	if (ps->size > 0)
	{
		for (size_t i = pos;i < ps->size;i++)
		{
			ps->a[i] = ps->a[i + 1];
		}
		ps->size--;
	}
}

复用:

void SeqListPopFront(SeqList* ps)
{
	assert(ps);
	assert(ps->size > 0);
	//size_t end = 0;
	//while (end < ps->size)
	//{
	//	ps->a[end] = ps->a[end + 1];
	//	end++;
	//}
	//ps->size--;
	SeqListErase(ps, 0);
}
void SeqListPopBack(SeqList* ps)
{
	assert(ps);
	assert(ps->size > 0);
	//ps->size--;
	SeqListErase(ps, ps->size-1);
}

SeqListDestory(释放空间)

在堆区开辟的空间不用了,记得还给操作系统。

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

全代码:

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

typedef int SLDateType;
typedef struct SeqList
{
	SLDateType* a;
	size_t size;
	size_t capacity;
}SeqList;

// 对数据的管理:增删查
void SeqListInit(SeqList* ps);

void SeqListDestory(SeqList* ps);

void SeqListPrint(SeqList* ps);

void SeqListPushBack(SeqList* ps, SLDateType x);

void SeqListPushFront(SeqList* ps, SLDateType x);

void SeqListPopFront(SeqList* ps);

void SeqListPopBack(SeqList* ps);
// 顺序表查找
int SeqListFind(SeqList* ps, SLDateType x);
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, size_t pos, SLDateType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, size_t pos);


// SeqList.c

void SeqListCheckCapacity(SeqList* ps)
{
	assert(ps);
	//检查容量
	if (ps->size == ps->capacity)
	{
		//容量是否为0?
		size_t newcapcity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		SLDateType* tmp = (SLDateType*)realloc(ps->a,sizeof(SLDateType) * newcapcity);
		if (tmp == NULL)
		{
			//扩容失败
			printf("%s\n", strerror(errno));
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newcapcity;
	}
}

void SeqListInit(SeqList* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = 0;
}

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

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

void SeqListPushBack(SeqList* ps, SLDateType x)
{
	//assert(ps);
	//SeqListCheckCapacity(ps);
	//ps->a[ps->size] = x;
	//ps->size++;

	SeqListInsert(ps, ps->size, x);
}

void SeqListPushFront(SeqList* ps, SLDateType x)
{
	assert(ps);
	//SeqListCheckCapacity(ps);
	//size_t end = ps->size;
	//while (end > 0)
	//{
	//	ps->a[end] = ps->a[end-1];
	//	end--;
	//}
	//ps->a[0] = x;
	//ps->size++;

	SeqListInsert(ps, 0, x);
}

void SeqListPopFront(SeqList* ps)
{
	assert(ps);
	assert(ps->size > 0);
	//size_t end = 0;
	//while (end < ps->size)
	//{
	//	ps->a[end] = ps->a[end + 1];
	//	end++;
	//}
	//ps->size--;
	SeqListErase(ps, 0);
}

void SeqListPopBack(SeqList* ps)
{
	assert(ps);
	assert(ps->size > 0);
	//ps->size--;
	SeqListErase(ps, ps->size-1);
}

// 顺序表查找
int SeqListFind(SeqList* ps, SLDateType x)
{
	for (int i = 0;i < ps->size;i++)
	{
		if (ps->a[i] == x)
			return i;
	}
	return -1;
}

// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, size_t pos, SLDateType x)
{
	assert(ps);
	if (pos > ps->size)
	{
		printf("pos 越界\n");
		return;
	}
	SeqListCheckCapacity(ps);
	size_t end = ps->size;
	while (end > pos)
	{
		ps->a[end] = ps->a[end - 1];
		end--;
	}
	ps->a[end] = x;
	ps->size++;
}
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, size_t pos)
{
	assert(ps);
	assert(ps->size > 0);
	if (pos > ps->size)
	{
		printf("pos 越界\n");
		return;
	}
	if (ps->size > 0)
	{
		for (size_t i = pos;i < ps->size;i++)
		{
			ps->a[i] = ps->a[i + 1];
		}
		ps->size--;
	}
}

顺序表优点和缺点

优点:

顺序表优点是地址连续,方便排序,很多排序算法都是基于数组地址的连续。因为地址连续,所以顺序表支持下标的随机访问,可以在任意合法下标内随机访问数据。

缺点:

一、因为顺序表地址连续,在进行头插头删和pos位置插入数据时都要挪动数据。头插头删时间复杂度是O(N),pos位置删除插入要挪动N - (pos+1)次,但pos也可能是0的位置,也就是头删,所以pos位置删除插入的时间复杂度也是O(N)。

二、顺序表都会有空间浪费,无论是静态顺序表还是动态顺序表都无法避免空间浪费的问题,只不过动态顺序表比静态顺序表要更加灵活一点。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
动态顺序表是指顺序表的长度可以动态增长或缩短,其实现方式是使用动态内存分配函数(如malloc、realloc、free)来动态分配、释放内存。 输入元素逆置即将输入的元素按照与输入顺序相反的顺序存储顺序表中。下面是一个示例代码: ```c #include <stdio.h> #include <stdlib.h> #define INIT_SIZE 10 // 初始分配大小 #define INCREMENT 5 // 增量 typedef struct { int *data; // 动态数组 int length; // 当前长度 int size; // 当前分配的存储容量 } SqList; // 动态顺序表 // 初始化动态顺序表 void initList(SqList *L) { L->data = (int *)malloc(INIT_SIZE * sizeof(int)); // 分配初始存储空间 if (L->data == NULL) { printf("Error: memory allocation failed!\n"); exit(1); } L->length = 0; L->size = INIT_SIZE; } // 在动态顺序表中插入元素 void insert(SqList *L, int e) { if (L->length == L->size) { // 空间已满,需重新分配内存 L->data = (int *)realloc(L->data, (L->size + INCREMENT) * sizeof(int)); if (L->data == NULL) { printf("Error: memory reallocation failed!\n"); exit(1); } L->size += INCREMENT; } L->data[L->length++] = e; // 插入元素 } // 逆置动态顺序表中的元素 void reverse(SqList *L) { int i, temp; for (i = 0; i < L->length / 2; i++) { // 前后交换元素 temp = L->data[i]; L->data[i] = L->data[L->length - i - 1]; L->data[L->length - i - 1] = temp; } } // 输出动态顺序表中的元素 void printList(SqList L) { int i; for (i = 0; i < L.length; i++) { printf("%d ", L.data[i]); } printf("\n"); } int main() { SqList L; int i, n; initList(&L); // 初始化动态顺序表 printf("Please input the number of elements: "); scanf("%d", &n); printf("Please input the elements: "); for (i = 0; i < n; i++) { int e; scanf("%d", &e); insert(&L, e); // 插入元素 } printf("The original list is: "); printList(L); reverse(&L); // 逆置元素 printf("The reversed list is: "); printList(L); free(L.data); // 释放内存 return 0; } ``` 在上述代码中,initList函数用于初始化动态顺序表,insert函数用于在动态顺序表中插入元素,reverse函数用于逆置动态顺序表中的元素,printList函数用于输出动态顺序表中的元素。在main函数中,首先输入元素个数和元素值,然后调用insert函数将元素插入动态顺序表中,输出原始的动态顺序表,接着调用reverse函数逆置动态顺序表中的元素,最后输出逆置后的动态顺序表。最后,需要调用free函数释放动态分配的内存。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值