C语言数据结构(2.线性表和顺序表)

目录

前言

1.线性表

1.1线性表的特点:

2.顺序表

2.1顺序表的特点:

2.2顺序表和数组的区别?

2.3顺序表和数组的关系:

1.数组

2.顺序表

2.4顺序表的分类:

1.静态顺序表

2.动态顺序表

2.5实现动态顺序表:

2.6动态顺序表的增删改查:

1.Seqlist.h头文件

2.Seqlist.c源文件

3.text.c测试文件

2.7顺序表问题与思考


前言

紧接上期的内容,我们已经学过了复杂度的基础知识,那么本篇博客就算是真正的走进了数据结构。那么让我们一起走进线性表和顺序表的世界,和博主一起感受一下数据结构的魅力!话不多说,直接上硬货:


1.线性表

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

1.1线性表的特点:

1. 逻辑结构:都是线性的。

2. 物理结构:不一定是线性的。(物理结构指的是数据在内存中的存储方式)


2.顺序表

概念:顺序表是⽤⼀段物理地址连续的存储单元依次存储数据元素的线性结构,⼀般情况下采⽤数组存储。

2.1顺序表的特点:

1.逻辑结构:是线性的。

2.物理结构:也是线性的。因为顺序表的底层结构是数组!

说了这么多,那么顺序表到底长什么样子呢?请看下图:

那么顺序表和数组有什么区别和关系呢?

2.2顺序表和数组的区别?

顺序表的底层结构是数组,对数组的封装,实现了常⽤的增删改查等接⼝

2.3顺序表和数组的关系:

  1. 顺序表就是对数组进行封装,然后进行对数组数据的增删改查!

  2. 对数组进行封装的工具是:结构体!

1.数组

  1. 定义之前已知数组的大小,直接定义即可!

  2. 定义之前不知道数组的大小,就要使用——动态内存管理!

2.顺序表

  1. 静态顺序表:定义之前已知顺序表的大小:

  2. 动态顺序表:定义之前不知道顺序表的大小;

相比较下来,动态顺序表更好!

需要注意的就是:

线性表包含顺序表,但是顺序表不包含线性表。线性表是一个总的概念!

看了上边的比较,你可能会产生一些疑惑:

什么是静态的顺序表?什么是动态的顺序表?通过后边的解释你可能已经有了一些了解,但是理解的并不是很透彻,那么通过下边的图片和代码的演示,你可能会有一个更好的理解:

2.4顺序表的分类:

1.静态顺序表

概念:使⽤定⻓数组存储元素

静态顺序表缺陷:空间给少了不够⽤,给多了造成空间浪费

看了上边的代码,你可能会疑惑为什么要使用这个代码:

 typedef int SLDataType;

这是因为方便我们对数据类型的修改,使用这个代码就相当于给int类型取了个新的名字,当然,如果你想修改数据类型,那么只用把Int修改为你想要的数据类型即可,这样方便你后续代码的修改,减少了代码的修改工作量。

2.动态顺序表

动态顺序表的空间大小是可以增容的,这也是和静态顺序表的最大区别,所以说和静态顺序表相比较而言,动态顺序表更好,也更值得使用!

那么顺序表的分类讲完了,接下来就是最为重要的,实现动态顺序表,并且完成动态顺序表的增删改查

2.5实现动态顺序表:

2.6动态顺序表的增删改查:

1.Seqlist.h头文件

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

//定义动态顺序表的结构
typedef int SLDatatype;//这是对int类型的重新命名(方便以后进行数据类型的修改),注意和引用的区别

typedef struct Seqlist {
	SLDatatype* arr;//动态顺序表的底层结构是数组
	int capacity;//动态顺序表的空间大小
	int size;//动态顺序表中的有效数据的个数
}SL;

//typedef struct SeqList SL;//这样改名为SL也是可以的,有了这个就可以不用在括号后加上SL
//也不用在struct之前加上typedef


//比如:这样也可以:
//struct SeqList {
//	SLDatatype* arr;
//	int capacity; //空间大小
//	int size;     //有效数据个数
//};
//typedef struct SeqList SL;

//动态顺序表的初始化
void SLInit(SL* ps);

//动态顺序表的销毁
void SLDestroy(SL* ps);

//动态顺序表的打印
void SLPrint(SL* ps);

