动态顺序表(C语言)

顺序表概念及结构

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

在这里,我们要特别注意红色标记,它和数组是有区别的,一定是依次存储数据元素!

顺序表一般可以分为:

  1. 静态顺序表:使用定长数组存储元素。
  2. 动态顺序表:使用动态开辟的数组存储。

静态顺序表

首先,我们看一下静态顺序表结构:

#define MAX 100
typedef int SLDataType;

typedef struct SeqList
{
	SLDataType arr[MAX];
	size_t size;//有效数据的个数
}SeqList;

静态顺序表的初始化我们注意一下:

void SeqListInit(SeqList* ps)
{
	assert(ps);
	memset(ps->arr, 0, sizeof(SLDataType) * MAX);//初始化数组
	ps->size = 0;
}

在这里,初始化数组我们用memset函数来初始化,这是要注意的一点,其它的接口函数都非常简单,下面我们重点讲解一下动态顺序表。

动态顺序表

动态顺序表的结构是什么样的?请往下看:

动态顺序表的结构

在这里插入图片描述
了解了动态顺序表的结构,下面我们来实现顺序表的基本接口:

初始化

使用顺序表时,一定要初始化,不然会出现错误。

void SeqListInit(SeqList* ps)
{
	assert(ps);

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

在这里也可以有其它的初始化方式,可以一开始给它一个空间,都是可以的,我们这里为什么是0,下面再说。

检查扩容

当数据满的时候,我们就需要扩容,在很多地方都需要用到,所以我们就把它单独写个函数。
我们先看下面的代码:

void SeqListCheckCapacity(SeqList* ps)
{
	assert(ps);
	// 如果满了,我们要扩容
	if (ps->size == ps->capacity)
	{
	SLDataType* tmp = (SLDataType*)realloc(ps->arr, sizeof(SLDataType)*ps->capacity*2);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		else
		{
			ps->arr = tmp;
			ps->capacity*=2;
		}
	}
}

这个代码是有问题的,因为在初始化的时候,我们将capacity初始化为0,这里乘2,还是0。
我们应该这样去写:
在这里插入图片描述
这里,我们扩容前先判断一下,capacity是否为0,如果为0,我们就随便赋一个值,如果不为0,我们乘2倍(随便乘几,2倍最合适,因为扩少了,会频繁的扩,增加开销,扩大了,容易浪费)。

ps->arr为NULL,我们不必担心,因为realloc函数的第一个参数如果为NULL,realloc的作用和malloc一样,开辟空间。

如果开辟失败,我们就exit(-1),强制结束,因为都开辟失败了,也就没法进行下面的内容,所以这里用exit而不是return。

尾插函数

void SeqListPushBack(SL* ps, SLDataType x)
{
	SeqListCheckCapacity(ps);//检查增容
	ps->arr[ps->size] = x;
	ps->size++;
}

尾插很简单,因为数组下标是从0开始的,所以ps->size就是最后一个元素下一个位置。插入后,有效数据加1。

头插函数

头插函数比尾插复杂一点,因为在表头插入,表中的数据都要往后挪动一位。假如我们将4插入表头:
在这里插入图片描述
我们需要将1,2,3向后移动:
在这里插入图片描述
将4放进去:
在这里插入图片描述

void SeqListPushFront(SL* ps, SLDataType x)
{
	SeqListCheckCapacity(ps);//检查增容
	int end = ps->size - 1;
	while (end >= 0)
	{
		ps->arr[end + 1] = ps->arr[end];
		end--;
	}
	ps->arr[0] = x;
	ps->size++;

}

尾删函数

尾删函数最简单,直接ps->size–,这样就会认为有效数据少一个:

void SeqListPopBack(SL* ps)
{
	//判断顺序表是否为空
	if (ps->size == 0)
	{
		printf("size is NULL\n");
		return;
	}
	ps->size--;
}

头删函数

头删我们只需要将后面的数据向前覆盖:
在这里插入图片描述

void SeqListPopFront(SL* ps)
{
	if (ps->size == 0)
	{
		printf("size is NULL\n");
		return;
	}
	int end = 0;
	while (end <ps->size-1)
	{
		ps->arr[end] = ps->arr[end + 1];
		end++;
	}
	ps->size--;
}

在pos位置插入x

假设我们要将3插入到下标为1的地方:
在这里插入图片描述
所以,我们要将下标为1,2,3的数据向后移动,然后将3放进去:
在这里插入图片描述
这里这样写是有问题的:
当pos为0,也就是头插时,ps->size为0,end为-1,因为pos为size_t,end>=pos会发生整型提升,就会出现死循环。

那么我们该如何解决呢?

方法一:
把pos的类型改为int,虽然能解决问题,但有时候库里的函数实现就是size_t。

方法二:
在end>=pos时,把pos强制转换成int

方法三:
我们上面写的函数end是指向最后一个元素的:
在这里插入图片描述
我们把end指向最后一个元素下一个位置:
在这里插入图片描述

size_t end = ps->size;
	while (end >pos)
	{
		ps->arr[end] = ps->arr[end-1];
		--end;
	}

我们把end前一个元素给end,当end为1时就把下标为0的元素移动到后面了。

删除pos位置的数据

这里就很简单了,代码如下:

void SeqListErase(SeqList* psl, size_t pos)
{
	assert(psl);
	assert(pos < psl->size);

	size_t begin = pos + 1;
	while (begin < psl->size)
	{
		psl->arr[begin - 1] = psl->arr[begin];
		++begin;
	}

	psl->size--;
}

这里就不会出现上面的问题,因为begin为pos+1,不会出现小于0情况。

销毁函数

在不用顺序表的时候记得销毁,因为有一些越界报错只有在销毁时才能被编译器检查到,这时候一般是越界问题。

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

记得把arr置为NULL,否则会出现野指针。

顺序表的优缺点:

优点:连续物理空间,方便下标随机访问。

缺点:

  1. 中间/头部的插入删除,时间复杂度为O(N)
  2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
  3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

总结:
到这里,动态顺序表基本结束了,还有查找和打印,这两个比较简单就不写了。如果大家认为我有哪些不足之处或者知识上的错误都可以告诉我,我会在之后的文章中不断改正,也请大家多多包涵。如果大家觉得这篇文章有用的话,也希望大家可以给我关注点赞,你们的支持就是对我最大的鼓励,我们下一篇文章再见。
在这里插入图片描述

  • 32
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 39
    评论
有序表合并是指将两个有序表合并成一个有序表的操作。在C语言中,可以使用数组来表示有序表,并通过遍历和比较元素的方式进行合并。 以下是一个简单的示例代码,演示了如何合并两个有序表: ```c #include <stdio.h> // 合并有序表的函数 void merge(int arr1[], int size1, int arr2[], int size2, int result[]) { int i = 0, j = 0, k = 0; // 遍历两个有序表,比较元素大小,并将较小的元素放入结果数组中 while (i < size1 && j < size2) { if (arr1[i] <= arr2[j]) { result[k++] = arr1[i++]; } else { result[k++] = arr2[j++]; } } // 将剩余的元素放入结果数组中 while (i < size1) { result[k++] = arr1[i++]; } while (j < size2) { result[k++] = arr2[j++]; } } int main() { int arr1[] = {1, 3, 5, 7}; int size1 = sizeof(arr1) / sizeof(arr1[0]); int arr2[] = {2, 4, 6, 8}; int size2 = sizeof(arr2) / sizeof(arr2[0]); int result[size1 + size2]; // 结果数组大小为两个有序表的大小之和 merge(arr1, size1, arr2, size2, result); printf("合并后的有序表:"); for (int i = 0; i < size1 + size2; i++) { printf("%d ", result[i]); } printf("\n"); return 0; } ``` 运行以上代码,输出结果为: ``` 合并后的有序表:1 2 3 4 5 6 7 8 ``` 这段代码中,`merge`函数接受两个有序表的数组和大小,以及一个结果数组。它使用三个指针分别指向两个有序表和结果数组的当前位置,通过比较元素大小,将较小的元素放入结果数组中。最后,将剩余的元素依次放入结果数组中。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

学代码的咸鱼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值