为什么我们要使用顺序表:
我们先来看两张图:
想像如果在一篇草场上放我们自己的羊,当我们某天想要找一只叫肖恩的羊拿来做烤串,但是在草场上羊是杂乱无序的,我们想要找到一个特定的羊是非常困难的。而当我们像图二一样集中起来排好序,让每个羊都有自己的位置顺序排列,给肖恩标记号,这样当我们想要去找这只羊的时候只需要到它的编号的羊圈里找就可以很快的找到。
内存也是如此类似的,我们知道数组在内存中是连续的,而顺序表的底层就是数组这样我们就可以用这种特性使我们的程序效率大大提高。
如何创建一个顺序表:
顺序表的基本功能有增删查改,在VS中我们先创建一个.h文件用来存放头文件和函数的声明,一个SeqList.c用来实现增删查改等内容,一个test.c文件用来测试我们写的函数。
这样我们就将我们就在这三个项目中实现我们的顺序表。
静态顺序表:
#define N 100
struct SeqList
{
int arr[N];
int size;//有效数据个数
};
静态顺序表的大小为我们自己指定的,但这种方式的缺陷就是不能灵活的调整大小,给少了不够用,给多了用不完。因此在正常情况下,我们使用动态顺序表。
动态顺序表:
定义:
typedef int SLDataType;//方便后续类型的替换
typedef struct SeqList
{
SLDataType* arr;
int size;//有效的数据个数
int capacity;//空间大小
}SL;
//也可以用typedef struct SeqList SL;
初始化:
我们把初始化函数声明为:
void SLInit(SL*ps);
接下来我们把里面的内容初始化
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
判断空间并扩容:
初始化好了之后我们就可以进行增删查改的操作,但我们最开是没有给顺序表空间,因此我们在增加的时候每一次都需要判断顺序表是否有足够的空间给我们使用。因此我们把判断是否有空间和自动增加空间的功能封装成一个函数,在一个每次增加元素的时候调用这个函数即可。
我们将判断空间的函数声明为
void SLCheckcapacity(SL* ps)
{
if (ps->capacity == ps->size)
{
//申请空间
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;
}
}
这段代码中使用newcapacity来决定要扩容多大的空间(如果原空间为0我们给四个字节,如果原本有空间我们就扩大两倍。同时定义一个临时指针来接受扩容的空间(为了防止扩容失败后,还把我们原本空间中的数据释放)。
尾插:
如图我们知道size指向的位置就是我们要进行尾插的位置,我们只需要在size的位置插入后让size++就可以。
void SLPushBack(SL* ps,SLDataType x)
{
assert(ps);//等价于if(ps!=NULL)
SLCheckcapacity(ps);//插入之前检查空间够不够
ps->arr[ps->size++] = x;
}
ps是我们的顺序表的指针,x是我们要尾插的元素。
头插:
头插就是在size=0的位置插入一个元素,如下图,我们把size前面的数据使用for循环每个都向后移动一位让最后一个元素到size的位置,size++,再在0的位置插入我们要插入的元素即可。
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++;
}
尾删:
因为顺序表内容访问是size之前的数据,在尾删中如果我们让size--让size向前移动一个位置就可以实现,而减少的那个内容我们不需要去覆盖什么值,因为我们不会访问它,并且我们进行更改增加等操作时会有新值覆盖它,对这些操作都不会有任何影响。
void SLPopBack(SL* ps)
{
assert(ps);
//顺序表是否为空
assert(ps->size);
--ps->size;
}
头删:
如下图,头删就是删除第一个元素,那么我们让第一个元素后面的元素向前移动一个,那么第二个元素覆盖第一个依次类推就可以实现头删。因为数据少了一个size--。
void SLPopFront(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
指定位置插入:
如上图我们在下标为2的位置插入数据时我们只需要让下标为2以及后面的元素向后移动一位,同时size++就可以腾出下标2的空间来插入数据。
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);//必须在顺序表数据范围内
//插入数据:空间够不够
SLCheckcapacity(ps);
for (int i = ps->size; i > pos; i--)
{
ps->arr[i] = ps -> arr[i - 1];
}
ps->arr[pos] = x;
ps->size++;
}
指定位置删除:
与头删思路一样,我们只需要让删除的元素的位置后面的元素向前移动一位后,size--即可。
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
for (int i = pos; i < ps->size - 1; i++)
{
ps->arr[i] = ps -> arr[i + 1];
}
ps->size--;
}
查找:
查找的方式其实就是用遍历来找到与我们要找的元素一样的位置
int SLFind(SL* ps, SLDataType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
return i;
}
}
return -1;
}
那么我们的增删查改的功能就差不多了。
我们可以通过调试或者是写一个打印代码来测试
打印代码:
void SLPrint(SL s)
{
for (int i = 0; i < s.size; i++)
{
printf("%d ", s.arr[i]);
}
printf("\n");
}
顺序表的销毁:
别忘了我们用完之后置空把内存要还回去哦
void SLDestroy(SL* ps)
{
if (ps->arr)
{
free(ps->arr);
}
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
测试一下:
源代码:
SeqList.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//
//静态顺序表
//#define N 100
//struct SeqList
//{
// int arr[N];
// int size;//有效数据个数
//};
//动态顺序表
typedef int SLDataType;//方便后续类型的替换
typedef struct SeqList
{
SLDataType* arr;
int size;//有效的数据个数
int capacity;//空间大小
}SL;
//typedef struct SeqList SL;
//顺序表初始化
void SLInit(SL*ps);
//顺序表的销毁
void SLDestroy(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);
//顺序表的查找
int SLFind(SL* ps, SLDataType x);
//顺序表的打印
void SLPrint(SL s);
SeqList.c
#include "SeqList.h"
//================================================
// 顺序表的初始化
//================================================
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
//================================================
// 判断空间够不够 //
// ===============================================
void SLCheckcapacity(SL* ps)
{
if (ps->capacity == ps->size)
{
//申请空间
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);//等价于if(ps!=NULL)
//插入之前检查空间够不够
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--)
{
ps->arr[i] = ps->arr[i-1];//arr[1]=arr[0]
}
ps->arr[0] = x;
ps->size++;
}
//==================================================
// 顺序表的尾删 //
//==================================================
void SLPopBack(SL* ps)
{
assert(ps);
//顺序表是否为空
assert(ps->size);
--ps->size;
}
//================================================
// 顺序表的头删 //
// ===============================================
void SLPopFront(SL* ps)
{
assert(ps);
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);
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(pos >= 0 && pos < ps->size);
for (int i = pos; i < ps->size - 1; i++)
{
ps->arr[i] = ps -> arr[i + 1];
}
ps->size--;
}
//================================================
// 顺序表的打印 //
//================================================
void SLPrint(SL s)
{
for (int i = 0; i < s.size; i++)
{
printf("%d ", s.arr[i]);
}
printf("\n");
}
//================================================
// 顺序表的查找 //
//================================================
int SLFind(SL* ps, SLDataType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
return i;
}
}
return -1;
}
//================================================
// 顺序表的销毁 //
//================================================
void SLDestroy(SL* ps)
{
if (ps->arr)
{
free(ps->arr);
}
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
//================================================
test.c
#include "SeqList.h"
void SLTest01()
{
SL sl;
SLInit(&sl);
//增删查改
//测试尾插
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPushFront(&sl, 0);
SLPushFront(&sl, -1);
SLPushFront(&sl, -2);
SLPushFront(&sl, -3);
printf("原本的顺序表sl是\n");
SLPrint(sl);
printf("尾删后的的顺序表sl是\n");
SLPopBack(&sl);
SLPrint(sl);
printf("头删后的顺序表sl是\n");
SLPopFront(&sl);
SLPrint(sl);
SLDestroy(&sl);
}
void SLTest02()
{
SL sq;
SLInit(&sq);
SLPushBack(&sq, 1);
SLPushBack(&sq, 2);
SLPushBack(&sq, 3);
SLPushBack(&sq, 4);
printf("原本的顺序表sq是\n");
SLPrint(sq);
printf("指定下标位置3插入0后的顺序表sq是\n");
SLInsert(&sq, 3, 0);
SLPrint(sq);
printf("指定下标位置2删除后的顺序表sq是\n");
SLErase(&sq, 2);
SLPrint(sq);
printf("寻找数字2\n");
int find = SLFind(&sq, 2);
if (find < 0)
{
printf("没有找到\n");
}
else printf("找到了,下标位置%d\n",find);
SLDestroy(&sq);
}
int main()
{
SLTest01();
SLTest02();
return 0;
}