//动态顺序表的头插法插入数据
void SLPushFront(SL* ps ,SLDatatype x);

//动态顺序表的尾插法插入数据
void SLPushBack(SL* ps, SLDatatype x);

//动态顺序表的头删法删除顺序表
void SLPopFront(SL*ps);

//动态顺序表的尾删法删除顺序表
void SLPopBack(SL* ps);

//在指定位置之前插入数据(pos就是指定的位置)
void SLInsertFront(SL* ps, SLDatatype x, int pos);

//在指定位置之前插入数据(pos就是指定的位置)
void SLInsertBack(SL* ps, SLDatatype x, int pos);

//删除指定位置的动态顺序表的数据
void SLErase(SL* ps, int pos);

//查找指定位置的动态顺序表的数据
void SLFindPos(SL* ps, SLDatatype x, int pos);

//查找动态顺序表中的数据
void SLFind(SL* ps,SLDatatype x);

2.Seqlist.c源文件

#define _CRT_SECURE_NO_WARNINGS
#include "Seqlist.h"

//动态顺序表的初始化
void SLInit(SL* ps)
{
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}
//动态顺序表的销毁
void SLDestroy(SL* ps)
{
	//if (ps->arr != NULL)
	//{
	//	free(ps->arr);//释放空间
	//}
	//ps->arr = NULL;//记得将指针置为空
	//ps->capacity = ps->size = 0;//将数据元素置为初始化的值

	if (ps->arr)//相当于ps->arr != NULL
	{
		free(ps->arr);
	}
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}
//动态顺序表的打印
void SLPrint(SL* ps)
{
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d", ps->arr[i]);
	}
	printf("\n");
}
//插入和删除数据的时候,需要先判断动态顺序表的空间是否充足
void SLCheckCapacity(SL* ps)
{
	//判断空间是否充足
	if (ps->capacity == ps->size == 0)
	{
		//增容2*0=0
		//若capacity为0,给个默认值,否则*2倍
		SLDatatype newcapacity = ps->capacity = 0 ? 4 : 2 * ps->capacity;
		SLDatatype* tmp = (SLDatatype*)realloc(ps->arr, newcapacity * sizeof(SLDatatype));
		if (tmp == NULL)
		{
			perror("realloc fail!");
		}
		ps->arr = tmp;
		ps->capacity = newcapacity;
	}
}
//动态顺序表的头插法插入数据
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];
	}
	//将下标为0的位置空出来给要插入的数据
	ps->arr[0] = x;
	ps->size++;
}

//动态顺序表的尾插法插入数据
void SLPushBack(SL* ps, SLDatatype x)
{
	//粗暴的解决方法---断言
	assert(ps);//等价于assert(ps != NULL)

	温柔的解决方式
	//if (ps == NULL)
	//{
	//	return;
	//}
	//先判断动态顺序表的空间是否充足
	SLCheckCapacity(ps);
	ps->arr[ps->size++] = x;
}

//动态顺序表的头删法删除顺序表
void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size);
	//数据整体向前移动一个位置
	for (int i = 0; i < ps->size; i++)
	{
		ps->arr[i] = ps->arr[i + 1];//i = size-2
	}
	ps->size--;
}

//动态顺序表的尾删法删除顺序表
void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size);
	//ps->arr[ps->size - 1] = -1;//多余了
	ps->size--;
}

//在指定位置之前插入数据(pos就是指定的位置,空间足够才能插入数据)
void SLInsertFront(SL* ps, SLDatatype x, int pos)
{
	assert(ps);
	assert(pos >= 0&&pos<ps->size);
	SLCheckCapacity(ps);
	//pos及之后的数据整体向后移动一位
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1]; //pos+1   ->   pos
	}
	ps->arr[pos] = x;
	ps->size++;
}

//在指定位置之后插入数据(pos就是指定的位置)
void SLInsertBack(SL* ps, SLDatatype x, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	SLCheckCapacity(ps);
	//pos之后的数据向后移动一个位置
	for (int i = ps->size; i > pos + 1; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos+1] = x;
	ps->size++;
}

//删除指定位置的动态顺序表的数据
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);//还有更多的限制,比如动态顺序表不能为空......
	//pos位置的数据删除之后,pos之后的数据全部往前移一个位置
	for (int i = pos; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];//size-2<-size-1
	}
	ps->size--;
}

