《数据结构》(二)线性表之顺序表(超详细的万字教程)

11 篇文章 2 订阅
5 篇文章 0 订阅

今天把数据结构顺序表做了一个详细的总结🎉🎉🎉 ,既是方便自己复习也能帮助大家,大家有什么疑惑,或者不同的见解都可以和我讨论 ✉️✉️✉️

字数有点多,建议大家收藏起来慢慢看 😇家人们一起加油哈! 你我终成大牛! 😎😎😎

一.顺序表

下面我们先来介绍一下什么是线性表和什么是顺序表

1.1线性表

🎤线性表: 线性表的其中之一就是顺序表,属于包含与被包含的关系,线性表: 是n个具有相同特性的数据元素的有限序列,常见的线性表:顺序表、链表、栈、队列、字符串…
线性表理想状态下是呈现一条线性的,
但是在物理结构中不一定是连续的,通常以数组和链式结构的形式存储

在这里插入图片描述


1.2顺序表


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


二.顺序表的实现

顺序表一般可以分为两种:

1. 静态顺序表:使用定长数组存储。

🎤我理解的静态表的意思就是:创建时的容量是确定的,也就是使用固定长度的数组 , 下面我们就来构建一下静态存储

//构建顺序表中的静态存储
#define N 100             //用宏自定义数组长度,方便日后更改数据类型,增强代码可维护性
typedef int SLDataType;   //这里是将int重命名为SLDataType,也是增强代码可维护性
typedef struct SeqList
{
    SLDataType a[N];   //定长数组
    int size;             //数组中有效数据的个数
}SeqList;

🎤大家很容易就看出了静态存储有很多弊端。假如我们要存1000个数据,定长数组明显放不下,如果我们定长数组长度是1000,而我们只有10个数组需要存放,显然浪费空间,这样往返改变N的大小是不够方便的,这就体现了静态存储的不灵活性。😕😕😕下面我们就详细介绍动态顺序表与其接口实现


2. 动态顺序表:使用动态开辟的数组存储。

2.1构建动态顺序表
// 顺序表的动态存储
typedef int SQDataType;// 增强程序可维护性
typedef struct SeqList
{
	SQDataType* a;     //指向动态内存开辟的数组
	int size;          // 有效数据的个数
	int capacity;      // 容量空间的大小
}SL;

2.2动态顺序表的初始化
void SeqListInit(SL* ps)//初始化函数实现
{
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = 0;
}

🎤这里为什么要用指针呢?😮😮😮
🎤我们结合主函数给大家解释一下

int main()
{
    SL s;
	SeqListInit(&s); 
}
return 0;

因为传过去的形参只能在这个函数块(SeqListInit)内有效,改变形参对实参没有影响(我们的最终目的就是通过改变形参来改变实参),所以这个用指针的方式,一荣俱荣,一损俱损。😉😉😉


2.3顺序表的空间检查及扩容

🎤动态存储与静态存储不一样的地方就是它能及时的扩容
大致思路:先判断在数据个数与容量是否相同,如果相同就进行扩容,这里就用realloc来动态申请内存详细解释也写在代码块的注释里

void SeqListCheckCapacity(SL* ps)//增容函数实现--检查空间是否够用
{
	// 满了就要扩容
	if (ps->size == ps->capacity)//当数据个数与容量相同就是满了,进入下面扩容,else就不用写了,空间够了直接过了就行
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//如果newcapacity等于零就先给四个空间,如果不等于零就二倍一下
		SQDataType* tmp = (SQDataType*)realloc(ps->a, newcapacity * sizeof(SQDataType));//两倍两倍的扩容
		if (tmp == NULL)//检查是否失败--失败
		{
			printf("realloc fail\n");
			exit(-1);//结束程序
		}
		else//成功
		{
			ps->a = tmp;
			ps->capacity = newcapacity;
		}
	}
}

2.4增删查改等接口实现

🎤顺序表基本上就是为了数据在内存中存起来,并实现增删查改

2.4.1尾部插入

🎤大致思路:尾部插入就是在顺序表中有效数据的后面增加一个新的数据,空间不够就扩容。下面用图片来帮助大家理解一下:
在这里插入图片描述

