目录
顺序表- Seqlist -> sequence 顺序 list 表
顺序表的概念
问题与解答
顺序表的分类
静态顺序表
动态顺序表
问题与解答(递进式)
动态顺序表的实现
尾插
头插
尾删
头删
指定位置插入
指定位置删除
销毁
总结
前言:线性表是具有相同特性的一类数据结构的集合。它是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串......
线性表在逻辑上是线性结构,指的是人为想出来的;但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
当然,当学习到数据结构时,需要具备结构体、指针(一级、二级)、内存动态管理这些知识。
顺序表- Seqlist -> sequence 顺序 list 表
顺序表的概念
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数据存储。
总结:
- 逻辑结构:一定是线性。因为这是认为想出来的。
- 物理结构:一定也是线性的。因为它的底层结构是数组。
问题与解答
问题:既然顺序表的底层是数组,那么顺序表和数组的区别是什么?
回答:顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口。
就像同样是饭馆,高级餐厅就会对菜品进行包装,加上一些饰品,就把菜名换一个名称。同样的顺序表其实就是数组多加上了一些功能。
顺序表的分类
我们实现动态顺序表需要创建三个文件:Seqlist.h 头文件(声明结构和方法), Seqlist.c 源文件(实现声明的方法), test.c 测试文件
静态顺序表
概念:使用定长数组存储元素
//静态顺序表
#define N 100
typedef int SLdatatype;
把静态顺序表重命名,创建的时候更容易
typedef struct SeqList
{
SLdatatype arr[N]; //定长数组
int size; // 有效的个数
}SL;
typedef struct SeqList SL; // 单独给他拉出来命名也可以
我们需要使用typedef对顺序表的名称进行重定义,这样使用起来更方便。
使用define定义常量N,这样只要改上面的数字,就可以进行更改。
静态顺序表的缺陷:空间给少了不够用,给多了造成空间浪费。
动态顺序表
动态顺序表需要自己手动申请空间。
问题与解答(递进式)
问题1:动态顺序表需要申请空间,那么一次申请一个空间可以吗?
回答:一次增加一个,这是增容频繁,会导致程序效率低下。
问题2:既然一次申请一个空间会效率低下,那么一次多申请空间呢?
回答:一次多申请空间,增加大空间,会造成空间浪费。
问题3:那么应该怎么申请空间呢?
回答3:通常,增容是按倍数增加的,如2倍。既能避免频繁增容,又能保证最大程度的减少空间的浪费。
总结:
一般增容是按2倍增容的,要插入的数据跟增量是成正相关的。
动态顺序表的实现
Seqlist.h
typedef int SLdatatype;
typedef struct SeList
{
SLdatatype* arr;
int size; //有效的个数
int capacity; // 容量大小
}SL;
构建动态顺序表。
接下来是要对动态顺序表进行初始化
Seqlist.h
//初始化
void SLInit(SL* ps);
实现函数声明
Seqlist.c(源文件)
#include "SeqList.h"
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
测试函数
test.c
#include "SeqList.h"
void SLTest()
{
SL s1;
SLInit(&s1);
}
int main()
{
SLTest();
return 0;
}
我们可以看到初始化成功了,并且值也成功给了s1。
想必大家看到这里肯定会有疑惑,为什么这初始化函数里面传的是&s1的地址,而不是s1,
因为s1是传值,这是把值传给Seqlist.c函数中的形参 SL ps(这个写法是值传递的写法),虽然接收了值,但是它还会再内存中重新开辟一块地址,所以值传递,是不会改变原有的s1.
大家可以在自己的IDE里面试一下。
尾插
接下来是尾插
Seqlist.h
//尾插
void SLPushBack(SL* ps, SLdatatype x);
Seqlist.c(源文件)
void SLPushBack(SL* ps, SLdatatype x)
{
assert(ps);
int Newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//三目表达式
if (ps->size == ps->capacity)
{
int Newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLdatatype* newarr = (SLdatatype*)realloc(ps->arr, Newcapacity*sizeof(SLdatatype));
if (newarr == NULL)
{
perror("realloc");
exit(1);
}
ps->arr = newarr;
ps->capacity = Newcapacity;
}
首先你要判断传过来的是不能为NULL的,否则是不能进行尾插的。
三目表达式,如果等于0的话,初始化的值给4个字节。因为我们一开始初始化顺序表的时,给capacity值为0,这样不管扩容几倍,相乘都为0,所以这边要有这样的操作。
接下来,如果空间不够,给顺序表里的数组扩容(不会的查文档realloc函数的使用),当判断扩容成功时,把开辟的空间给数组,然后再对容量进行改变。
最后,进行插入尾插。
经行测试函数
test.c
#include "SeqList.h"
void SLTest()
{
SL s1;
SLInit(&s1);
SLPushBack(&s2, 1);
SLPushBack(&s2, 2);
SLPushBack(&s2, 5);
SLPushBack(&s2, 10);
SLPushBack(&s2, 12);
}
int main()
{
SLTest();
return 0;
}
我们可以看到代码的返回值为0,这样子就是说明这个尾插函数是没有问题的。
从监视窗口也可以看到,当空间不够时,也可以扩容。
头插
接下来是头插
Seqlist.h
//头插
void SLPushFront(SL* ps, SLdatatype x);
Seqlist.c(源文件)
void SLPushFront(SL* ps, SLdatatype x)
{
assert(ps);
CheckSpace(ps);
//插入
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[0] = x;
//有效的个数增加
++ps->size;
}
头插,从后往前覆盖,然后把首个位置空出来,最后把需要插入的元素放进去,把有效的个数往后挪一个。
因为扩容这段代码,在头插 和 尾插 里面都有,所以我们给他封装成一个函数。这样要使用就比较方便。
void CheckSpace(SL* ps)
{
//容量不够,要扩容
if (ps->size == ps->capacity)
{
int Newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLdatatype* newarr = (SLdatatype*)realloc(ps->arr, Newcapacity * sizeof(SLdatatype));
if (newarr == NULL)
{
perror("realloc");
exit(1);
}
ps->arr = newarr;
ps->capacity = Newcapacity;
}
}
经行测试函数
test.c
#include "SeqList.h"
void SLTest()
{
SL s1;
SLInit(&s1);
SLPushFront(&s1, 2);
SLPushFront(&s1, 3);
SLPushFront(&s1, 4);
SLPushFront(&s1, 5);
SLPushFront(&s1, 6);
}
int main()
{
SLTest();
return 0;
}
尾删
接下来是尾删
Seqlist.h
//尾删
void SLPopBack(SL* ps);
Seqlist.c(源文件)
void SLPopBack(SL* ps)
{
assert(ps && ps->size);
--ps->size;
}
当要尾删时,你传入的不能是NULL空指针 和 0个有效个数(里面需要有元素)。
因为 --ps->size;使有效的个数往前减一个,往后在增加的时候会直接覆盖。
这边写一个打印函数给大家理解一下
void SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
}
经行测试函数
test.c
#include "SeqList.h"
void SLTest()
{
SL s1;
SLInit(&s1);
SLPushFront(&s1, 2);
SLPushFront(&s1, 3);
SLPushFront(&s1, 4);
SLPushFront(&s1, 5);
SLPushFront(&s1, 6);
SLPopBack(&s1); //6 5 4 3
SLPrint(&s1);
printf("\n");
SLPopBack(&s1); //6 5 4
SLPrint(&s1);
}
int main()
{
SLTest();
return 0;
}
头删
接下来是头删
Seqlist.h
//头删
void SLPopFront(SL* ps);
Seqlist.c(源文件)
void SLPopFront(SL* ps)
{
assert(ps && ps->size);
for (int i = 0; i < ps->size-1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
--ps->size; //有效个数减1
}
当进行头删的时候,需要把元素往前挪。然后把有效的个数减少一个。
进行测试函数
test.c
#include "SeqList.h"
void SLTest()
{
SL s1;
SLInit(&s1);
SLPushFront(&s1, 2);
SLPushFront(&s1, 3);
SLPushFront(&s1, 4);
SLPushFront(&s1, 5);
SLPushFront(&s1, 6);
SLPopFront(&s1);
SLPrint(&s1);
printf("\n");
SLPopFront(&s1);
SLPrint(&s1);
printf("\n");
}
int main()
{
SLTest();
return 0;
}
指定位置插入
接下来是指定位置插入
Seqlist.h
//指定位置插入
void SLInsert(SL* ps, int pos, SLdatatype x);
Seqlist.c(源文件)
void SLInsert(SL* ps, int pos, SLdatatype x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
for (int i = ps->size; i > pos; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;
//增加
++ps->size;
}
当你要插入元素时,你传的pos是要大于等于0(相当于头插),然后要小于等于有效个数(相当于尾插) 。最后有效个数加一。
进行测试函数
test.c
#include "SeqList.h"
void SLTest()
{
SL s1;
SLInit(&s1);
SLPushBack(&s2, 1);
SLPushBack(&s2, 2);
SLPushBack(&s2, 5);
SLInsert(&s2, 2, 10); //1 2 10 5
}
int main()
{
SLTest();
return 0;
}
指定位置删除
接下来是 指定位置删除
Seqlist.h
//指定位置删除
void SLErase(SL* ps, int pos);
Seqlist.c(源文件)
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;
}
指定位置删除,把pos之后的数据往前挪,pos要在size之前,因为在size这个位置没有数据,是不能够删除的。最后再把有效个数减去一个。
进行测试函数
test.c
#include "SeqList.h"
void SLTest()
{
SL s1;
SLInit(&s1);
SLPushBack(&s2, 1);
SLPushBack(&s2, 2);
SLPushBack(&s2, 5);
SLErase(&s2, 1); //1 5
}
int main()
{
SLTest();
return 0;
}
销毁
最后是进行销毁动态开辟的内存
Seqlist.h
//销毁
void SLDestroy(SL* ps);
Seqlist.c(源文件)
void SLDestroy(SL* ps)
{
if (ps->arr)
{
free(ps->arr);
}
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
需要判断是否数组初始化过,不然释放空间会出错。
进行测试函数
test.c
#include "SeqList.h"
void SLTest()
{
SL s1;
SLInit(&s1);
SLPushBack(&s2, 1);
SLPushBack(&s2, 2);
SLPushBack(&s2, 5);
SLInsert(&s2, 2, 10);
SLDestroy(&s1);
}
int main()
{
SLTest();
return 0;
}
最后我们也是成功的看到动态空间被释放掉了。
正所谓有借有还,再借不难。
总结
编写代码过程中要勤测试,避免写出⼤量代码后再测试⽽导致出现问题,问题定位⽆从下 ⼿。
最后来看一下整体的代码
Seqlist.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//静态顺序表
#define N 100
typedef int SLdatatype;
//把静态顺序表重命名,创建的时候更容易
//typedef struct SeqList
//{
// SLdatatype arr[N];
// int size; // 有效的个数
//}SL;
//typedef struct SeqList SL; // 单独给他拉出来命名也可以
//动态顺序表
typedef struct SeList
{
SLdatatype* arr;
int size; //有效的个数
int capacity; // 容量大小
}SL;
//初始化
void SLInit(SL* ps);
//销毁
void SLDestroy(SL* ps);
//尾插
void SLPushBack(SL* ps, SLdatatype x);
//头插
void SLPushFront(SL* ps, SLdatatype x);
//打印
void SLPrint(SL* ps);
//尾删
void SLPopBack(SL* ps);
//头删
void SLPopFront(SL* ps);
//查找
int SLFindData(SL* ps, SLdatatype x);
//指定位置插入
void SLInsert(SL* ps, int pos, SLdatatype x);
//指定位置删除
void SLErase(SL* ps, int pos);
Seqlist.c(源文件)
#define _CRT_SECURE_NO_WARNINGS
#include "SeqList.h"
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
void SLDestroy(SL* ps)
{
if (ps->arr)
{
free(ps->arr);
}
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
void CheckSpace(SL* ps)
{
//容量不够,要扩容
if (ps->size == ps->capacity)
{
int Newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLdatatype* newarr = (SLdatatype*)realloc(ps->arr, Newcapacity * sizeof(SLdatatype));
if (newarr == NULL)
{
perror("realloc");
exit(1);
}
ps->arr = newarr;
ps->capacity = Newcapacity;
}
}
void SLPushBack(SL* ps, SLdatatype x)
{
assert(ps);
CheckSpace(ps);
//插入
ps->arr[ps->size++] = x;
}
void SLPushFront(SL* ps, SLdatatype x)
{
assert(ps);
CheckSpace(ps);
//插入
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[0] = x;
//有效的个数增加
++ps->size;
}
void SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
}
void SLPopBack(SL* ps)
{
assert(ps && ps->size);
--ps->size;
}
void SLPopFront(SL* ps)
{
assert(ps && ps->size);
for (int i = 0; i < ps->size-1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
--ps->size;
}
int SLFindData(SL* ps, SLdatatype x)
{
assert(ps && ps->size);
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
return i;
}
}
return -1;
}
void SLInsert(SL* ps, int pos, SLdatatype x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
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;
}
test.c测试文件
#define _CRT_SECURE_NO_WARNINGS
#include "SeqList.h"
void SLTest()
{
SL s1;
SLInit(&s1);
/*SLPushBack(&s1, 1);
SLPushBack(&s1, 2);
SLPushBack(&s1, 5);
SLPushBack(&s1, 10);
SLPushBack(&s1, 12);*/
//SLPushFront(&s1, 2);
/*SLPrint(&s1);
printf("\n");*/
//SLPushFront(&s1, 3);
/*SLPrint(&s1);
printf("\n");*/
/*SLPushFront(&s1, 4);
SLPushFront(&s1, 5);
SLPushFront(&s1, 6);
*/
/*SLPopBack(&s1);
SLPrint(&s1);
printf("\n");
SLPopBack(&s1);
SLPrint(&s1);*/
/*SLPopFront(&s1);
SLPrint(&s1);
printf("\n");
SLPopFront(&s1);
SLPrint(&s1);
printf("\n");*/
SLPushBack(&s1, 1);
SLPushBack(&s1, 2);
SLPushBack(&s1, 5);
SLErase(&s1, 1);
SLPrint(&s1);
printf("\n");
SLDestroy(&s1);
}
void SLTest2()
{
SL s2;
SLInit(&s2);
SLPushBack(&s2, 1);
SLPushBack(&s2, 2);
SLPushBack(&s2, 5);
SLPushBack(&s2, 10);
SLPushBack(&s2, 12);
SLPrint(&s2);
printf("\n");
int ret = SLFindData(&s2, 5);
if (ret > 0)
{
printf("找到了\n");
}
else
{
printf("未找到\n");
}
SLInsert(&s2, 2, 1);
SLPrint(&s2);
printf("\n");
SLErase(&s2, 4);
SLPrint(&s2);
printf("\n");
}
int main()
{
SLTest();
//SLTest2();
return 0;
}