数据结构->顺序表实战指南,(手把手教会你拿捏数据结构顺序表)

系列文章目录

文章目录

前言

✅作者简介:大家好,我是橘橙黄又青,一个想要与大家共同进步的男人😉😉

🍎个人主页:橘橙黄又青-CSDN博客

今天开始我们正式进入数据结构的学习了,这篇简单了解一下:

  1. 线性表的存储结构:顺序存储结构、链式存储结构;
  2. 顺序表的定义:用一段物理地址连续的存储单元依次存储数据元素的线性结构;
  3. 顺序表的分类:静态顺序表、动态顺序表;
  4. 顺序表的增删查改的实现。

简单了解,通讯录具备增加、删除、修改、查找联系⼈等操作。

1.🍎线性表

在这里我们先了解一下线性表

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

 怎么理解,举一个例子:

生动形象。线性表的存储结构有顺序存储结构和链式存储结构两种。前者称为顺序表,后者称为链表:

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

2.🍎什么是顺序表?

2.1🍎 顺序表定义

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

 

3.🍎顺序表分类

3.1🍎静态顺序表

代码:

//静态顺序表 —— 使用定长数组存储元素(不实用)
#define N 7//存储单元初始分配量
typedef int SLDataType;//SLDataType类型根据实际情况而定,这里是int

typedef struct SeqList
{
	SLDataType a[N];//定长数组
	int size;//有效数据的个数
}SL;

 图解:

静态顺序表

缺陷:空间给少了不够⽤,给多了造成空间浪费  

优点:访问数据快速,由于是连续存储,所以可以直接通过下标访问元素,效率高。

3.2🍎动态顺序表

代码:

typedef int SLDataType;//SLDataType类型根据实际情况而定,这里是int

typedef struct SeqList
{
	SLDataType* a;
	int size;//有效数据的个数
	int capacity;//空间容量
}SL;

图解: 

 动态顺序表

 优点:可以使用指针动态地分配内存,具有高效的存储和访问效率。

 缺点:在插入和删除元素时需要移动大量的数据,效率较低。

4.🍎顺序表的基本操作

通过上面的学习我们已经初步了解动态顺序表,与静态顺序表相比动态的使用更为灵活,可以避免

空间给少了不够⽤,给多了造成空间浪费的问题,合理开辟空间。接下来我们也是用动态顺序表来实现基本操作。

分装函数:

 4.1🍎动态顺序表结构体创建

typedef int SLDataType;

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

代码解析:

  1. 结构体中 a 指向的数组类型是我们重定义的SLDataType,这样当我们想创建其它类型的顺序表时只需要对 typedef 后面的类型进行需改即可;
  2. size是用来计数的,统计当前顺序表一共有多少个有效元素;
  3. capacity是用来表示当前顺序表的容量,当capacity == size表示容量已满。

 4.2🍎初始化和扩容

//初始化和扩容
void SLInit(SL* ps) {
    assert(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;//如果是扩容是0,则开辟4个int空间,不是则开辟原来空间的两倍
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
		if (tmp == NULL) {
			perror("realloc fail!");
			exit(1);//退出程序
		}
		//扩容成功
		ps->arr = tmp;
		ps->capacity = newCapacity;
	}
}

代码深剖 :

  • 在这里我们需要使用assertps进行一下断言,以防传入空指针
  • 开辟空间,一定要进行判断是否开辟成功,如果不进行判断直接使用可能会导致程序崩溃。

 扩容思路:

 4.3🍎打印顺序表

代码:

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

4.4🍎顺序表头部插入

代码:

//顺序表的头部插入
void SLPushFront(SL* ps, SLDataType x) {
	assert(ps);

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

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

图解:

 

 4.5🍎头部删除

代码:

//顺序表的尾部删除
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--;
}

图解:

 4.6🍎顺序表的尾部插入

尾插时需要先判断顺序表是否满了,满了要先进行扩容才能继续进行扩容。size表示有效元素个数,同时也是顺序表中最后一个元素的后一个位置的下标。成功插入后要对有效数据个数size进行加1操作

代码:

//顺序表的尾部插入
void SLPushBack(SL* ps, SLDataType x) {
	//断言--粗暴的解决方式
	//assert(ps != NULL);
	assert(ps);

	//if判断--温柔的解决方式
	//if (ps == NULL) {
	//	return;
	//}

	//空间不够,扩容
	SLCheckCapacity(ps);

	//空间足够,直接插入
	ps->arr[ps->size++] = x;
	//ps->size++;
}

图解:

 

4.7🍎顺序表尾删

只需要有效size往前面挪一个覆盖一个,这样有效的数据就会少一个。

代码:

//顺序表的尾部删除
void SLPopBack(SL* ps) {
	assert(ps);
	assert(ps->size);

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

图解:

4.8🍎指定pos下标位置插入数据

//指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x) {
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);

	SLCheckCapacity(ps);

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

图解:

 

 4.9🍎指定pos下标位置删除数据

代码:

//删除指定位置数据
void SLErase(SL* ps, int pos) {
	assert(ps);
	assert(pos >= 0 && pos < ps->size);

	//pos以后的数据往前挪动一位
	for (int i = pos; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];//ps->arr[i-2] = ps->arr[i-1];
	}
	ps->size--;
}

图解:

 5.🍎项目全部代码

5.1🍎SeqList.h代码

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

//静态顺序表

//#define N 100
//struct SeqList
//{
//	SLDataType 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);

5.2🍎seqlist.c代码

#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;
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
		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判断--温柔的解决方式
	//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--) //i = 1
	{
		ps->arr[i] = ps->arr[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--;
}
//顺序表的尾部删除
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 SLInsert(SL* ps, int pos, SLDataType x) {
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);

	SLCheckCapacity(ps);

	//pos及之后的数据往后挪动一位,pos空出来
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1]; //ps->arr[pos+1] = ps->arr[pos]
	}
	ps->arr[pos] = x;
	ps->size++;
}
//删除指定位置数据
void SLErase(SL* ps, int pos) {
	assert(ps);
	assert(pos >= 0 && pos < ps->size);

	//pos以后的数据往前挪动一位
	for (int i = pos; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];//ps->arr[i-2] = 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");
}

5.3🍎test.c代码

用来测试结果:

#include"SeqList.h"

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

	//测试代码
	// 
	//顺序表的尾部插入
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);//ctrl+dٸ
	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);
	//SLPrint(&sl);   //1 2
	
	//顺序表的头部删除
	//SLPopFront(&sl);
	//SLPopFront(&sl);
	//SLPopFront(&sl);
	//SLPopFront(&sl);
	//SLPopFront(&sl);
	//SLPrint(&sl);   //3 4
	
	//指定位置之前插入数据
	//SLInsert(&sl, 0, 100);
	//SLPrint(&sl); //100 1 2 3 4
	//SLInsert(&sl, sl.size, 200);
	//SLPrint(&sl); //100 1 2 3 4 200
	//SLInsert(&sl, 100, 300);
	//SLPrint(&sl);

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

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

 test.c里面的测试代码,有需要的小伙伴可以解开来试一试

好啦,今天就到这里了,都看到这里了,点一个赞吧,感谢观看。

版权声明:本文为博主作者:橘橙黄又青原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/chendemingxxx/article/details/135757215

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
心中带点小风骚的头像心中带点小风骚普通用户
上一篇 2024年2月19日
下一篇 2024年2月19日

相关推荐