void SeqListPushBack(SL* ps, SQDataType x)//尾部插入函数实现
{
	SeqListCheckCapacity(ps);             //调用增容函数
	ps->a[ps->size] = x;                  //把x放在在下标为size的位置上
	ps->size++;                           //有效数据的个数增加1
}

2.4.2头部插入

🎤大致思路:头部插入和尾部插入刚好相反,基本思想就是先将顺序表中有效数据向后移动一位,然后将需要插入的新数据放在第一位上,空间不够就扩容。 如图:
在这里插入图片描述

void SeqListPushFront(SL* ps, SQDataType x)//头部插入函数实现
{
	SeqListCheckCapacity(ps);              //调用增容函数
	int end = ps->size - 1;
	while (end >= 0)                       //结束条件
	{
		ps->a[end + 1] = ps->a[end];
		--end;
	}
	ps->a[0] = x;
	ps->size++;
}

2.4.3尾部删除

🎤大致思路:尾部删除也是比较简单的,就直接找到顺序表中有效数据的最后一个,并删除(置零)就ok啦,洒洒水的啦。 如图:
在这里插入图片描述

void SeqListPopBack(SL* ps)//尾部删除函数实现
{
	assert(ps->size > 0);//assert断言,判断顺序表中是否有数据,大于零就继续,等于零就报错,如果没有一个数据,还删个锤子
	ps->a[ps->size - 1] = 0;
	ps->size--;
}

2.4.4头部删除

🎤大致思路:头部删除就是把顺序表中有效数据从左到右一次向左移动一位,直接覆盖就行了, 如图:
在这里插入图片描述

void SeqListPopFront(SL* ps)//头部删除函数实现
{
	assert(ps->size > 0);//同上尾部删除函数实现相同
	int start = 1;
	while (start < ps->size)
	{
		ps->a[start - 1] = ps->a[start];
		++start;
	}
	ps->size--;
}

2.4.5任意位置插入

🎤大致思路:
第一步: 需要我们要确认我们需要插入的位置是否在有效数据位置之内,没在其中还不如头插和尾插…
第二步: pos是我们要插入位置的数组下标,它后面的数据从右到左依次向右移动一位
第三步: 将我们新增数据x放到我们需要放到的位置即可,空间不够就扩容。
如图:
在这里插入图片描述

void SeqListInsert(SL* ps, int pos, SQDataType x)//任意位置插入函数实现---pos待插入位置的下标
{
	assert(pos <= ps->size);                     //不能在其他地方插入,只能在ps->size内插入
	SeqListCheckCapacity(ps);                    //调用增容函数
	int end = ps->size-1;                        //end代表最后一个有效数据的下标
	while (end >= pos-1)
	{
		ps->a[end+1] = ps->a[end];
		--end;
	}
	ps->a[pos] = x;
	ps->size++;
}

2.4.6任意位置删除

🎤大致思路:
第一步: 需要我们要确认我们需要删除的位置是否在有效数据位置之内;
第二步: 找到待删除数据pos的下一位(start),直接从左到右向前覆盖,每覆盖一次++start,直到(start>=size)为止。
如图:
在这里插入图片描述

void SeqListErase(SL* ps, int pos)//任意位置删除函数实现---pos待删除位置的下标
{
	assert(pos < ps->size);//同上任意位置插入函数实现一样
	int start = pos+1;
	while (start < ps->size)
	{
		ps->a[start-1] = ps->a[start];
		++start;
	}
	ps->size--;
}

2.4.7查找数据

🎤大致思路:查找的话大家可以自己试一试更高效率的查找算法(二分查找肯定不行的,我们数据可能存放无序数据),这里我就不写那么清楚了,直接一手暴力循环查找

int SeqListFind(SL* ps, SQDataType x)//查找函数实现
{
	for (int i = 0; i < ps->size; ++i)
	{
		if (ps->a[i] == x)
		{
			return i;
		}
	}
	return -1;
}

2.4.8更改数据

🎤大致思路:更改的话太简单了,直接找到需要更改数据的下标,然后把新数据放进去就好了,搜易贼呀😆😆😆

void SeqListModity(SL* ps, int pos, SQDataType x)//更改函数实现
{
	assert(pos < ps->size);
	ps->a[pos] = x;
}

2.5打印数据

