【数据结构之顺序表】附源码【收藏】

📝引言

逻辑结构:数据元素之间的逻辑关系;想象出来的。

物理结构:数据结构在计算机中的表示(又称映像)称为数据的物理结构,又称存储结构;实际在内存中存储的状态。

顺序表的实现

💖线性表之-顺序表💖

线性表

线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…

线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

顺序表

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

顺序表一般可以分为:

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

1️⃣静态顺序表

初始化:

在这里插入图片描述

那么我们接下来实现其他接口函数的时候,却发现了静态顺序表太过于局限,如果说我们的数据存放满了,就会出问题,不能够自动扩容。

所以我们考虑用动态顺序表。

静态 VS 动态

在这里插入图片描述

对比静态和动态的结构可以看出两者之间的区别。

  • 静态顺序表较为死板,存储空间被定死。
  • 动态顺序表较为灵活,存储空间被存放满时可以自动扩容。

2️⃣动态顺序表

初始化:

在这里插入图片描述

📃实现接口目录

//初始化顺序表
void SeqListInit(SL* ps);
//打印顺序表
void SeqListPrint(SL* ps);
//销毁空间
void SeqListDestory(SL* ps);
//检查容量增容
void SeqListCheckCapacity(SL* ps);

//头部插入
void SeqListPushFront(SL* ps, SLDataType x);
//尾部插入
void SeqListPushBack(SL* ps, SLDataType x);
//头部删除
void SeqListPopFront(SL* ps);
//尾部删除
void SeqListPopBack(SL* ps);

// 找到了返回x位置下标,没有找打返回-1
int SeqListFind(SL* ps, SLDataType x);

// 指定pos下标位置插入
void SeqListInsert(SL* ps, int pos, SLDataType x);
// 删除pos位置的数据
void SeqListErase(SL* ps, int pos);

💌初始化

void SeqListInit(SL* ps);

初始化需要传址,如果传值是改变不了S1的

在这里插入图片描述

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

插入必然涉及到以下三个问题:

  1. 顺序表没有空间,需要扩容。
  2. 顺序表有空间,不需要扩容,直接插入数据即可。
  3. 顺序表空间已满,需要扩容。

💌打印数据

打印数据可以传值也可以传址,这里仍用的传址。

//打印顺序表
void SeqListPrint(SL* ps)
{
    assert(ps);
    
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->element[i]);
	}
	printf("\n");
}

💌空间销毁

释放空间的好处

  • 防止内存泄漏
  • 帮助检查越界(如果越界没有报错的时候,当我们释放空间的时候它会帮助我们报错)
