1. 线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储
2.顺序表
2.1概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。顺序表一般可以分为:
-
静态顺序表:使用定长数组存储元素。

-
动态顺序表:使用动态开辟的数组存储。

2.2 接口实现
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。
2.2.0 动态顺序表
//seqList.h
#pragma once
#include<stdio.h>
typedef int SLDataType;
#define INIT_CAPACITY 4 //默认初始化大小
//动态顺序表——按需申请
typedef struct SeqList
{
SLDataType* a;
int size; //有效数据个数
int capacity;//空间容量
}SL;
2.2.1 顺序表初始化SLInit()
版本1
//seqList.c
#include"SeqList20250226.h"
//顺序表初始化1
void seqInit(SL s)
{
s.a = (SLDataType*)malloc(sizeof(SLDataType)* INIT_CAPACITY);
if (s.a == NULL)
{
perror("seqInit");
return;
}
s.size = 0;
s.capacity = INIT_CAPACITY;
}
//test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList20250226.h"
SL s;
void TestSeqList1(SL s)
{
seqInit(s);
}
int main()
{
TestSeqList1(s);
return 0;
}
初始化结果:单步调试
版本2(后续都使用版本2)
//顺序表初始化2
void SLInit(SL* ps)
{
assert(ps != NULL); //断言
ps->a = ((SLDataType*)malloc(sizeof(SLDataType) * INIT_CAPACITY));
if (ps->a == NULL)
{
perror("SLInit");
return;
}
ps->size = 0;
ps->capacity = INIT_CAPACITY;
}
2.2.2 销毁和打印
//seqList.h
//销毁
void SLDestroy(SL* ps);
//打印
void SLPrint(SL* ps);
//seqList.c
//销毁
void SLDestroy(SL* ps)
{
free(ps->a); //释放动态开辟的空间
ps->a = NULL; //释放动态开辟的空间
ps->size = ps->capacity = 0;//数据个数和容量大小均置0
}
//打印
void SLPrint(SL* ps)
{
int i = 0;
for (i = 0; i < ps->size; i++)//遍历整个链表
{
printf("%d", ps->a[i]);
}
printf("\n");
}
2.2.3 尾插SLPushBack()
- 尾插代码实现
seqList.h
#include<stdlib.h>
//尾插
void SLPushBack(SL* ps, SLDataType x);
seqList.c
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
//扩容 这里扩容了2倍,是自己定的,是一个比较合理的值,具体需要开辟多大是要和自己的实际问题结合
if (ps->size == ps->capacity)//检查容量,满了则增容
{
SLDataType* tem = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * ((ps->capacity) * 2));
if (tem == NULL)
{
perror("SLPushBack::realloc");
return;
}
ps->a = tem;
ps->capacity *= 2;
}
//ps->a[ps->size] = x;
//ps->size++; 这两行可以合并为下面的1行
ps->a[ps->size++] = x;
}
- 尾插验证
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList20250226.h"
void TestSeqList1()
{
SL s;
SLInit(&s);
SLPushBack(&s, 1);
SLPushBack(&s, 2);
SLPushBack(&s, 3);
SLPushBack(&s, 4);
SLPushBack(&s, 5);
SLPushBack(&s, 6);
SLPushBack(&s, 7);
SLPushBack(&s, 8);
SLPushBack(&s, 9);
SLPrint(&s);
}
int main()
{
TestSeqList1();
return 0;
}
- 尾插验证

2.2.4 尾删SLPopBack()
不知道 SLDataType 是什么类型的数据,不能冒然的将顺序表最后一个数据赋值为 0,我们只需将有效数据个数 size 减 1 即可达到尾删的目的。
- 尾删代码实现
//尾删
void SLPopBack(SL* ps)
{
assert(ps);
//判断是否为空
if (ps->size == 0)
{
return 0;
}
//不为空就直接size-1
ps->size--;
}
- 尾删验证
验证插入4个元素,删掉1个元素
2.2.5 头插
头插思路:因为顺序表是连续存储的,所以头插时要依次挪动数据
扩容函数封装
void SLCheckCapacity(SL* ps)
{
assert(ps);
if (ps->size == ps->capacity)//判断目前是大小是否和容量大小一样,一样的话,就需要扩容
{
SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * (ps->capacity) * 2); //扩容原来容量的两倍
if (tmp == NULL)
{
perror("SLCheckCapacity::realloc fail!");
return;
}
ps->a = tmp;
ps->capacity *= 2;
}
}
- 头插代码实现
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);//断言
//这里要考虑扩容的事情,所以我们需要封装一个扩容函数,
SLCheckCapacity(ps); //检查顺序表容量是否已满
int end =ps->size-1;
while (end>=0)
{
ps->a[end+1] = ps->a[end ];//从后往前依次向后挪
end--;
}
ps->a[0] = x;//头插数据
ps->size++; //有效数据个数+1
}
头插验证
2.2.6 头删
思路:因为顺序表是连续存储的,所以头删时要依次挪动数据
- 头删代码实现
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size > 0);//size小于0 直接断言报错。
int begin = 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];//后一个位置的值直接赋值给前一个
begin++;
}
ps->size--;//最后size--
}
- 头删验证

