目录
一.顺序表
1.1 概念与结构
概念:⽤⼀段物理地址连续的存储单元依次存储数据元素的线性结构。
特点:逻辑结构是连续的
物理结构是连续的(顺序表的底层是数组)
虽说顺序表的底层是数组,但二者还是存在一定区别
以下为一个形象的例子
所谓的米其林餐厅,其实就是苍蝇馆子豪华版,同样的所谓顺序表可以理解为数组promax,数组能做的事情他都可以做到,并且顺序表拥有着米其林餐馆的“高端定制服务”——你可以自己增加你想实现的操作。
1.2分类
1.2.1 静态顺序表
同时,注意第一行的灵魂操作——typedef int SLDatatype:
顺序表只是一种数据结构,这也就意味着其并不像数组一样只能存储单一的元素,因此我们使用SLDataType来取代int,这样在未来如果需要改变其储存的类型时候,只需要改上这一行就好了
1.2.2 动态顺序表
静态顺序表在空间上的使用是静态的,但是在如今的大数据时代,信息不停变化的,那么我们如果想要实现动态的申请空间大小不断扩容,就需要实现动态顺序表
1.3 动态顺序表实现
在实现动态顺序表过程中大致分为以下三个部分
1.3.1 Seqlist.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//动态顺序表
typedef int SLDataType;
typedef struct sqlist
{
SLDataType* arr;
int size;//有效数据个数
int capacity;//空间大小
}SL;
//顺序表初始化
void SLInit(SL* ps);
//顺序表的销毁
void SLDestroy(SL* ps);
//顺序表的打印
void SLPrint(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);
//顺序表的查找
void SLFind(SL* ps, SLDataType x);
这是我们定义的结构体主体,首行是一个数组指针,这里不再定义一个定长数组而是使用一个数组指针,这样可以通过realloc进行灵活的扩容,并且定义有效数据个数与空间大小,这些结构体成员在后续论述中经常用到,所以这里单独再截个图.
1.3.2 Seqlish.c
1.3.2.1 顺序表的初始化与销毁
//顺序表初始化
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
//顺序表销毁
void SLDestroy(SL* ps)
{
if (ps->arr != NULL)
{
free(ps->arr);
}
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
1.3.2.2 封装是否需要扩容的函数
在后续头插与尾插中都需要检测是否需要扩容,因此我们将这个操作1单独封装,也可以是头插尾插的操作更加清晰明了
//封装检测是否需要扩容的函数
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;
}
}
注意:因此在malloc使用之时需要注意三点:
1.如果初始空间就为0,该怎么办? 这里我们创建新的变量newcapacity,利用三目操作符进行判断,如果capacity为0则则赋值为4,否则赋值为2*ps->capacity即对空间进行双倍扩容
2.在申请内存时候,注意由于我们顺序表的内部贮存的类型为我们自定义的SLDatatype,所以扩容大小应为(newcapacity*sizeof(SLDatatype)),并且将返回类型强制转换为SLDataType*,并使用对应类型的指针tmp接收
3.realloc的扩容用概率会失败,如果失败则会返回NULL,所以在使用之前需要写一个判断条件,防止产生空指针
1.3.2.3 打印顺序表
在动态顺序表的实现过程中,经常需要对实现的操作进行调试,因此首先实现顺序的打印可以帮助我们更加方便的观察程序是否出错
//打印顺序表
void SLPrint(SL ps)
{
for (int i = 0; i < ps.size; i++)
{
printf("%d ", ps.arr[i]);
}
printf("\n");
}
注:在实现下述操作时,第一步总是利用assert进行断言,增加代码代码的健壮性
1.3.2.4 头插与尾插
//尾删
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size);
ps->size--;
}
//头删
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size);
for (int i = 1; i < ps->size; i++)
{
ps->arr[i - 1] = ps->arr[i];
}
ps->size--;
}
注意:在头插尾插之后,千万不要忘记对ps->size进行操作!头删尾删/同样如此
1.3.2.5 头删与尾删
//尾删
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size);
ps->size--;
}
//头删
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size);
for (int i = 1; i < ps->size; i++)
{
ps->arr[i - 1] = ps->arr[i];
}
ps->size--;
}
1.3.2.6 指定位置的插入与删除
//指定位置插入
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos>=0 && pos<=ps->size);
void SLCheckcapacity(SL * 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--;
}
1.3.3 test.c
#include"sqlist.h"
void sltest01()
{
sl sl;
slinit(&sl); //&sl传地址,用sl会报错
slpushfront(&sl, 1);
slpushfront(&sl, 2);
slpushfront(&sl, 3);
slpushfront(&sl, 4);
slpushfront(&sl, 5);
slfind(&sl, 100);
slprint(sl);
sldestroy(&sl);
}
int main()
{
sltest01()
return 0;
}