🌠先赞后看,不足指正!🌠
🎈这将对我有很大的帮助!🎈
📝所属专栏:数据结构与算法
📝阿哇旭的主页:Awas-Home page
目录
[1. 什么是顺序表?](#1. 什么是顺序表?)
[2. 顺序表的分类](#2. 顺序表的分类)
[2.1 顺序表和数组的区别](#2.1 顺序表和数组的区别)
[2.2 静态顺序表](#2.2 静态顺序表)
[2.3 动态顺序表](#2.3 动态顺序表)
[3. 顺序表的功能](#3. 顺序表的功能)
[4. 顺序表的定义](#4. 顺序表的定义)
[5. 具体功能实现](#5. 具体功能实现)
[5.1 初始化顺序表](#5.1 初始化顺序表)
[5.2 打印顺序表数据](#5.2 打印顺序表数据)
[5.3 对顺序表插入/删除数据](#5.3 对顺序表插入/删除数据)
[5.3.1 检查剩余空间大小](#5.3.1 检查剩余空间大小)
[5.3.2 尾部插入数据](#5.3.2 尾部插入数据)
[5.3.3 尾部删除数据](#5.3.3 尾部删除数据)
[5.3.4 头部插入数据](#5.3.4 头部插入数据)
[5.3.5 头部删除数据](#5.3.5 头部删除数据)
[5.4 指定位置插入/删除数据](#5.4 指定位置插入/删除数据)
[5.4.1 指定位置插入数据](#5.4.1 指定位置插入数据)
[5.4.2 指定位置删除数据](#5.4.2 指定位置删除数据)
[5.5 指定位置修改数据](#5.5 指定位置修改数据)
[5.6 查找顺序表数据](#5.6 查找顺序表数据)
[5.7 销毁顺序表](#5.7 销毁顺序表)
[6. 完整代码](#6. 完整代码)
[6.1 Seqlist.h](#6.1 Seqlist.h)
[6.2 Seqlist.c](#6.2 Seqlist.c)
[7. 结语](#7. 结语)
引言
今天,我们将接触一个全新的概念"数据结构"。顾名思义,数据结构是由"数据"和"结构"两词组合而来,接下来,我将为大家带来数据结构的初阶知识。本篇文章的主题是顺序表。
那么,话不多说,我们一起来看看吧!
1. 什么是顺序表?
顺序表是一种线性表的存储结构,它是由一组物理地址连续 的存储单元(通常是数组)依次存储线性表中的元素。顺序表中的元素在内存中是连续存储的,通过元素在数组中的下标来访问和操作元素。
顺序表的优点是访问元素的时间复杂度为O(1),即可以通过下标直接访问元素,操作效率高;缺点是插入和删除元素时需要移动其他元素,时间复杂度为O(n),效率较低。
2. 顺序表的分类
2.1 顺序表和数组的区别
顺序表不同于数组,顺序表的底层结构是数组,对数组的封装,实现了对数据进行增删改查等接口。
2.2 静态顺序表
cpp
#define N 100
//静态顺序表
struct SeqList
{
int arr[N];//定长数组
int size;//顺序表当前有效的数据个数
};
- 静态顺序表缺陷:空间给少了不够用,给多了造成空间浪费
2.3 动态顺序表
cpp
//动态顺序表
typedef struct SeqList
{
SLDataType* arr;
int size;//有效的数据个数
int capacity;//空间大小
}SL;
- 相较于静态顺序表,动态顺序表有个明显的优点------可以动态调整内存空间的大小(增容),它解决了静态顺序表的固定大小和内存浪费的问题。
3. 顺序表的功能
顺序表的大致功能如下:
- 初始化顺序表
- 打印顺序表数据
- 对顺序表末尾插入/删除数据
- 对顺序表开头插入/删除数据
- 对顺序表指定位置插入/删除数据
- 对顺序表指定位置修改数据
- 查找顺序表数据
- 销毁顺序表
4. 顺序表的定义
首先,我们需要定义动态顺序表的数据类型SLDataType和结构体SeqList。动态顺序表使用数组arr存储数据,size 表示当前有效数据个数,capacity表示当前容量大小。
cpp
typedef int SLDataType;//定义动态顺序表的数据类型
//动态顺序表结构体
typedef struct SeqList
{
SLDataType* arr;//指向存储数据的数组
int size;//当前有效数据个数
int capacity;//当前容量大小
}SL;
5. 具体功能实现
5.1 初始化顺序表
在进行初始化的时候,将顺序表的数组指针初始化为NULL,表示当前没有分配内存空间。同时,将顺序表的大小(size) 和**容量(capacity)**都初始化为0,表示当前顺序表中没有元素。
cpp
//顺序表的初始化
void SLIint(SL* ps)
{
assert(ps);//断言
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
5.2 打印顺序表数据
打印数据前,先判断顺序表是否为空。若不为空,使用循环遍历顺序表中的元素,依次打印出来即可。
cpp
//打印顺序表元素
void SLprint(SL* ps)
{
assert(ps);//断言
if (ps->size == 0)
{
printf("顺序表为空!\n");
return;
}
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
5.3 对顺序表插入/删除数据
5.3.1 检查剩余空间大小
对顺序表插入数据前,我们要检查顺序表的剩余空间大小是否足够插入新数据、是否需要扩容。
cpp
//检查剩余空间大小
void SLCheckCapacity(SL* ps)
{
//插入数据之前先看空间够不够
if (ps->capacity == ps->size)
{
//申请空间
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;//三目操作符
SLDataType* ptr = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));//需要申请多大的空间
if (ptr == NULL)
{
perror("realloc fail!");
exit(1);//直接退出程序,不再继续执行
}
//空间申请成功
ps->arr = ptr;
ps->capacity = newCapacity;
}
}
5.3.2 尾部插入数据
对顺序表尾部插入数据,再插入数据前先检查剩余空间大小是否足够插入新数据。
cpp
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);//断言
SLCheckCapacity(ps);//检查剩余空间大小
ps->arr[ps->size] = x;//尾部插入数据
ps->size++;//有效数据个数+1
}
5.3.3 尾部删除数据
同理,删除顺序表尾部的一个元素后,记得将有效数据个数-1。(注:在删除数据前要检查顺序表是否为空。
cpp
//尾删
void SLPopBack(SL* ps)
{
assert(ps);//断言
assert(ps->size);//顺序表不为空
ps->size--;//删除数据,有效的数据个数减少
}
5.3.4 头部插入数据
检查剩余空间的大小,使用循环将顺序表中的已有数据整体往后移一位,为新数据腾出位置。
cpp
//头插
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];
}
ps->arr[0] = x;
ps->size++;//增加数据,有效的数据个数增加
}
5.3.5 头部删除数据
与尾部删除数据不同,头部删除数据只需要将原有数据覆盖即可。
cpp
//头删
void SLPopFront(SL* ps)
{
assert(ps);//断言
assert(ps->size);//顺序表不为空
//顺序表不为空
for (int i = 0; i < ps->size; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;//删除数据,有效的数据个数减少
}
5.4 指定位置插入/删除数据
5.4.1 指定位置插入数据
和头插、尾插一样,指定位置插入数据前也需要判断是否需要扩容。
cpp
//指定位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);//断言
assert(ps->size);//顺序表不为空
assert(pos >= 0 && pos <= ps->size);//检查下标pos的合法性
SLCheckCapacity(ps);//扩容
for (int i = ps->size; i > pos; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;
ps->size++;//增加数据,有效的数据个数增多
}
5.4.2 指定位置删除数据
与前边的删除数据大致相同,只是指定删除数据需要依次覆盖。
cpp
//指定位置删除数据
void SLErase(SL* ps, int pos)
{
assert(ps);//断言
assert(ps->size);//顺序表不为空
assert(pos >= 0 && pos < ps->size);//检查下标pos的合法性
for (int i = pos; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];//将pos位置后面的数据依次向前移动一位
}
ps->size--;//删除数据,有效的数据个数减少
}
5.5 指定位置修改数据
我们可以通过指定下标或者查找指定值的下标来修改任意位置的值。
cpp
//指定位置修改数据
void SLmodify(SL* ps, int pos, SLDataType x)
{
assert(ps);//断言
assert(ps->size);//顺序表不为空
assert(pos >= 0 && pos < ps->size);//检查下标pos的合法性
ps->arr[pos] = x;//修改下标为pos所对应的数据
}
5.6 查找顺序表数据
根据输入参数,在顺序表中查找指定的值并返回其下标,若未找到则返回-1。
cpp
//查找数据
int SLFind(SL* ps, SLDataType x)
{
assert(ps);//断言
assert(ps->size);//顺序表不为空
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
return i;//查找到,返回该数据在数组中的下标值
}
}
return -1;//未查找到,返回值-1
}
5.7 销毁顺序表
在顺序表使用完毕后,应该及时回收顺序表所占用的内存空间,以避免内存泄漏和提高系统的内存利用率。
cpp
//顺序表的销毁
void SLDestroy(SL* ps)
{
if (ps->arr != NULL) //检查顺序表的数据存储空间是否存在
{
free(ps->arr); //释放顺序表的数据存储空间
}
ps->arr = NULL; //将数据存储空间指针置为NULL
ps->size = ps->capacity = 0; //将顺序表的大小和容量设为0
}
6. 完整代码
使用多个文件来组织和管理项目。将代码分散到多个文件中有助于提高代码的可读性、可维护性和可扩展性。
6.1 Seqlist.h
cpp
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDataType;//定义动态顺序表的数据类型
//动态顺序表结构体
typedef struct SeqList
{
SLDataType* arr;//指向存储数据的数组
int size;//当前有效数据个数
int capacity;//当前容量大小
}SL;
//typedef struct SeqList SL;
void SLIint(SL* ps);//初始化顺序表
void SLprint(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);//指定删除数据
void SLmodify(SL* ps, int pos, SLDataType x);//修改数据
int SLFind(SL* ps, SLDataType x);//查找顺序表数据
void SLDestory(SL* ps);//销毁顺序表,回收空间
6.2 Seqlist.c
cpp
#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"
//顺序表的初始化
void SLIint(SL* ps)
{
assert(ps);//断言
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
//打印顺序表元素
void SLprint(SL* ps)
{
assert(ps);//断言
if (ps->size == 0)
{
printf("顺序表为空!\n");
return;
}
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);//断言
SLCheckCapacity(ps);//检查剩余空间大小
ps->arr[ps->size] = x;//尾部插入数据
ps->size++;//有效数据个数+1
}
//尾删
void SLPopBack(SL* ps)
{
assert(ps);//断言
assert(ps->size);//顺序表不为空
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];
}
ps->arr[0] = x;
ps->size++;//增加数据,有效的数据个数增加
}
//头删
void SLPopFront(SL* ps)
{
assert(ps);//断言
assert(ps->size);//顺序表不为空
//顺序表不为空
for (int i = 0; i < ps->size; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;//删除数据,有效的数据个数减少
}
//指定位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);//断言
assert(ps->size);//顺序表不为空
assert(pos >= 0 && pos <= ps->size);//检查下标pos的合法性
SLCheckCapacity(ps);//扩容
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(ps->size);//顺序表不为空
assert(pos >= 0 && pos < ps->size);//检查下标pos的合法性
for (int i = pos; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];//将pos位置后面的数据依次向前移动一位
}
ps->size--;//删除数据,有效的数据个数减少
}
//指定位置修改数据
void SLmodify(SL* ps, int pos, SLDataType x)
{
assert(ps);//断言
assert(ps->size);//顺序表不为空
assert(pos >= 0 && pos < ps->size);//检查下标pos的合法性
ps->arr[pos] = x;//修改下标为pos所对应的数据
}
//查找数据
int SLFind(SL* ps, SLDataType x)
{
assert(ps);//断言
assert(ps->size);//顺序表不为空
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
return i;//查找到,返回该数据在数组中的下标值
}
}
return -1;//未查找到,返回值-1
}
//顺序表的销毁
void SLDestroy(SL* ps)
{
if (ps->arr != NULL) //检查顺序表的数据存储空间是否存在
{
free(ps->arr); //释放顺序表的数据存储空间
}
ps->arr = NULL; //将数据存储空间指针置为NULL
ps->size = ps->capacity = 0; //将顺序表的大小和容量设为0
}
7. 结语
希望这篇文章对大家有所帮助,如果你有任何问题和建议,欢迎在评论区留言,这将对我有很大的帮助。
完结!咻~