C语言 :顺序表专题

⽬录
1. 课前准备
2. 顺序表概念及结构
3. 顺序表分类
4. 实现动态顺序表
1 .课程目标
C语言语法基础到数据结构与算法,前面已经掌握并具备了扎实的C语言基础,为什么还要学习数据结构课程?——通讯录
2.需要的储备知识
简单了解,通讯录具备增加、删除、修改、查找联系人等操作。要想实现通讯录项目两个技术关键:
1)C语言语法基础
2)数据结构之顺序表/链表
3.数据结构相关概念
 1.什么是数据结构?
    数据结构是由"数据“和“机构”两词组合而来。
概念:数据结构是计算机 存储 组织数据 的方式。
【思考】有了数组,为什么还要学习其他的数据结构?
假定数组有10个空间,已经使⽤了5个,向数组中插⼊数据步骤:
    求数组的⻓度,求数组的有效数据个数,向下标为数据有效个数的位置插⼊数据(注意:这⾥是
否要判断数组是否满了,满了还能继续插⼊吗).....
假设数据量⾮常庞⼤,频繁的获取数组有效数据个数会影响程序执⾏效率。
数组不满足用于复杂场景数据的管理。
1)数组仅存储同类型的数据  int a[100]、char a[100]
2)数组可提供的接口不足以支撑复杂场景的数据处理
a[100]存储的是年龄数据
学生管理系统
1、查看学生信息页面
查看学生信息.......
2、学生个人信息页面
查看学生信息.......
3、1班同学个人信息页面
查看学生信息.......
......
顺序表
1.顺序表的概念及结构
  顺序表是线性表的一种。
  线性表:指的是具有相同特性的一类数据结构的统称(比如 橘子 苹果 香蕉 火龙果 都是水果)
  线性表在逻辑结构是线性的(连着的),在物理结构不一定是线性的(可能不存放在一块)。
  逻辑结构是人为想象出来的    物理结构是内存存储上的。
  顺序表的底层结构是数组,所以顺序表在逻辑结构上是线性的,在物理结构上也是线性的。
  数组分为定长数组和动态数组。
2.顺序表分类
  顺序表和数组的区别
     顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口
  顺序表的分类
       顺序表分为两种:静态顺序表和动态顺序表
      //静态顺序表
struct SeqList{
     int a[100];
     int size; //有效数据个数
}

空间不够造成数据无法存储,在实际场景中,若数据无法有效保存,就会造成数据丢失

静态顺序表的特点:

空间给小了不够用,给多了造成空间浪费。

    //动态顺序表--按需申请

struct SeqList{
     int *a;
     int size;//有效数据的个数
     int capacity;//空间大小
}

二:顺序表的相关操作

结构体的重命名,简化其名,用之顺手尔

//动态顺序表
typedef int SLDataType;
struct SeqList
{
	SLDataType* a;
	int size;//顺序表中有效的数据个数
	int capacity;//顺序表当前的空间大小
};
typedef struct SeqList SL;

在这个代码中 我们把类型 为:struct SeqList 重命名为SL

typedef struct SeqList SL;

当我们所写的结构体给他人使用时,我们并不知道他要使用那种类型所以就不仅仅是我们缩写的int类型,我们可以写成 typedef int SLDataType;

而我们在定义数据域的时候需要写成

SLDataType*a;

# define INIT_CAPACITY 4
typedef int SLDataType;
// 动态顺序表 -- 按需申请
typedef struct SeqList
{
SLDataType* a;//可以实现其他类型
int size; //  数据表的有效个数
int capacity; //顺序表的大小
}SL;
//函数声明
void SLInit (SL* ps);//初始化
void SLDestroy (SL* ps);//销毁
void SLPrint (SL* ps);//打印
// 扩容
void SLCheckCapacity (SL* ps);
// 头部插⼊删除 / 尾部插⼊删除
void SLPushBack (SL* ps, SLDataType x);//尾插
void SLPopBack (SL* ps);//尾删
void SLPushFront (SL* ps, SLDataType x);//头插
void SLPopFront (SL* ps);//头删
// 指定位置之前插⼊ / 删除数据
void SLInsert (SL* ps, int pos, SLDataType x);//指定位置插入
void SLErase (SL* ps, int pos);//指定位置删除
int SLFind (SL* ps, SLDataType x);

顺序表里面涉及函数的具体实现

1.顺序表的初始化

void SLInit(SL *ps) {
	ps ->a = NULL;//结构体成员访问操作符  一个是. 一个是->
	//结构体变量.成员变量名   结构体指针->成员变量名
	ps ->size = ps->capacity = 0;
}

2.顺序表的销毁

void SLDestroy(SL* ps)//销毁
{
	if (ps->a)//判断所指向的空间是否为空
	{
		free(ps->a);//free只能用来释放掉动态内存开辟的空间如malloc calloc realloc所开辟的空间
		ps->a = NULL;
	}
	ps->size = ps->capacity = 0;
}

 3.顺序表的打印