🎤大致思路:传址调用吧, 节省空间,然后同样也是一个循环就行了

void SeqListPrint(SL* ps)//打印数据函数实现
{
	for (int i = 0; i < ps->size; ++i)//i<ps->size 终止条件,所有数据打印完为止
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

总代码

Test.c

#define _CRT_SECURE_NO_WARNINGS 1//针对vs2022,scanf报错的举措
#include "SeqList.h"
void menu()
{
	printf("**********************************************\n");
	printf("1.尾插数据, 2.头插数据\n");
	printf("3.尾删数据, 4.头删数据\n");
	printf("5.任插数据, 6.任删数据\n");
	printf("7.查找数据, 8.更改数据\n");
	printf("9.打印数据,-1.退出\n");
	printf("**********************************************\n");
	printf("请输入你要操作的选项:");
}

int main()
{
	SL s;
	SeqListInit(&s);
	int option = 0;
	int x = 0;
	int pos = 0;
	int rot = 0;
	while (option != -1)
	{
		menu();
		scanf("%d", &option);
		switch (option)
		{
		case 1:
			printf("请输入你要在尾部插入的数据,以-1结束\n");
			do {
				scanf("%d", &x);
				if (x != -1)
				{
					SeqListPushBack(&s, x);
				}
			} while (x != -1);
			break;
		case 2:
			printf("请输入你要在头部插入的数据,以-1结束\n");
			do {
				scanf("%d", &x);
				if (x != -1)
				{
					SeqListPushFront(&s, x);
				}
			} while (x != -1);
			break;
		case 3:
			printf("正在删除尾部最后一个数据\n");
			SeqListPopBack(&s);
			printf("删除成功\n");
			printf("现在你的数据为:");
			SeqListPrint(&s);
			break;
		case 4:
			printf("正在删除头部最后一个数据\n");
			SeqListPopFront(&s);
			printf("删除成功\n");
			printf("现在你的数据为:");
			SeqListPrint(&s);
			break;
		case 5:
			printf("请输入需要插入数组中位置的下标与数据,以空格隔开:>");
			scanf("%d %d", &pos, &x);
			SeqListInsert(&s, pos,x);
			printf("插入成功\n");
			printf("现在你的数据为:");
			SeqListPrint(&s);
			break;
		case 6:
			printf("请输入需要删除数组中位置的下标:>");
			scanf("%d", &pos);
			SeqListErase(&s,pos);
			printf("删除成功\n");
			printf("现在你的数据为:");
			SeqListPrint(&s);
			break;
		case 7:
			printf("请输入需要查找的数据:>");
			scanf("%d", &x);
			rot=SeqListFind(&s, x);
			if (rot != -1)
			{
				printf("找到了!在数组中下标为:%d的位置\n", rot);
			}
			else
			{
				printf("暂无此数据\n");
			}
			break;
		case 8:
			printf("请输入需要更改的位置下标与数据,以空格隔开:>");
			scanf("%d %d", &pos,&x);
			SeqListModity(&s,  pos,  x);
			printf("更改成功\n");
			printf("现在你的数据为:");
			SeqListPrint(&s);
			break;
		case 9:
			SeqListPrint(&s);
			break;
		default:
			break;
		}
	}

	SeqListDestory(&s);//释放空间函数

	return 0;
}

SeqList.h

#pragma once                                  //防止头文件被重复的包含

#include<stdio.h>
#include<string.h>
#include <stdlib.h>
#include <assert.h>

typedef int SQDataType;                       // 增强程序可维护性
typedef struct SeqList
{
	SQDataType* a;                            //指向动态内存开辟的数组
	int size;                                 // 有效数据的个数
	int capacity;                             // 容量空间的大小
}SL;

// 增删查改等接口函数
void SeqListInit(SL* ps);                     //初始化函数
void SeqListPrint(SL* ps);                    //打印数据函数
void SeqListDestory(SL* ps);                  //释放空间函数

// 尾插 头插 尾删 头删
void SeqListPushBack(SL* ps, SQDataType x);   //尾部插入函数
void SeqListPushFront(SL* ps, SQDataType x);  //头部插入函数
void SeqListPopBack(SL* ps);                  //尾部删除函数
void SeqListPopFront(SL* ps);                 //头部删除函数
void SeqListInsert(SL* ps, int pos, SQDataType x);//任意位置插入函数
void SeqListErase(SL* ps, int pos);           //任意位置删除函数

//查  改
int SeqListFind(SL* ps, SQDataType x);        //查找函数
void SeqListModity(SL* ps, int pos, SQDataType x);//更改函数

源文件SeqList.c

#include "SeqList.h"
//初始化函数实现
void SeqListInit(SL* ps)
{
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = 0;
}

//释放空间函数实现
void SeqListDestory(SL* ps)
{
	free(ps->a);
	ps->a = NULL;
	ps->capacity = ps->size = 0;
}

//增容函数实现--检查空间是否够用
void SeqListCheckCapacity(SL* ps)
{
	// 满了就要扩容
	if (ps->size == ps->capacity)//当数据个数与容量相同就是满了,所以扩容
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//如果newcapacity等于零就先给四个空间,如果不等于零就二倍一下
		SQDataType* tmp = (SQDataType*)realloc(ps->a, newcapacity * sizeof(SQDataType));//两倍两倍的扩容
		if (tmp == NULL)//检查是否失败--失败
		{
			printf("realloc fail\n");
			exit(-1);//结束程序
		}
		else//成功
		{
			ps->a = tmp;
			ps->capacity = newcapacity;
		}
	}
}

