十三. C语言顺序表

前言:

      大家好,好久不见,由于种种原因,顺序表这一专题拖了很久。不过没关系,接下来我将介绍关于顺序表的一些知识。这一块需要我们对之前学习的‘指针’  ‘结构体’ 掌握到位,接下来让我们一起学习吧!

1.顺序表的概念

  在学习顺序表之前,我们不妨先了解一下线性表

线性表(linear list n个具有相同特性的数据元素的有限序列。 线性表是⼀种在实际中⼴泛使 用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储。

  那么线性表到底是什么呢

  我们用图来说明一下

从实际意义上来讲,线性表提供了一些列基本的操作,包括插入元素、删除元素、查找元素、获取元素数量等.  当然,它的底层结构是数组

今天我们学习的顺序表就是线性表的一种 

2.顺序表的分类

2.1 静态顺序表

概念:使用定长数组储存元素

    定义宏

      在这里我们定义宏  #define    N    7     为了方便改变N 的大小,当我们代码量非常之多时,我们对于简单定义的数据可以用  宏   来表示,直观且方便修改

   一键替换

   上述代码中出现了   typedef int  SLDataType;

    前面几章的知识我们知道  typedef  是给一个东西起一个别名 

    在这里,我们给 “int”  起一个别名  “SLDataType”

    为什么这么做呢?

     在我们之后投入到工作中后,我们会发现,工作任务麻烦又复杂,万一情况突变,上级要求你把这个项目某个区块中 所有  int  类型的全部转化为  char  类型的,到那时候,再高级的编译器也无法精确到我们需要改变的区块,这时候我们根据需要把每个项目中  数据类型 给它起个别名,这样问题就解决了,需要改变类型时我们只需要改变它的别名,这样既安全又不会干扰其它区块的相同类型

     静态顺序表的缺陷

    空间给少了不够用,给多了浪费空间    

    给定的数组长度,若不够,会导致后续的数据保存失败

    数据丢失:非常严重的技术事故,严重时威胁工作和公司的利益

 2.2 动态顺序表

概念:按需申请数组长度

3.动态顺序表的实现

     这一部分内容之前,先说明一件事情,其实越往后学习,就越会发现,我们在定义一个区块或者一个项目的名称时,通常是有它实际意义在内的,所以我们要习惯一些复杂的名称.

     创建源文件和头文件

首先,我们创建源文件和头文件

     基本结构体

接着,我们写出基本结构体(对于这部分知识不了解的老头,可以回头看看结构体那一章哦!)

    初始化

这里首先我们来看看如何初始化

SeqList.h

SeqList.c

test.c

大家可以调试一下,这里不再赘述太多操作啦

强调:一定要注意指针的正确使用方法:传参、传地址等

    插入数据

在此过程中,我们可以实现头插(SLPushBack)和尾插(SLPushFront)

尾插

我们首先学习比较复杂的尾插

尾插分为3种情况

当处于前两种情况时,我们可以直接插入    arr[size]=6

而当空间不够时,我们会选择 扩容

扩容有三种办法,分别是

当实际操作后我们会发现,前两种方法是不可用的,它们大量浪费内存资源,且效率低下,而后面这个方法在大部分情况下是没问题的

在这里,作者水平有限,无法说明,不过百度会给出答案

回顾我们之前学习 动态内存管理 用到的 三个函数    malloc  calloc realloc

在这里我们会用到  realloc

void SLCheckCapacity(SL* ps)
{
	if (ps->size == ps->capacity)   //判断是否需要扩容
	{
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;//防止影响capacity
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));//开辟4个整型大小的空间
		if (tmp == NULL)
		{
			perror("realloc fail!");
			exit(1);//退出
		}
		//扩容成功
		ps->arr = tmp;
		ps->capacity = newCapacity;
	}
}

//顺序表的尾部插入
void SLPushBack(SL* ps, SLDataType x) 
{
	//空间不够,扩容
	SLCheckCapacity(ps);
	//空间足够,直接插入
	ps->arr[ps->size++] = x;     
	//ps->size++;
}

这里运用的三目操作符需要大家注意

我们测试一下

发现没问题

当然,我们需要判断 ps 是否为NULL

头插

这里分两种情况,即空间足够和空间不够的情况

void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);

	//判断是否扩容
	SLCheckCapacity(ps);

	//旧数据往后挪动一位,从后往前遍历
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];//i=1  ps->arr[1]=ps->arr[0]
	}
	ps->arr[0] = x;
	ps->size++;

}

我们测试一波

没问题

删除

同样的,这里也分为头删和尾删

尾删

尾删比较简单,我们先看尾删

这里有两种情况

在这里特别注意:当删除一个数据时应让  size - -

好,那么问题来了

当我们进行尾删时是让删除的数据删除,还是置为假呢,因为当你想要删除数据时,其实是给它一个负数(假),这该怎么理解呢?

其实我们只需要改成红色的就行啦

这是为啥呢?

当我们让size --  时,它指向了前一个数据,而当我们想要进行后续的操作时,

比如让我们修改、查找数据二时,size  只标记了下标为2处的数据,这样我们就可以忽视下标为3处的数据,不影响我们操作数据 

我们来测试一下

尾插四次,尾删两次,没有问题

而当我们删过头时,会发生什么呢?

我们会发现,直接报错(断言失败)

头删

头删就有点烦人了

涉及到挪动数据(太烦人了)

头删也分两种情况

没有数据的情况我们直接用断言判断,我们重点看有数据的情况