//销毁顺序表
void SeqListDestory(SL* ps)
{
    assert(ps);
    
	free(ps->element);
	ps->element = NULL;

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

💌尾部插入

void SeqListPushBack(SL* ps, SLDataType x);
  1. 初始化的时候容量被设置为0,所以我们必须要先对容量进行处理。

  2. 然后就是扩容,扩容我们用到realloc,realloc是调整大小的,但是呢,如果原大小为0时,他会和malloc一样开辟空间。一般我们扩容都是扩到原大小的二倍。

    • 其释义如下:

      The realloc function changes the size of an allocated memory block. The memblock argument points to the beginning of the memory block. If memblock is NULL, realloc behaves the same way as malloc and allocates a new block of size bytes. If memblock is not NULL, it should be a pointer returned by a previous call to calloc, malloc, or realloc.

  3. 如果扩容失败就会返回空指针,如果成功则把维护扩容之后空间的那个指针交给维护原来那片空间的指针维护。并调整好扩容后的容量大小。

//尾部插入
void SeqListPushBack(SL* ps, SLDataType x)
{
    assert(ps);
	//当容量不足或者没有容量的时候就扩容
	if (ps->size == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity*2;
		SLDataType* temp = (SLDataType*)realloc(ps->element, newcapacity * sizeof(SLDataType));
		if (temp == NULL)
		{
			printf("realloc fail!\n");
			exit(-1);
		}

		ps->element = temp;
		ps->capacity = newcapacity;
		
	}
	//尾部插入
	ps->element[ps->size] = x;
	ps->size++;
}

💌尾部删除

void SeqListPopBack(SL* ps);

我们先要知道,如果这个数组本来就没有内容或者被删完了就不能继续删了,所以要控制这一点,有效值要控制大于0。然后删除的话,直接自减有效值也就是丢弃右边的数据让有效值的范围左走就行了。

//尾部删除
void SeqListPopBack(SL* ps)
{
    assert(ps);
	/*
	//温柔删除
	if (ps->size > 0)
	{
		ps->size--;
	}
	*/

	//暴力删除
	assert(ps->size > 0);
	ps->size--;
}

💌头部插入

void SeqListPushBack(SL* ps, SQDataType x);

在这里插入图片描述

从4开始依次往后挪一个位置,size为当前的有效位,size起始值是从1开始的。end作为索引位置的下标,所以end是作为下标的存在,下标是从0开始的,所以要减1。

当end指向0的时候,存上数据就终止头插的步骤。

//头部插入
void SeqListPushFront(SL* ps, SLDataType x)
{
    assert(ps);
	//检查容量是否足够并增容
	SeqListCheckCapacity(ps);

	int end = ps->size - 1;
	
	while (end >= 0)
	{
		ps->element[end + 1] = ps->element[end];
		--end;
	}
	//插入
	ps->element[0] = x;
	ps->size++;
}

至此,我们实现了尾部插入和头部插入,相信已经可以发现只要是插入都存在空间不足或者没有空间时需要扩容的情况。这里需要反复复用我们写的扩容的代码,故此,可以将其包装成一个函数void SeqListCheckCapacity(SL* ps);

💌头部删除

void SeqListPopFront(SL* ps);

头删就是往左边覆盖,注意这里的起始位置是从第二个数据开始。

//头部删除
void SeqListPopFront(SL* ps)
{
    assert(ps);
	assert(ps->size > 0);

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

💌任意位置插入

void SeqListInsert(SL* ps, int pos, SQDataType x);
  1. 在任意位置插入数据,那么这个位置pos必须要是在有效数据内插入,不可能说到右边空的空间去插入吧。(保证插入位置的有效性)

  2. 插入数据的时候也可能空间已经满了,所以提前还得检查空间增容

  3. 然后开始插入数据,我们得把pos位置上以及其右边的数据依次右移一个位置腾出pos的空间去插入数据。插入数据后有效位数需要加1

在这里插入图片描述

注:如果有N个数据,那么第N个数据的下标为N-1。

//指定pos下标位置插入
void SeqListInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	//保证插入范围的有效性
	assert(pos >= 0 && pos <= ps->size);
	//检查容量
	SeqListCheckCapacity(ps);

	int end = ps->size - 1;

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

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

}

断言中的pos <= ps->size等于可以保证实现尾插的操作。

💌任意位置删除

void SeqListErase(SL* ps, int pos);
  1. 同样删除范围也必须是在有效范围内。(保证插入位置的有效性)

  2. 那么删除数据还是一如既往的覆盖,也就是从pos位置右边的一个数据开始,将其一个一个依次向左移动一个位置也就是覆盖。

  3. 删除完数据后,有效位数需要减1

//删除pos位置的数据
void SeqListErase(SL* ps, int pos)
{
	assert(ps);
	//保证删除位置的有效性
	assert(pos >= 0 && pos < ps->size);

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

	ps->size--;
}

💌查找数据

遍历一遍数据就好了

//查找某个数据,找到了返回查找数据位置下标,没有找打返回-1
int SeqListFind(SL* ps, SLDataType x)
{
	assert(ps);

	int i = 0;
	for ( i = 0; i < ps->size; i++)
	{
		if (x == ps->element[i])
			return i;
	}
    
	return -1;
}

💌查找某数据并插入数据

结合调用前面实现的查找数据任意位置插入接口即可

//示例
int main()
{	SL s1;

	SeqListInit(&s1);
	SeqListPushBack(&s1, 1);
	SeqListPushBack(&s1, 2);
	SeqListPushBack(&s1, 3);
	SeqListPushBack(&s1, 4);
 
    int pos = SeqListFind(&s1, 3);
 	if(pos != -1)
    {
        SeqListInsert(&s1, pos, 10);
    }
 	else
        printf("find fail!\n");
 
 	SeqListPrint(&s1);
 
 	return 0;
}

💌查找某数据并将其删除

结合调用前面实现的查找数据任意位置删除接口即可

//示例
int main()
{	SL s1;

	SeqListInit(&s1);
	SeqListPushBack(&s1, 1);
	SeqListPushBack(&s1, 2);
	SeqListPushBack(&s1, 3);
	SeqListPushBack(&s1, 4);
 
    int pos = SeqListFind(&s1, 3);
 	if(pos != -1)
    {
        SeqListErase(&s1, pos);
    }
 	else
        printf("find fail!\n");
 
 	SeqListPrint(&s1);
 
 	return 0;
}

💌头部插入(调用任意位置插入接口实现)

void SeqListPushFront(SL* ps, SLDataType x)
{
    assert(ps);
    //检查容量是否足够并增容
	SeqListCheckCapacity(ps);
    //插入
    SeqListInsert(ps, 0, x);
}

💌尾部插入 (调用任意位置插入接口实现)

void SeqListPushBack(SL* ps, SLDataType x)
{
    assert(ps);
    //检查容量是否足够并增容
	SeqListCheckCapacity(ps);
    //插入
    SeqListInsert(ps, ps->size, x);
}

💌头部删除(调用任意位置删除接口实现)

void SeqListPopFront(SL* ps)
{
	assert(ps);
    //保证size的有效性(至少有数据才可以删)
	assert(ps->size > 0);
    
	//删除
	SeqListErase(ps, 0);
}

💌尾部删除(调用任意位置删除接口实现)

void SeqListPopBack(SL* ps)
{
    assert(ps);
    //保证size的有效性(至少有数据才可以删)
    assert(ps->size > 0);
    
    //删除
    SeqListErase(ps, ps->size - 1);
}

💌自动扩容

void SeqListCheckCapacity(SL* ps);
  1. 扩容我们用到realloc,realloc是调整大小的,但是呢,如果原大小为0时,他会和malloc一样开辟空间。一般我们扩容都是扩到原大小的二倍。

    • 其释义如下:

      The realloc function changes the size of an allocated memory block. The memblock argument points to the beginning of the memory block. If memblock is NULL, realloc behaves the same way as malloc and allocates a new block of size bytes. If memblock is not NULL, it should be a pointer returned by a previous call to calloc, malloc, or realloc.

  2. 如果扩容失败就会返回空指针,如果成功则把维护扩容之后空间的那个指针交给维护原来那片空间的指针维护。并调整好扩容后的容量大小。

我们单独把扩容的步骤封装成一个函数。

//扩容
void SeqListCheckCapacity(SL* ps)
{
	//当容量不足或者没有容量的时候就扩容
	if (ps->size == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		SLDataType* temp = (SLDataType*)realloc(ps->element, newcapacity * sizeof(SLDataType));
		if (temp == NULL)
		{
			printf("realloc fail!\n");
			exit(-1);
		}

		ps->element = temp;
		ps->capacity = newcapacity;
	}
}

总结:

  • 删除数据需要考虑为空的情况,必须保证size大于0
  • 插入数据需要考虑空间不足的情况,需要扩容

🎯顺序表的缺陷

  1. 空间不够需要增容,增容我们用的realloc

    这个函数扩容分两种情况

    1. 原地扩

    2. 异地扩;(如果是异地扩容代价会更大)

    在这里插入图片描述

  2. 为了避免频繁扩容,我们每次扩容的空间视情况而定,但是基本都会大于1,故此如果扩容之后只使用一个空间必定会导致空间的浪费。

  3. 数据必须是从开始位置连续存储,那么我们从头部或者中间位置插入删除数据的时候必定会需要挪动数据,效率不高。

而链表就是针对顺序表的缺陷设计出来的!

🎯顺序表的优点

  • 支持随机访问,需要随机访问的结构支持的算法可以很好的适用。

(全剧终)感谢食用!
|
|🔥🔥🔥
|
往期回顾:
详解浮点型在内存中的存储
字节序之Little-Endian&Big-Endian
自己写了一个万能排序
图解指针八大笔试题

  • 38
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 37
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值