2.2.7 给pos位置插入一个元素
思路
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
SLCheckCapacity(ps);
int end = ps->size - 1;
while (pos <= end)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[pos] = x;
ps->size++;
}
验证
2.2.8 删除pos位置的值
思路
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
int begin = pos + 1;
while (begin <ps->size)
{
ps->a[begin-1] = ps->a[begin];
begin++;
}
ps->size--;
}
验证
2.2.9 查找函数
思路:比较简单,直接遍历即可
int SLFind(SL* ps, SLDataType x)//直接遍历即可
{
assert(ps);
int i = 0;
for (i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
printf("找到了");
return i;
}
}
return -1;
}
2.2.10 在顺序表指定下标位置插入数据
将指定位置后的所有数据依次向后挪动一位,初始代码如下:
int i = 0;
for (i = psl->size - 1; i >= pos; i--)
psl->a[i + 1] = psl->a[i];
- 原先这种写法,当顺序表为空 size = 0 时,会导致 i = -1,执行 i >= pos 时,i 被算术转换成无符号数,而无符号数的 -1 是一个值很大的正数,远大于 pos,满足条件进入循环,会造成越界访问
- 注:转换并不会改变 i 本身的值,而是执行 i >= pos 时,生成一个临时的值与 pos 比较
- 如果在顺序表头部插入数据 pos = 0,i 最终也会减减变成 -1,被算术转换后变成一个很大的数
- 总结:避免负数给到无符号数,或者避免有符号数变成负数后,被算术转换或整型提升后,变成一个很大的数
下面这样下就避免i变成-1了
void SeqListInsert(SeqList* ps, size_t pos, SLDataType x)
{
assert(ps); //断言
assert(pos >= 0 && pos <= ps->size); //检查pos下标的合法性
CheckCapacity(psl); //检查顺序表容量是否已满
size_t i = 0;
for (i = ps->size; i > pos; i--) //将pos位置后面的数据依次向后挪动一位
{
ps->a[i] = ps->a[i - 1];
}
ps->a[pos] = x; //插入数据
ps->size++; //有效数据个数+1
}
- 实现了此接口,顺序表头插相当于在下标为 0 位置处插入数据,可以改进下顺序表头插的代码:
void SeqListPushFront(SeqList* ps, SLDataType x)
{
SeqListInsert(ps, 0, x); //改造头插接口
}
2.2.11在顺序表中删除指定下标位置的数据
void SeqListErase(SeqList* psl, size_t pos)
{
assert(ps); //断言
assert(ps->size > 0); //顺序表不能为空
assert(pos >= 0 && pos < ps->size); //检查pos下标的合法性
size_t i = 0;
for (i = pos + 1; i < ps->size; i++) //将pos位置后面的数据依次向前挪动一位
{
ps->a[i - 1] = ps->a[i];
}
ps->size--; //有效数据个数-1
}
- 实现了此接口,顺序表头删相当于删除下标为 0 位置处的数据,可以改进下顺序表头删的代码
//顺序表头删
void SeqListPopFront(SeqList* ps)
{
SeqListErase(ps, 0); //改造头删接口
}
2.2.12 查看顺序表中有效数据个数
- 可能大家会有一个疑问,我在主函数里面直接通过定义的结构体变量直接访问就好了呀,为啥还要实现一个函数呢
- 在数据结构中有一个约定,如果要访问或修改数据结构中的数据,不要直接去访问,要去调用它的函数来访问和修改,这样更加规范安全,也更方便检查是否出现了越界等一些错误情况
size_t SeqListSize(const SeqList* psl)
{
assert(psl); //断言
return psl->size;
}
补充:越界不一定报错,系统对越界的检查是一种抽查
越界读一般是检查不出来的
越界写如果是修改到标志位才会检查出来
(系统在数组末尾后设的有标志位,越界写时,恰好修改到标志位了,就会被检查出来)
2.2.13 修改指定下标位置的数据
void SeqListAt(SeqList* psl, size_t pos, SLDataType x)
{
assert(psl); //断言
assert(psl->size > 0); //顺序表不能为空
assert(pos >= 0 && pos < psl->size); //检查pos下标的合法性
psl->a[pos] = x; //修改pos下标处对应的数据
}
2.3 源代码
2.3.1 SeqList20250226.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//#define N 100
//typedef int SLDataType;
// 静态顺序表 -- 开少了不够用 开多了浪费
//struct SeqList
//{
// SLDataType a[N];
// int sise;
//};
typedef int SLDataType;
#define INIT_CAPACITY 4 //默认初始化大小
//动态顺序表——按需申请
typedef struct SeqList
{
SLDataType* a;
int size; //有效数据个数
int capacity;//空间容量
}SL;
//基础的增删查改函数
//初始化
void seqInit(SL* ps);
void SLInit(SL* ps);
//销毁
void SLDestroy(SL* ps);
//打印
void SLPrint(SL* ps);
//尾插
void SLPushBack(SL* ps, SLDataType x);
//尾删
void SLPopBack(SL* ps);
////头插
void SLPushFront(SL* ps, SLDataType x);
////头删
void SLPopFront(SL* ps);
// 顺序表在pos位置插入x
void SLInsert(SL* ps, int pos, SLDataType x);
// 顺序表删除pos位置的值
void SLErase(SL* ps, int pos);
//查找表中有没有某个值
int SLFind(SL* ps, SLDataType x);
2.3.2 SeqList20250226.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList20250226.h"
//顺序表初始化1
void seqInit(SL s)
{
s.a = (SLDataType*)malloc(sizeof(SLDataType)* INIT_CAPACITY);
if (s.a == NULL)
{
perror("seqInit");
return;
}
s.size = 0;
s.capacity = INIT_CAPACITY;
}
//顺序表初始化2
void SLInit(SL* ps)
{
ps->a = (SLDataType*)malloc(sizeof(SLDataType) * INIT_CAPACITY);
if (ps->a == NULL)
{
perror("SLInit");
return;
}
ps->size = 0;
ps->capacity = INIT_CAPACITY;
}
//销毁
void SLDestroy(SL* ps)
{
free(ps->a);
ps->a = NULL;
ps->size = ps->capacity = 0;
}
//打印
void SLPrint(SL* ps)
{
int i = 0;
for (i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
//判断是否要扩容
if (ps->size == ps->capacity)
{
SLDataType* tem = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * (ps->capacity) * 2);
if (tem == NULL)
{
perror("SLPushBack::realloc fail");
return;
}
ps->a = tem;
ps->capacity *= 2;
}
//ps->a[ps->size] = x;
//ps->size++; //这两行可以合并为下面的1行
ps->a[ps->size++] = x;
}
//尾删
void SLPopBack(SL* ps)
{
assert(ps);
//判断是否为空
if (ps->size == 0)
{
return 0;
}
//不为空就直接size-1
ps->size--;
}
void SLCheckCapacity(SL* ps)
{
assert(ps);
if (ps->size == ps->capacity)
{
SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * (ps->capacity) * 2);
if (tmp == NULL)
{
perror("SLCheckCapacity::realloc fail!");
return;
}
ps->a = tmp;
ps->capacity *= 2;
}
}
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
//这里要考虑扩容的事情,所以我们需要封装一个扩容函数,
SLCheckCapacity(ps);
int end =ps->size-1;
while (end>=0)
{
ps->a[end+1] = ps->a[end ];//从后往前依次向后挪
end--;
}
ps->a[0] = x;
ps->size++;
}
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size > 0);//size小于0 直接断言报错。
int begin = 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
begin++;
}
ps->size--;
}
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
SLCheckCapacity(ps);
int end = ps->size - 1;
while (pos <= end)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[pos] = x;
ps->size++;
}
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
int begin = pos + 1;
while (begin <ps->size)
{
ps->a[begin-1] = ps->a[begin];
begin++;
}
ps->size--;
}
int SLFind(SL* ps, SLDataType x)//直接遍历即可
{
assert(ps);
int i = 0;
for (i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
printf("找到了");
return i;
}
}
return -1;
}
2.3.3 FileName20250226.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList20250226.h"
void TestSeqList1()
{
SL s;
SLInit(&s);
SLPushBack(&s, 0);
SLPushBack(&s, 1);
SLPushBack(&s, 2);
SLPushBack(&s, 3);
SLPushBack(&s, 4);
// SLPopBack(&s);
//
// SLPushFront(&s,5);
// SLPopFront(&s);
// SLInsert(&s, 2, 20);
SLErase(&s, 2);
SLPrint(&s);
SLDestroy(&s);
}
void manu()
{
printf("**************************\n");
printf("****1:头插 2.尾插****\n");
printf("****3.头删 4.尾删****\n");
printf("****5.插入 -1.退出****\n");
printf("**************************\n");
}
int main()
{
SL s;
SLInit(&s);
int input = 0;
while (input != -1)
{
manu();
scanf("%d", &input);
printf("请输入您的选择:\n");
if (input == 1)
{
printf("请输入您想要插入的数据,并以-1结尾\n");
int x = 0;
while (x != -1)
{
scanf("%d", &x);
SLPushBack(&s, x);
}
}
else if (input == 7)
{
SLPrint(&s);
}
}
//TestSeqList1();
return 0;
}










1941

被折叠的 条评论
为什么被折叠?



