文章目录
1.线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使
用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,
线性表在物理上存储时,通常以数组和链式结构的形式存储。
2.顺序表
2.1顺序表的定义及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存
储。在数组上完成数据的增删查改。
顺序表一般可以分为:
2.1.1 静态顺序表:使用定长数组存储元素。
2.1.2 动态顺序表:使用动态开辟的数组存储。
小结
静态的顺序表的缺点很明显:空间给小了不够用,给大了会浪费,不实用。所以我们通常使用动态的顺序表,灵活性更高。
2.2顺序表的接口实现
新建一个工程(动态顺序表)
SeqList.h(顺序表的类型定义、接口函数声明、引用的头文件)
SeqList.c(顺序表接口函数的实现)
test.c(主函数、测试顺序表各个接口功能)
完整的代码放在后面(包括测试代码),这里就不会展示测试的效果图。大家可以自己别敲边按测试代码测试。注释会写的很详细的,么么😙
注意:size是数据个数,看做下标的话,他是最后一个数据的下一个位置
2.2.1顺序表初始化
//顺序表初始化
void SLInit(SL* psl)
{
assert(psl);//断言一下防止传空指针进来
psl->a = NULL;//初始顺序表为空
psl->size = 0; //初始数据个数为0
psl->capacity = 0;//初始空间容量为0,也可以给初始空间值
}
2.2.2顺序表打印
//顺序表打印
void SLPrint(const SL* psl)
{
assert(psl);//断言一下防止传空指针进来
//这里也不用去判断顺序表为不为空,因为为空就不会打印。
for (int i = 0; i < psl->size; i++)//打印顺序表
{
printf("%d ", psl->a[i]);
}
printf("\n");
}
2.2.3检查空间,如果满了,进行增容
注意:若传入realloc的指针为空指针(NULL),则realloc函数的作用相当于malloc函数。
//检查空间,如果满了,进行增容
void SLCheckCapacity(SL* psl)
{
assert(psl);//断言一下防止传空指针进来
if (psl->size == psl->capacity)//检查空间,满了就增容
{
int newcapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;//这里用了三字母词,就是如果capacity为0则赋一个4如果不为空则扩容自身的2倍
SLDataType* tmp = (SLDataType*)realloc(psl->a, newcapacity * sizeof(SLDataType));//realloc扩容,开一个新指针去接收,万一没开辟成功,也不会影响原来的psl所指向的空间,有时候后面连续空间不够,会重新开辟一个新空间,地址也会改变。
if (tmp == NULL)//判断是否开辟失败
{
perror("realloc");
return;
}
psl->a = tmp;
psl->capacity = newcapacity;//更新容量
}
}
2.2.4顺序表尾插
//顺序表尾插
void SLPushBack(SL* psl, SLDataType x)
{
assert(psl);//断言一下防止传空指针进来
SLCheckCapacity(psl);//调用函数检查容量
psl->a[psl->size] = x;//尾插数据
psl->size++;//更新有效个数
}
2.2.5顺序表尾删
//顺序表尾删
void SLPopBack(SL* psl)
{
assert(psl);//断言一下防止传空指针进来
assert(psl->size > 0);//顺序表不能为空,防止删空
psl->size--;//直接减少有效个数就ok了,访问不到了。
}
2.2.6顺序表头插
//顺序表头插
void SLPushPront(SL* psl, SLDataType x)
{
assert(psl);//断言一下防止传空指针进来
SLCheckCapacity(psl);//调用函数检查容量
int end = psl->size - 1;//找尾
while (end >= 0)//挪动数据
{
psl->a[end + 1] = psl->a[end];//这里要从后往前移
end--;
}
psl->a[0] = x;//头插数据
psl->size++;//更新有效个数
}
2.2.7顺序表头删
//顺序表头删
void SLPopPront(SL* psl)
{
assert(psl);//断言一下防止传空指针进来
assert(psl->size > 0);//顺序表不能为空,防止删空
int begin = 1;//找头
while (begin < psl->size)//挪动数据
{
psl->a[begin - 1] = psl->a[begin];//从前往后移动
begin++;
}
psl->size--;//更新有效数据
}
2.2.8顺序表在下标pos位置插入x
// 顺序表在下标pos位置插入x
void SLInsert(SL* psl, int pos, SLDataType x)
{
assert(psl);//断言一下防止传空指针进来
assert(pos >= 0 && pos <= psl->size);//顺序表是连续的,所以要确保pos的下标有效
SLCheckCapacity(psl);//调用函数检查容量
int end = psl->size - 1;//找尾
while (end >= pos)//挪动数据
{
psl->a[end + 1] = psl->a[end];//从后往前移动
end--;
}
psl->a[pos] = x;//在下标pos处插入数据
psl->size++;//更新有效数据
}
2.2.9 顺序表删除下标pos位置的值
// 顺序表删除下标pos位置的值
void SLErase(SL* psl, int pos)
{
assert(psl);//断言一下防止传空指针进来
assert(psl->size > 0);//顺序表不能为空,防止删空
assert(pos >= 0 && pos <psl->size);//顺序表是连续的,所以要确保pos的下标有效
int begin = pos + 1;//找pos后一个位置
while (begin < psl->size)//挪动数据
{
psl->a[begin - 1] = psl->a[begin];//从前往后移动
begin++;
}
psl->size--;//更新有效数据
}
2.2.10 顺序表查找x
//顺序表查找x
int SLFind(const SL* psl, SLDataType x)
{
assert(psl);//断言一下防止传空指针进来
for (int i = 0; i < psl->size; i++)//遍历查找
{
if (psl->a[i] == x)
{
return i;//相等则反回该值在数组中的下标
}
}
return -1;//数组中没有则返回-1
}
2.2.11 修改顺序表下标pos位置的值
void SLModify(SL* psl, int pos, SLDataType x)
{
assert(psl); //断言一下防止传空指针进来
assert(psl->size > 0); //顺序表不能为空
assert(pos >= 0 && pos < psl->size); //顺序表是连续的,所以要确保pos的下标有效
psl->a[pos] = x; //修改pos下标处对应的数据
}
2.2.12 查看顺序表中有效数据个数
可能大家会有一个疑问,我在主函数里面直接通过定义的结构体变量直接访问就好了呀,为啥还要弄一个函数呢?
在数据结构中有一个约定,如果要访问或修改数据结构中的数据,不要直接去访问,要去调用它的函数来访问和修改,这样更加规范安全,也更方便检查是否出现了越界等一些错误情况。
越界不一定报错,系统对越界的检查是一种抽查
越界读一般是检查不出来的
越界写如果是修改到标志位才会检查出来
(系统在数组末尾后设的有标志位,越界写时,恰好修改到标志位了,就会被检查出来)
int SLSize(const SL* psl)
{
assert(psl); //断言一下防止传空指针进来
return psl->size;
}
2.2.13 顺序表销毁
//顺序表销毁
void SLDestory(SL* psl)
{
assert(psl); //断言一下防止传空指针进来
if (psl->a != NULL)//判断如果为空则不用管,不为空则手动释放
{
free(psl->a);//释放动态开辟的空间
psl->a = NULL;//置空
psl->size = psl->capacity = 0;//全部置为0
}
}
完整代码(包括测试代码)
SeqList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//顺序表的动态储存
typedef int SLDataType;//给类型重命名,方便后续更改类型
typedef struct SeqList
{
SLDataType* a;//指向动态开辟的数组
int size;//有效空间个数
int capacity;//容量空间的大小
}SL;
//顺序表初始化
void SLInit(SL* psl);
//顺序表打印
void SLPrint(const SL* psl);
//检查空间,如果满了,进行增容
void SLCheckCapacity(SL* psl);
//顺序表尾插
void SLPushBack(SL* psl, SLDataType x);
//顺序表尾删
void SLPopBack(SL* psl);
//顺序表头插
void SLPushPront(SL* psl, SLDataType x);
//顺序表头删
void SLPopPront(SL* psl);
// 顺序表在下标pos位置插入x
void SLInsert(SL* psl, int pos, SLDataType x);
// 顺序表删除下标pos位置的值
void SLErase(SL* psl, int pos);
//顺序表查找x
int SLFind(const SL* psl, SLDataType x);
//修改顺序表下标pos位置的值
void SLModify(SL* psl, int pos, SLDataType x);
//查看顺序表中有效数据个数
int SLSize(const SL* psl);
// 顺序表销毁
void SLDestory(SL* psl);
SeqList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
//顺序表初始化
void SLInit(SL* psl)
{
assert(psl);//断言一下防止传空指针进来
psl->a = NULL;//初始顺序表为空
psl->size = 0; //初始数据个数为0
psl->capacity = 0;//初始空间容量为0,也可以给初始空间值
}
//顺序表打印
void SLPrint(const SL* psl)
{
assert(psl);//断言一下防止传空指针进来
//这里也不用去判断顺序表为不为空,因为为空就不会打印。
for (int i = 0; i < psl->size; i++)//打印顺序表
{
printf("%d ", psl->a[i]);
}
printf("\n");
}
//检查空间,如果满了,进行增容
void SLCheckCapacity(SL* psl)
{
assert(psl);//断言一下防止传空指针进来
if (psl->size == psl->capacity)//检查空间,满了就增容
{
int newcapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
//这里用了三字母词,就是如果capacity为0则赋一个4如果不为空则扩容自身的2倍
SLDataType* tmp = (SLDataType*)realloc(psl->a, newcapacity * sizeof(SLDataType));
//realloc扩容,开一个新指针去接收,万一没开辟成功,也不会影响原来的psl所指向的空间,有时候后面连续空间不够,会重新开辟一个新空间,地址也会改变。
if (tmp == NULL)//判断是否开辟失败
{
perror("realloc");
return;
}
psl->a = tmp;
psl->capacity = newcapacity;//更新容量
}
}
//顺序表尾插
void SLPushBack(SL* psl, SLDataType x)
{
assert(psl);//断言一下防止传空指针进来
SLCheckCapacity(psl);//调用函数检查容量
psl->a[psl->size] = x;//尾插数据
psl->size++;//更新有效个数
}
//顺序表尾删
void SLPopBack(SL* psl)
{
assert(psl);//断言一下防止传空指针进来
assert(psl->size > 0);//顺序表不能为空,防止删空
psl->size--;//直接减少有效个数就ok了,访问不到了。
}
//顺序表头插
void SLPushPront(SL* psl, SLDataType x)
{
assert(psl);//断言一下防止传空指针进来
SLCheckCapacity(psl);//调用函数检查容量
int end = psl->size - 1;//找尾
while (end >= 0)//挪动数据
{
psl->a[end + 1] = psl->a[end];//这里要从后往前移
end--;
}
psl->a[0] = x;//头插数据
psl->size++;//更新有效个数
}
//顺序表头删
void SLPopPront(SL* psl)
{
assert(psl);//断言一下防止传空指针进来
assert(psl->size > 0);//顺序表不能为空,防止删空
int begin = 1;//找头
while (begin < psl->size)//挪动数据
{
psl->a[begin - 1] = psl->a[begin];//从前往后移动
begin++;
}
psl->size--;//更新有效数据
}
// 顺序表在下标pos位置插入x
void SLInsert(SL* psl, int pos, SLDataType x)
{
assert(psl);//断言一下防止传空指针进来
assert(pos >= 0 && pos <= psl->size);//顺序表是连续的,所以要确保pos的下标有效
SLCheckCapacity(psl);//调用函数检查容量
int end = psl->size - 1;//找尾
while (end >= pos)//挪动数据
{
psl->a[end + 1] = psl->a[end];//从后往前移动
end--;
}
psl->a[pos] = x;//在下标pos处插入数据
psl->size++;//更新有效数据
}
// 顺序表删除下标pos位置的值
void SLErase(SL* psl, int pos)
{
assert(psl);//断言一下防止传空指针进来
assert(psl->size > 0);//顺序表不能为空,防止删空
assert(pos >= 0 && pos < psl->size);//顺序表是连续的,所以要确保pos的下标有效
int begin = pos + 1;//找pos后一个位置
while (begin < psl->size)//挪动数据
{
psl->a[begin - 1] = psl->a[begin];//从前往后移动
begin++;
}
psl->size--;//更新有效数据
}
//顺序表查找x
int SLFind(const SL* psl, SLDataType x)
{
assert(psl);//断言一下防止传空指针进来
for (int i = 0; i < psl->size; i++)//遍历查找
{
if (psl->a[i] == x)
{
return i;//相等则反回该值在数组中的下标
}
}
return -1;//数组中没有则返回-1
}
//修改顺序表下标pos位置的值
void SLModify(SL* psl, int pos, SLDataType x)
{
assert(psl); //断言一下防止传空指针进来
assert(psl->size > 0); //顺序表不能为空
assert(pos >= 0 && pos < psl->size); //顺序表是连续的,所以要确保pos的下标有效
psl->a[pos] = x; //修改pos下标处对应的数据
}
// 查看顺序表中有效数据个数
int SLSize(const SL* psl)
{
assert(psl); //断言一下防止传空指针进来
return psl->size;
}
//顺序表销毁
void SLDestory(SL* psl)
{
assert(psl); //断言一下防止传空指针进来
if (psl->a != NULL)//判断如果为空则不用管,不为空则手动释放
{
free(psl->a);//释放动态开辟的空间
psl->a = NULL;//置空
psl->size = psl->capacity = 0;//全部置为0
}
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
void SLtest1()
{
SL sl;
SLInit(&sl);
SLPrint(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPrint(&sl);
SLPopBack(&sl);
SLPopBack(&sl);
SLPopBack(&sl);
SLPopBack(&sl);
//SLPopBack(&sl);
SLPrint(&sl);
SLDestory(&sl);
}
void SLtest2()
{
SL sl;
SLInit(&sl);
SLPrint(&sl);
SLPushPront(&sl, 1);
SLPushPront(&sl, 2);
SLPushPront(&sl, 3);
SLPushPront(&sl, 4);
SLPrint(&sl);
SLPopPront(&sl);
SLPopPront(&sl);
SLPopPront(&sl);
SLPopPront(&sl);
//SLPopPront(&sl);
SLPrint(&sl);
SLDestory(&sl);
}
void SLtest3()
{
SL sl;
SLInit(&sl);
SLPrint(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPushBack(&sl, 5);
SLPushBack(&sl, 6);
SLPushBack(&sl, 7);
SLPushBack(&sl, 8);
SLPushBack(&sl, 9);
SLPrint(&sl);
SLInsert(&sl, 4, 70);
SLInsert(&sl, 0, 70);
//SLInsert(&sl, 11, 70);
//SLInsert(&sl, 13, 70);
SLPrint(&sl);
SLDestory(&sl);
}
void SLtest4()
{
SL sl;
SLInit(&sl);
SLPrint(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPrint(&sl);
SLErase(&sl, 0);
SLPrint(&sl);
SLErase(&sl, 0);
SLPrint(&sl);
SLErase(&sl, 0);
SLPrint(&sl);
//SLErase(&sl, 0);
//SLPrint(&sl);
SLDestory(&sl);
}
void SLtest5()
{
SL sl;
SLInit(&sl);
SLPrint(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPushBack(&sl, 5);
SLPushBack(&sl, 6);
SLPushBack(&sl, 7);
SLPushBack(&sl, 8);
SLPushBack(&sl, 9);
SLPrint(&sl);
int pos = SLFind(&sl, 6);
printf("%d \n", pos);
SLModify(&sl, pos, 90);
SLPrint(&sl);
int size = SLSize(&sl);
printf("%d \n", size);
SLDestory(&sl);
}
int main()
{
SLtest3();
return 0;
}
总结
顺序表的优点:
(1)物理内存连续,支持下标随机访问O(1)
(2)尾插尾删很方便
顺序表的缺点:
(1)头部中间插入和删除效率低,通常须移动数据O(N)
(2)空间不够需要扩容,扩容有一定的消耗,且可能存在一定的空间浪费