线性表是最基本、最简单、也是最常用的一种数据结构。线性表(linear list)是数据结构的一种,一个线性表是n个具有相同特性的数据元素的有限序列。
线性表在逻辑上是线性结构,就是连续的一条直线。但是在物理上并不一定是线序的,线性表在物理上存储是,通常以数组和链式结构的形式存储。
概念:使用定长数组存储元素
静态顺序表的缺陷:空间少了不够用,空间多了浪费。
//静态顺序表
typedef int SLDataType;
#define N 7
typedef struct SeqList
{
SLDataType a[N]; //定长数组
int size; //有效数据个数
}SL;
使用动态开辟的数组存储
//动态顺序表
typedef struct SeqList
{
SLDataType* a; //指向动态数组的指针
int size; //有效数据个数
int capacity; //空间容量
};
笔者的注释部分含有草稿笔记,不清楚
Seqlist.h
部分
#pragma once //防止头文件被多次包含
#include
#include
#include
#include
//动态顺序表
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a; //动态指针数组
int size; // 顺序表中有效的个数
int capacity; //顺序表当前的空间大小
}SL; //简化结构体名称
//初始化顺序表与销毁
void SLInit(SL* ps);
void SLDestory(SL* ps);
//头插 尾插 尾删 头删
void SLPushBack(SL* ps, SLDataType x);
void SLPushFront(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
void SLPopFront(SL* ps);
//打印SL
void SLprint(SL* ps);
//判断SL是否为空
bool SLIsEmpty(SL* ps);
//在指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x);
//删除指定位置的数据
void SLErase(SL* ps, int pos);
//查找数据
bool SLFind(SL* ps, SLDataType x);
SeqList.c
部分
#include"SeqList.h"
void SLInit(SL* ps) //传过来顺序表,对它进行初始化
{
ps->a = NULL;
ps->size = ps->capacity = 0;
}
void SLDestory(SL* ps)
{
if(ps->a)
free(ps->a);
ps->a = NULL;
ps->size = ps->capacity = 0;
}
void SLCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
//空间已经满了,需要扩容
int newCapcity = ps->capacity == 0 ? 2 : 2 * ps->capacity;
SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapcity * sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc fail.\n");
//return 1;//这个会有弱警告,但不妨碍.warning C4098: “SLCheckCapacity”:“void”函数返回值.因此所以用exit(1);退出
exit(1);
}
ps->a = tmp;
ps->capacity = newCapcity;
}
}
void SLPushBack(SL* ps, SLDataType x)
{
//暴力的方式
assert(ps); //assert(ps != NUll);简化一下,()内放ps就行了
//if (ps == NULL)
//{
// return;
//}
//(1)空间足够,在后面尾插
//(2)空间不够,需要扩容
SLCheckCapacity(ps);
//直接插入数据
ps->a[ps->size++] = x;
}
//头插
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
//判断空间是否足够,不够就扩容
SLCheckCapacity(ps);
//数据后移
for (size_t i = ps->size; i > 0; i--)
{
ps->a[i] = ps->a[i - 1];
}
ps->a[0] = x;
ps->size++;
}
//尾删
void SLPopBack(SL* ps)
{
assert(ps);
assert(!SLIsEmpty(ps));//非空
ps->size--;//ps->a[ps->size-1]=0;这样显然没有必要,直接让他有效个数减一就行了,即ps->size--;
}
//头删
void SLPopFront(SL* ps)
{
assert(ps);
assert(!SLIsEmpty(ps));
//让后面的数据往前挪动一位
for (size_t i = 0; i < ps->size-1; i++)
{
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
//打印
void SLprint(SL* ps)
{
for (size_t i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
//判断SL是否为空
bool SLIsEmpty(SL* ps)
{
assert(ps);
return ps->size == 0;
}
//在指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x) //对pos限制范围
{
assert(ps);
// 对pos限制范围 边界0是可以的
assert(pos >= 0 && pos<=ps->size);
//扩容
SLCheckCapacity(ps);
//把pos位置及以后的数据往后挪动一位
方法一
//for (size_t i = ps->size; i > pos; i--)
//{
// ps->a[i] = ps->a[i - 1];
//}
方法一
for (size_t i = ps->size-1; i > pos-1; i--)
{
ps->a[i+1] = ps->a[i];
}
ps->a[pos] = x;
ps->size++;
}
//删除指定位置的数据
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(!SLIsEmpty(ps));
assert(pos >= 0 && pos < ps->size);
for (size_t i = pos; i < ps->size-1; i++)
{
//最后一个进来的i是ps->size-2, ps->a[ps->size-2]=ps->a[ps->size-1]
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
//查找数据
bool SLFind(SL* ps, SLDataType x)
{
assert(ps);
for (size_t i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
//找到了
return true;
}
}
}
test.c
部分
#include"SeqList.h"
void SLtest()
{
SL sl;
SLInit(&sl);
//一下顺序表的操作
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);//1,2,3,4
SLprint(&sl);
//头插
SLPushFront(&sl, 5);//5,1,2,3,4
SLPushFront(&sl, 6);//6,5,1,2,3,4
SLPushFront(&sl, 7);//7,6,5,1,2,3,4
SLprint(&sl);
//尾删
SLPopBack(&sl); //7,6,5,1,2,3
SLprint(&sl);
SLPopBack(&sl); //7,6,5,1,2
SLprint(&sl);
//头删
SLPopFront(&sl); //6,5,1,2
SLprint(&sl);
SLPopFront(&sl); //5,1,2
SLprint(&sl);
//SLPopFront(&sl); //1,2
//SLprint(&sl);
//SLPopFront(&sl); //2
//SLprint(&sl);
到这里就没有了
//SLPopFront(&sl); //NULL
//SLprint(&sl);
测试bool SLIsEmpty(SL* ps) 出现报错 assert(!SLIsEmpty(ps));成功
//SLPopFront(&sl); //1,2
//SLprint(&sl); //Assertion failed: !SLIsEmpty(ps),
//在指定位置之前插入数据
//SLInsert(&sl, 100, 12);//报错,范围太大
SLInsert(&sl, 2, 12); //5, 1, 12,2
SLprint(&sl);
SLInsert(&sl, sl.size, 9);//5, 1, 12,2,9
SLprint(&sl);
//删除指定位置的数据 pos指的是索引
//SLErase(&sl, 20);//报错,assert成功
//SLprint(&sl);
SLErase(&sl, 0);//1, 12,2,9
SLprint(&sl);
SLErase(&sl, 0);//12,2,9
SLprint(&sl);
SLErase(&sl, 2);//12,2
SLprint(&sl);
bool RetFind = SLFind(&sl, 2);
if (RetFind)
{
printf("找到了\n");
}
else
printf("没找到\n");
//以上顺序表的操作
SLDestory(&sl);
}
int main()
{
SLtest();
return 0;
}
顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口
(1)我们增删改查接口放在头文件SeqList.h
中,源文件放SeqList.c
实现接口函数,test.c
测试代码
SeqList.h
引用我们需要的头文件,
#include
,#include
,#include
,#include
(2)我们再SeqList.h
声明函数void SLInit(SL* ps) ;
,在SelList.c
定义函数void SLInit(SL* ps) { }
(3)在test.c
测试中,我们使用指针方式调用成员,所以函数的声明中,都传递的地址,例如void SLInit(SL* ps);
//动态顺序表
typedef int SLDataType; //SL = struct SeqList 方便使用
typedef struct SeqList
{
SLDataType* a;
int size; // 顺序表中有效的个数
int capacity; //顺序表当前的空间大小
}SL; //简化结构体名称,方便后续书写与修改
(1) 初始化顺序表
ps->a=NULL 指针数组置空
ps->size = ps->capacity = 0 有效个数,空间置空
void SLInit(SL* ps) //传过来顺序表,对它进行初始化
{
ps->a = NULL;
ps->size = ps->capacity = 0;
}
(2) 销毁顺序表
有初始化就有销毁,使用
free()
释放开辟的动态数组,首先保证当前数组a不为空,所以进行一次if
判断.
若不释放空间,会出现野指针的情况.
void SLDestory(SL* ps)
{
if(ps->a)
free(ps->a);
ps->a = NULL;
ps->size = ps->capacity = 0;
}
判断顺序表是否有足够的空间,若空间足够,直接插入,否则扩容.而每一次增加数据,增加空间,若频繁扩容,会降低程序的性能.于是有人提出,采用原数据1.5倍或者2倍扩容.
我们采用realloc扩容,可以进行大小调整,给顺序表扩容,数据类型是(SLDataType*),realloc的第二参数是size
,我们拿空间*数据类型大小,newCapcity * sizeof(SLDataType)
newCapcity判断capacity是否为0,若为0,初始化为2,这样避免0乘2后依旧为0
判断是否能扩容成功,用tmp接收创建的内容.判断tmp是否为空,然后就可以接收了,ps->a = tmp;ps->capacity = newCapcity;
void SLCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
//空间已经满了,需要扩容
//三目表达式,如果capacity为0,令它空间为2,不够的话,2倍递增
int newCapcity = ps->capacity == 0 ? 2 : 2 * ps->capacity;
SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapcity * sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc fail.\n");
exit(1);//给状态码1,退出
}
ps->a = tmp;
ps->capacity = newCapcity;
}
}
(1) 尾插
判断ps是否为空,传入NULL就坏了,我们采用assert,较暴力简洁.
if(ps->NULL){
return ;
}
if判断不简洁
void SLPushBack(SL* ps, SLDataType x)
{
//暴力的方式
assert(ps); //assert(ps != NUll);简化一下,()内放ps就行了
//扩容
SLCheckCapacity(ps);
//直接插入数据
//ps->a[ps->size] = x;
//size++;
ps->a[ps->size++] = x;//上面两行代码使用后置++,简化
}
(2) 头插
for循环实现从下标1开始数据后移,下标0插入x,
ps->a[0] = x;
,记得size++,不然后续数据都是下标1的元素,ps->size++;
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
//判断空间是否足够,不够就扩容
SLCheckCapacity(ps);
//数据后移
for (size_t i = ps->size; i > 0; i--)
{
ps->a[i] = ps->a[i - 1];
}
ps->a[0] = x;
ps->size++;
}
顺序表为空,顺序表没有一个有效的数据,那么有效数据size的值是0,return ps->size == 0;
返回’0’或’1’
bool SLIsEmpty(SL* ps)
{
assert(ps);
return ps->size == 0;
}
(1) 尾删
void SLPopBack(SL* ps)
{
assert(ps);
assert(!SLIsEmpty(ps));//非空
ps->size--;//这样显然没有必要,直接让他有效个数减一就行了 ps->a[ps->size - 1] = 0;
}
(2) 头删
采用for直接覆盖的方式
void SLPopFront(SL* ps)
{
assert(ps);
assert(!SLIsEmpty(ps));//不能一直删
//让后面的数据往前挪动一位
for (size_t i = 0; i < ps->size-1; i++)//最后一个有效数据的下标是ps->size-1
{
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
这个函数早就在前面使用了,由于思路的先后顺序,提前写了头尾增删,而这个函数的实现也比简单,也可用过调试查看,所有笔者就把它顺位靠后了
void SLprint(SL* ps)
{
for (size_t i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
(1) 定点插入
void SLInsert(SL* ps, int pos, SLDataType x) //对pos限制范围
{
assert(ps);
// 对pos限制范围 边界0是可以的
assert(pos >= 0 && pos<=ps->size);
//扩容
SLCheckCapacity(ps);
//把pos位置及以后的数据往后挪动一位
方法一
//for (size_t i = ps->size; i > pos; i--)
//{
// ps->a[i] = ps->a[i - 1];
//}
方法二
for (size_t i = ps->size-1; i > pos-1; i--)
{
ps->a[i+1] = ps->a[i];
}
ps->a[pos] = x;
ps->size++;
}
(2)定点删除
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(!SLIsEmpty(ps));
assert(pos >= 0 && pos < ps->size);
for (size_t i = pos; i < ps->size-1; i++)
{
//最后一个进来的i是ps->size-2, ps->a[ps->size-2]=ps->a[ps->size-1]
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
bool SLFind(SL* ps, SLDataType x)
{
assert(ps);
for (size_t i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
//找到了
return true;
}
}
}
一定要亲自调式!!!
一定要亲自调式!!!
一定要亲自调式!!!
在test.c
函数中反复调试,打印.
test.c
在目录3.0 完整代码那边
这里贴张警报图,大家也去调试调试
终于学会了,看了三遍视频,第一遍是懵的,第二遍理解了,第三遍结合视频写笔记.
之后手写一遍,错误不断,那就不断结合课件调试