// 尾插 头插 尾删 头删
//尾部插入函数实现
void SeqListPushBack(SL* ps, SQDataType x)
{
	SeqListCheckCapacity(ps);//调用增容函数
	ps->a[ps->size] = x;
	ps->size++;
}
//头部插入函数实现
void SeqListPushFront(SL* ps, SQDataType x)
{
	SeqListCheckCapacity(ps);//调用增容函数
	int end = ps->size - 1;
	while (end >= 0)
	{
		ps->a[end + 1] = ps->a[end];
		--end;
	}
	ps->a[0] = x;
	ps->size++;
}
//尾部删除函数实现
void SeqListPopBack(SL* ps)
{
	assert(ps->size > 0);//assert断言,判断顺序表中是否有数据,大于零就继续,等于零就报错,如果没有一个数据,还删个锤子
	ps->a[ps->size - 1] = 0;
	ps->size--;
}
//头部删除函数实现
void SeqListPopFront(SL* ps)
{
	assert(ps->size > 0);//同上尾部删除函数实现相同
	int start = 1;
	while (start < ps->size)
	{
		ps->a[start - 1] = ps->a[start];
		++start;
	}
	ps->size--;
}
//任插  任删
//任意位置插入函数实现---pos待插入位置的下标
void SeqListInsert(SL* ps, int pos, SQDataType x)
{
	assert(pos <= ps->size);//不能在其他地方插入,只能在ps->size内插入
	SeqListCheckCapacity(ps);//调用增容函数
	int end = ps->size-1;
	while (end >= pos-1)
	{
		ps->a[end+1] = ps->a[end];
		--end;
	}
	ps->a[pos] = x;
	ps->size++;
}
//任意位置删除函数实现---pos待删除位置的下标
void SeqListErase(SL* ps, int pos)
{
	assert(pos < ps->size);//同上任意位置插入函数实现一样
	int start = pos+1;
	while (start < ps->size)
	{
		ps->a[start-1] = ps->a[start];
		++start;
	}
	ps->size--;
}
//查 改
//查找函数实现
int SeqListFind(SL* ps, SQDataType x)
{
	for (int i = 0; i < ps->size; ++i)
	{
		if (ps->a[i] == x)
		{
			return i;
		}
	}

	return -1;
}
//更改函数实现
void SeqListModity(SL* ps, int pos, SQDataType x)
{
	assert(pos < ps->size);
	ps->a[pos] = x;
}

//打印数据函数实现
void SeqListPrint(SL* ps)
{
	for (int i = 0; i < ps->size; ++i)//i<ps->size 终止条件,所有数据打印完为止
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

结语

大家学到这里,对于顺序表已经掌握的差不多了🍭🍭🍭 后续我会持续更新数据结构的学习总结,大家感兴趣可以点赞关注一下喽😜😜😜
在这里插入图片描述

  • 27
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 20
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

#唐解元

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值