遍历数组,然后打印出来

void SLPrint(SL* ps)
{
	for (size_t i = 0; i < ps->size; i++)//这里的size_t 写成int也是可以的
	{
		printf("%d", ps->a[i]);
	}
	printf("\n");
}

4,尾插

尾插就是在数组的末尾插入一个数据,

如果我们想要在3后面插入一个数据,就要在下标为4的位置插入,也就是size-1的位置

当我们空间足够的时候,我们可以直接插入数据,而我们空间不够呢,我们之前在动态内存管理中介绍过

malloc():向内存申请一块连续的空间,且不初始化

calloc():向内存申请一块连续的空间,且初始化为0

realloc():向内存申请一块连续的空间不初始化,如果申请空间时,后面有的空间已经被占用,就会重新找一块连续的空间,且会把之前的数据拷贝下来。

而我们这里用到的是realloc()来开辟空间,因为他零活多变(就像你老板让三个人买西红柿,malloc和calloc只会 买西红柿,而realloc还会把卖西红柿的农人带到公司来商量供应事宜);

在插入数据之前,我们要判断空间是否足够我们插入数据,如果不够就需要扩容

void SLCheckCapacity(SL* ps) {
	if (ps->size == ps->capacity)
	{//空间不足的时候额外插入一个数据
		//扩容
		int newCapcity = ps->capacity == 0 ? 4 : 2 * ps -> capacity;
		SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapcity * sizeof(SLDataType));
		if (tmp == NULL) 
		{
			perror("realloc fail!\n");
			return 1;
		}
		ps->a = tmp;
		ps->capacity = newCapcity;
	}

尾插

//尾插
void SLPushBack(SL* ps, SLDataType x)
{
	//assert(ps!=NULL);
	//暴力的方法
	assert(ps);//确保指针的有效性
	//先确认是否有空间可以插入  不够就扩容
	//够了直接尾插
	SLCheckCapacity(ps);//因为SLCheckCapacity()函数中我们已经做好了判断,
    //所以不需要我们再判断
	//直接插入数据
	ps->a[ps->size++] = x;
}

5头插

头插,就是再再下标为0的位置插入数据,之前的数据全部后移,那么是先后移,还是先插入,如果我们先插入就会把第一个数据0覆盖,所以我们要先后移数据,俗话说的好打扫干净屋子再请客

//头插
void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);//惯例断言
	//惯例先判断空间是否足够,如果不够就扩容
	SLCheckCapacity(ps);
	//空间足够,全体后移
	for (size_t i = ps->size; i > 0; i--)
	{
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[0] = x;//把要插入的数据请进来
	ps->size++;//我们多了一个人,所以我们空间就要加1.
}

6,尾删

判空

bool SLIsEmpty(SL* ps)//头文件 <stdbool.h>
{
	assert(ps);
	//这样子是不对的,这里只能判断空间是否足够
	//return ps->size == ps->capacity;
	return ps->size == 0;
}
void SLPopBack(SL* ps)
{//判断顺序表是否为空
	assert(ps);
	assert(!SLIsEmpTY(ps));
//assert(IsEmpty(ps))这样写是不对的 assert()断言是用来判断里面的表达式是真还是假,
	//只有为假的话,这个断言才会生效
	//ps->a[ps->size -1]=0;
	ps->size--;//没有必要进行赋值,直接size--
}

7头删

void SLPopFront(SL* ps)
{
	assert(ps);//我们要判断一下顺序表是否为空
	aeesrt(!SLIsEmpty(ps));
	//让后面的数据往前挪动一位
	for (size_t i = 0; i < ps->size - 1; i++)
	{
		ps->a[i] = ps->a[i + 1];//我们只需要用后面的数据覆盖前面的数据,
    //就可以实现头删
	}
	ps->size--;
}

8.指定位置插入数据

//指定位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	//不要忘了对pos加以限制
	assert(pos >= 0 && pos <= ps->size);
	//管理 扩容
	SLCheckCapacity(ps);
	//我所指定的位置以后的都有向后移
	for (int i = ps->size - 1; i > pos - 1; i--)
	{
		//最后一次进来的i是pos
		ps->a[i + 1] = ps->a[i];//ps->a[pos+i]=ps->a[pos]
	}
	ps->a[pos] = x;
	ps->size++;
}

9.删除指定位置数据

首先我们肯定要判断顺序表是否为空,指定的位置是否有效,但是这次我们不需要判断空间是否足够大(这次不吃你家大米饭);

void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(!SLIsEmpty);
	//限定pos范围
	assert(pos >= 0 && pos < ps->size);
	for (int i = pos; i < ps->size - 1; i++)
	{//删除指定位置的数据,我们只需要用指定位置后面的数据往前移,覆盖掉即可
		//最后一次进来的i的数据ps->size-2
		ps->a[i] = ps->a[i + 1];//ps->a[ps->size-2]=ps->a[ps->size-1]
	}
	ps->size--;
}

好的,顺序表专题就此结束,我们顺序表 通讯录见

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值