一.内容总结
本篇博客会详细讲解什么是顺序表,如何实现顺序表的各种功能(增加,查找,删除,插入等等)。
二.什么是顺序表
顺序表是线性表的一种,其物理结构上与逻辑结构上都是线性的。其底层结构是数组,通过对数组的封装,实现了常用的增删查改等接口
三.实现顺序表
3.1 静态顺序表与动态顺序表
数组有固定大小的,也可以通过动态内存管理来调整数组空间的大小,就分为静态顺序表和动态顺序表,考虑到对空间的利用率,一般使用动态顺序表
3.2 顺序表结构
3.2.1 静态顺序表
是由空间大小不变的数组封装完成的,只需知道其有效数据个数即可实现增删查改等功能
typedef int SLDataType;
#define N 7
typedef struct SeqList
{
SLDataType arr[N];
int size;
}SL;
3.2.2 动态顺序表
因为我们可以调节其空间的大小,所以不仅需要有效数据的个数,还需要七当前空间的大小,一般我们每次调整其空间的大小至其1.5倍或2倍。
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* arr; //动态内存管理
int size; //有效数据个数
int capacity;//空间大小
}SL;
3.3实现讲解
我们要设置三个文件,一个头文件,进行函数声明,一个实现功能(函数)的文件,一个测试文件
3.3.1 头文件
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#define N 7
//typedef int SLDataType;
//typedef struct SeqList
//{
// SLDataType arr[N];
// int size;
//}SL;
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* arr; //动态内存管理
int size; //有效数据个数
int capacity;//空间大小
}SL;
//顺序表的初始化与销毁
void SLInit(SL* ps);
void SLDestroy(SL* ps);
//顺序表的头部插入与尾部插入
void SLPushFront(SL* ps,SLDataType x);
void SLPushBack(SL* ps,SLDataType x);
//顺序表的头部删除与尾部删除
void SLPopFront(SL* ps);
void SLPopBack(SL* ps);
//在当前位置的前一个位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x);
//删除当前位置数据
void SLErase(SL* ps, int pos);
//查找数据
void SLFind(SL* ps, SLDataType x);
//打印顺序表->进行测试功能
void SLPrint(SL* ps);
3.3.2 功能实现文件
1.顺序表的初始化
实现代码
#include "SeqList.h"
//顺序表的初始化
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
测试代码
#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"
void SLTest01()
{
//顺序表的初始化
SL sl; //--sl是我创建的一个结构体
SLInit(&sl); //将sl的地址传过去,若只传sl,就是传值调用,在SeqList中的
//只是一份sl的临时拷贝,不能初始化sl
}
int main()
{
SLTest01();
return 0;
}
2.顺序表的打印
要检验写的功能是否能成功实现,最直观的方法就i时将其打印出来
在SeqList.c中
//顺序表的打印
void SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d", ps->arr[i]);
}
//注意,这里用%d只是因为SLDataType暂时是int
printf("\n");
}
3.顺序表的尾部插入
尾部插入分为两种情况 1.空间已经满了,我们需要进行空间的扩大,但是我们待会要写的在指定位置前插入数据也可能是在空间已满的条件下,代码会重复,所以我们直接封装一个函数判断空间是否足够,不够扩大
2和3是同一种--空间未满,直接在arr[size-1]位置插入数据即可
检查空间函数
//检查空间是否充足
void SLCheckCapacity(SL* ps)
{
if(ps->capacity==ps->size)
{
int newCapacity = ps->capacity == 0 ? 4 : 2 * (ps->capacity);
//初始化时capacity是0 若我们直接写 capacity*=2; 那空间大小就是0*2==0;
//所以用一个三目运算符判断capacity是否为0
//若为0 --- 多给点 给个4 ,若不是0,正常扩大2倍(也可以是1.5倍/1.25倍数等等等等)
//开始扩大空间
SLDataType* tmp = (SLDataType*)realloc(ps->arr, sizeof(ps) * newCapacity);
//记得写sizeof(ps),要不然只扩大4个比特
if (tmp == NULL)//防止扩大失败返回空指针
{
perror("realloc");
exit(1);
}
ps->arr = tmp;
ps->capacity = newCapacity;
}
}
尾插函数
void SLPushBack(SL* ps,SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
//空间经过检查足够,开始尾部插入
ps->arr[ps->size] = x;
ps->size++;
}
4.顺序表的头部插入
注意:为防止覆盖数据,这里的数据必须从最后一个开始向后移动
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];
//最后:arr[1] = arr[0]
}
ps->arr[0] = x;
ps->size++;
}
5.顺序表的头部删除
首先,这里的删除并不是你把要删除的位置的数据改成一个不用的值,而是让这里的数据被前面覆盖。从最左边开始,全部数据向左挪动
void SLPopFront(SL* ps)
{
assert(ps);
//既然要删除,就不能传空数组
assert(ps->size);
for (int i = 0; i < ps->size - 1; i++)
{
//arr[size-2] = arr[size-1];
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
6.顺序表的尾部删除
明确删除的定义(我不去使用这个数据了),就可以看出非常简单,只需要把size--即可
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size);
ps->size--;
}
7.实现在当前位置的前一个位置插入数据
只需要把pos位置及其后面的数据全部向后挪动一位即可
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
//注意对pos也有要求,pos必需>=0 &&pos<=size,可以向等,因为空间是足够的,经过检查后,capacity>size
assert(pos >= 0 && pos <= ps->size);
for (int i =ps->size;i>pos;i-- )
{
//arr[pos+1] = arr[pos];
ps->arr[i] = ps->arr[i-1];
}
ps->arr[pos] = x;
ps->size++;
}
8.实现删除指定位置数据
只需要将删除位置之后的数据向前挪动一位,将要删除的数据覆盖就行了
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);//这次不能等于size了,size位置并没有数据
for (int i = pos; i<ps->size-1;i++)
{
ps->arr[i] = ps->arr[i+1];
//arr[pos] = arr[pos+1]
}
ps->size--;
}
9.查找数据
很简单,只要遍历数组找到相同的即可
void SLFind(SL* ps, SLDataType x)
{
assert(ps);
int b = -1;
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
printf("%d ", i);
b = 1;
}
}
if (b == -1)
{
printf("找不到该数据\n");
}
}
10.销毁顺序表
只需要释放空间,指针设为空,size、capacity归0即可
void SLDestroy(SL* ps)
{
assert(ps);
//但如果arr还没有被分配空间(初始时),就无需释放空间了
if (ps->arr != NULL)
{
free(ps->arr);
}
ps->arr = NULL;
ps->capacity = ps->size = 0;
//ps是函数的临时变量,系统自动销毁,那边传过来的的不是指针的地址,而是&sl,即结构体的地址
}
3.4 全部代码呈现
1.头文件
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#define N 7
//typedef int SLDataType;
//typedef struct SeqList
//{
// SLDataType arr[N];
// int size;
//}SL;
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* arr; //动态内存管理
int size; //有效数据个数
int capacity;//空间大小
}SL;
//顺序表的初始化与销毁
void SLInit(SL* ps);
void SLDestroy(SL* ps);
//顺序表的头部插入与尾部插入
void SLPushFront(SL* ps,SLDataType x);
void SLPushBack(SL* ps,SLDataType x);
//顺序表的头部删除与尾部删除
void SLPopFront(SL* ps);
void SLPopBack(SL* ps);
//在当前位置的前一个位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x);
//删除当前位置数据
void SLErase(SL* ps, int pos);
//查找数据
void SLFind(SL* ps, SLDataType x);
//打印顺序表->进行测试功能
void SLPrint(SL* ps);
2.功能实现文件
#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"
//顺序表的初始化
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
//检查空间是否充足
void SLCheckCapacity(SL* ps)
{
if(ps->capacity==ps->size)
{
int newCapacity = ps->capacity == 0 ? 4 : 2 * (ps->capacity);
//初始化时capacity是0 若我们直接写 capacity*=2; 那空间大小就是0*2==0;
//所以用一个三目运算符判断capacity是否为0
//若为0 --- 多给点 给个4 ,若不是0,正常扩大2倍(也可以是1.5倍/1.25倍数等等等等)
//开始扩大空间
SLDataType* tmp = (SLDataType*)realloc(ps->arr, sizeof(ps) * newCapacity);
//记得写sizeof(ps),要不然只扩大4个比特
if (tmp == NULL)//防止扩大失败返回空指针
{
perror("realloc");
exit(1);
}
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];
//最后:arr[1] = arr[0]
}
ps->arr[0] = x;
ps->size++;
}
void SLPushBack(SL* ps,SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
//空间经过检查足够,开始尾部插入
ps->arr[ps->size] = x;
ps->size++;
}
//顺序表的头部删除与尾部删除
void SLPopFront(SL* ps)
{
assert(ps);
//既然要删除,就不能传空数组
assert(ps->size);
for (int i = 0; i < ps->size - 1; i++)
{
//arr[size-2] = arr[size-1];
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size);
ps->size--;
}
//在当前位置的前一个位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
//注意对pos也有要求,pos必需>=0 &&pos<=size,可以向等,因为空间是足够的,经过检查后,capacity>size
assert(pos >= 0 && pos <= ps->size);
for (int i =ps->size;i>pos;i-- )
{
//arr[pos+1] = arr[pos];
ps->arr[i] = ps->arr[i-1];
}
ps->arr[pos] = x;
ps->size++;
}
//删除当前位置数据
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);//这次不能等于size了,size位置并没有数据
for (int i = pos; i<ps->size-1;i++)
{
ps->arr[i] = ps->arr[i+1];
//arr[pos] = arr[pos+1]
}
ps->size--;
}
//查找数据
void SLFind(SL* ps, SLDataType x)
{
assert(ps);
int b = -1;
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
printf("%d ", i);
b = 1;
}
}
if (b == -1)
{
printf("找不到该数据\n");
}
}
void SLDestroy(SL* ps)
{
assert(ps);
//但如果arr还没有被分配空间(初始时),就无需释放空间了
if (ps->arr != NULL)
{
free(ps->arr);
}
ps->arr = NULL;
ps->capacity = ps->size = 0;
//ps是函数的临时变量,系统自动销毁,那边传过来的的不是指针的地址,而是&sl,即结构体的地址
}
//顺序表的打印
void SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
//注意,这里用%d只是因为SLDataType暂时是int
printf("\n");
}
3.测试文件
#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"
void SLTest01()
{
//顺序表的初始化
SL sl; //--sl是我创建的一个结构体
SLInit(&sl); //将sl的地址传过去,若只传sl,就是传值调用,在SeqList中的
//只是一份sl的临时拷贝,不能初始化sl
/*SLPrint(&sl);*/
SLPushBack(&sl,1);
SLPushBack(&sl,2);
SLPushBack(&sl,3);
SLPrint(&sl);
SLPushFront(&sl, 6);
SLPushFront(&sl, 5);
SLPushFront(&sl, 4);
SLPrint(&sl);
/*SLPopFront(&sl);
SLPopFront(&sl);
SLPopBack(&sl);*/
/*SLErase(&sl, 4);*/
/*SLPrint(&sl);*/
SLFind(&sl, 100);
SLDestroy(&sl);
}
int main()
{
SLTest01();
return 0;
}