//查找指定位置的动态顺序表的数据
void SLFindPos(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);//还有更多的限制,比如动态顺序表不能为空......
	return ps->arr[pos];
}

//查找动态顺序表中的数据,并且返回它的数组下标
void SLFind(SL* ps, SLDatatype x)
{
	assert(ps);//判断动态顺序表不能为空
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == x)
		{
			return i;
		}
	}
	//如果动态顺序表全部查找之后,没有找到数据,那么就返回一个无效的数字即可,没有找到:返回一个无效的下标就可以了
	return -1;
}

3.text.c测试文件

#define _CRT_SECURE_NO_WARNINGS
#include "Seqlist.h"

void SLtest01()
{
	SL s;
	//初始化
	SLInit(&s);
	//尾插数据
	SLPushBack(&s, 1);
	SLPushBack(&s, 2);
	SLPushBack(&s, 3);
	SLPushBack(&s, 4);

	//SLPushBack(&s, 5);
	//SLPushBack(&s, 6);
	//SLPushBack(NULL , 6);
	
	//头插数据
	//SLPushFront(&s, 1);
	//SLPushFront(&s, 2);
	//SLPushFront(&s, 3);
	//SLPushFront(&s, 4);
	//打印数据
	SLPrint(&s); //1 2 3 4

	//尾删数据
	//SLPopBack(&s);
	//SLPrint(&s);
	//SLPopBack(&s);
	//SLPrint(&s);
	//SLPopBack(&s);
	//SLPrint(&s);
	//SLPopBack(&s);
	//SLPrint(&s);
	//SLPopBack(&s);
	//SLPrint(&s);
	
	//头删数据
	//SLPopFront(&s);
	//SLPrint(&s);
	//SLPopFront(&s);
	//SLPrint(&s);
	//SLPopFront(&s);
	//SLPrint(&s);
	//SLPopFront(&s);
	//SLPrint(&s);
	//SLPopFront(&s);
	//SLPrint(&s);

	//向相应的位置之前插入数据
	//SLInsertFront(&s, 11, 0);
	//SLPrint(&s);
	//SLInsertFront(&s, 22, s.size);
	//SLPrint(&s);
	//SLInsertFront(&s, 33, 1);
	//SLPrint(&s);
	 
	//向相应的位置之后插入数据
	//SLInsertBack(&s, 11, 0);
	//SLPrint(&s);
	//SLInsertBack(&s, 22, s.size);
	//SLPrint(&s);
	//SLInsertBack(&s, 33, 1);
	//SLPrint(&s);

	//删除数据
	//SLErase(&s, 1);
	//SLPrint(&s); //1 3 4
	//SLErase(&s,s.size-1);
	//SLPrint(&s);//2 3

	//销毁动态顺序表
	SLDestroy(&s);
	//SLDestroy(&s);//ctrl+d
}

int main()
{
	SLtest01();
	return 0;
}

以上便是动态顺序表的相关实现,关于动态顺序表的学习,博主建议各位小伙伴们要学会自己画图进行分析。关于运行结果,需要小伙伴们自己去尝试一下了!

当然,代码当中有一些是博主的创新内容,例如在对应的位置之后插入数据,这些创新的测试,博主就不展示出来了,小伙伴们自己可以去尝试一下!!!

代码小提示
编写代码过程中要勤测试,避免写出⼤量代码后再测试⽽导致出现问题,问题定位⽆从下
⼿。

2.7顺序表的相关习题

动态顺序表的增删改查我们已经实现过了,实践才是检验真理的唯一标准,所以我们通过下边的几道典型顺序表的题目来熟练的使用顺序表的增删改查吧:

2.7.1 移除元素
2.7.2 删除有序数组中的重复项
2.7.3 合并两个有序数组

2.8顺序表问题与思考

中间/头部的插⼊删除,时间复杂度为O(N)
增容需要申请新空间,拷⻉数据,释放旧空间。会有不⼩的消耗。
增容⼀般是呈2倍的增⻓,势必会有⼀定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插⼊了5个数据,后⾯没有数据插⼊了,那么就浪费了95个数据空间。
思考:如何解决以上问题呢?

好了,下期的知识咱们下期再聊,下期主要介绍单链表的实现,用以解决上述顺序表的问题。最后:希望每一个正在学习编程路上的小伙伴们都能有所收获,未来的路上,我们一起加油!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值