当顺序表不为空时,后面的数据往前挪动一位,size--

接着我们测试一下

没有问题

同样的,当我们多删时,会出现报错

指定位置插入和删除

这一块儿呢比较简单,我们来看一下

指定位置之前插入数据

假如我们在 pos=3 位置插入  x=100

首先我们需要判断ps是否为NULL和判断空间是否够不够

我们需要pos及之后的数据往后挪一位,pos空出来

我们来详细分析一下这段代码

我们测试一下

但是当我们pos 输入超过顺序表范围呢?

随机值,所以我们要改进一下

指定位置删除数据

这里我们也只考虑顺序表不为空的情况

插入差不多,我们需要删除指定数据,然后让后面数据向前挪动(补缺)

这里我就粗略概况啦,跟插入差不多

我们测试一下

注意:不能删除size位置,size无数据,如果执行,会报错

源代码

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"

void slTest01()
{
	SL sl;
	SLInit(&sl);

	//尾插
	SLPushBack(&sl, 1); //ctrl+d快速复制
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);
	SLPrint(&sl); //1 2 3 4
	//SLPushBack(&sl, 5);
	//SLPrint(&sl);

	头插
	//SLPushFront(&sl, 5);
	//SLPushFront(&sl, 6);
	//SLPushFront(&sl, 7);
	//SLPrint(&sl);  //7 6 5 1 2 3 4

	尾删
	//SLPopBack(&sl);
	//SLPopBack(&sl);
	//SLPopBack(&sl);
	//SLPrint(&sl);  //3  4
	
	头删
	//SLPopFront(&sl);
	//SLPopFront(&sl);
	//SLPopFront(&sl);
	//SLPrint(&sl);   //  4

	指定位置插入
	//SLInsert(&sl, 0, 520);
	//SLPrint(&sl);  //520 1 2 3 4
	//SLInsert(&sl, sl.size, 1314);
	//SLPrint(&sl);  //520 1 2 3 4 1314
	//SLInsert(&sl, 100, 1314);
	//SLPrint(&sl);  
	
	//指定位置删除数据
	//SLErase(&sl, 0);
	//SLPrint(&sl);  //2 3 4
	//SLErase(&sl, sl.size - 1);
	//SLPrint(&sl);  //2 3
	//SLErase(&sl, 1);
	//SLPrint(&sl); //1 3 4
}

int main()
{
	slTest01();
	return 0;

}

SeqList.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//静态顺序表
//
//#define N 100
//typedef int SLDataType;
//
//struct SeqList
//{
//	int a[N];
//	int size;
//};


//动态顺序表

typedef int SLDataType;

typedef struct  SeqList
{
	SLDataType* arr; //存储数据的底层结构
	int capacity;    //记录顺序表的空间大小
	int size;        //记录顺序表当前有效的数据个数
}SL;

//typedef struct SeqList SL;

//初始化和销毁
void SLInit(SL* ps);
void SLDestroy(SL* ps);
void SLPrint(SL* ps); 


//顺序表的头插/尾插
void SLPushBack(SL*ps,SLDataType x);
void SLPushFront(SL* ps, SLDataType x);

//顺序表的头部/尾部删除
void SLPopBack(SL* ps);
void SLPopFront(SL* ps);

//指定位置之前插入数据
//指定位置删除数据
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);

SeqList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"

//初始化和销毁
void SLInit(SL* ps)
{
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}

void SLCheckCapacity(SL* ps)
{
	if (ps->size == ps->capacity)   //判断是否需要扩容
	{
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;//防止影响capacity
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));//开辟4个整形大小的空间
		if (tmp == NULL)
		{
			perror("realloc fail!");
			exit(1);//退出
		}
		//扩容成功
		ps->arr = tmp;
		ps->capacity = newCapacity;
	}
}

//顺序表的头部/尾部插入
void SLPushBack(SL* ps, SLDataType x) 
{
	assert(ps != NULL);//断言
	//assert(ps);
	//if (ps == NULL)
	//{
	//	return;
	//}


	//空间不够,扩容
	SLCheckCapacity(ps);
	//空间足够,直接插入
	ps->arr[ps->size++] = x;     
	//ps->size++;
}

void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);

	//判断是否扩容
	SLCheckCapacity(ps);

	//旧数据往后挪动一位,从后往前遍历
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];//i=1  ps->arr[1]=ps->arr[0]
	}
	ps->arr[0] = x;
	ps->size++;

}



//顺序表的头部/尾部删除
void SLPopBack(SL* ps)
{
	//判断顺序表是否为空
	assert(ps);  //不能传空
	assert(ps->size); //顺序表内数据不能为零

	//顺序表不为空
	//ps->arr[ps->size - 1] = -1;
	//ps->size--;
	ps->size--;
}

void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size);

	//不为空执行挪动操作
	for (int i=0 ;i < ps->size-1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}



void SLPrint(SL* ps)
{
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n");
}


//指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size); //判断pos是否超出顺序表范围
	SLCheckCapacity(ps);//判断空间是否够不够

	//pos 及之后的数据往后挪动一位,pos空出来
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	ps->size++;
}

//指定位置删除数据
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);  //注意:不能删除size位置,size无数据,如果执行,会报错
	
	//pos 以后的数据往前挪动一位
	for (int i = pos; i < ps->size; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

总结:以上就是顺序表的全部内容啦,这是数据结构的开端,希望我们一起进步!

作者留言:代码非唯一性,若有错误欢迎指出!

    创作时间:2024.5.10

  • 24
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值