文章目录
引入
学完动态内存管理和结构体相关的知识后,最重要的应用之一就是自己动手来实现一个顺序表,下文将详细的展示顺序表的实现过程,包括顺序表的“增,删,查,改”等丰富内容。文章末尾还为读者献上了鄙人实现顺序表的源代码,速速学起来吧
线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是⼀种在实际中⼴泛使 ⽤的数据结构,常⻅的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储。
线性表中储存的是具有一类相同特性的数据结构的集合,这里的相同特性包括逻辑结构和物理结构,逻辑结构实际上是人为想象的数据的排列形式,比方说线性,树状。物理结构则是数据在内存上的存储形式,比方说数组在内存上的存储形式是线性且连续的,也正是这个原因,我们可以通过数组下标来访问数组元素
一句话概括:线性表就是字面意思,成线性结构的表。生活中较为直观的例子,像是各位的成绩单或者工资条,是成线性结构排列的。当然,在C语言中数组就是一种典型的线性结构,本篇重点阐述的顺序表,其底层逻辑就是数组,可以称之为–>数组plus
顺序表
概念与结构
-
概念:顺序表是⽤⼀段物理地址连续的存储单元依次存储数据元素的线性结构,⼀般情况下采⽤数组存储。
-
结构:逻辑结构线性,物理结构连续存放,和数组完全相同
YY:哎嗨?这就奇怪了,那数组和顺序表的区别是什么嘞?
现在假设我想让你删掉数组中某个特定的元素,你能直接做到嘛?那再让你在某个位置增加一个元素呢?就像让你算一个数组的大小,你会使用C语言提供的函数sizeof
那样,但现在似乎没有一个现成的方法让我们直接完成这件事情,但是顺序表就可以完成这些事情,顺序表对数组进行了封装,实现了常用的增删查改等接口
分类
顺序表分为静态顺序表和动态顺序表,实际应用中动态顺序表占大多数情况
静态顺序表
大白话解释:在使用之前已经知道表的大小
#define N 1000
typedef int DataType;
typedef struct SqList
{
DataType arr[N];//定长数组
int size;//记录顺序表的有效数据个数
}SL;//定义了一个结构体,并且命名为SL
YY:为啥要使用关键字typedef
关键字再次定义int
?
比方说,在你写的一个较大的项目中,一开始定义顺序表的需求是想要以int
类型的数组为底层结构,一段时间之后,你想要改变顺序表的底层结构,这时直接修改关键字之后的内容就行,不再需要逐个更换
动态顺序表
一开始不知道顺序表的大小,在程序执行过程中,使用动态内存管理来开辟空间
typedef int DataType;
//声明顺序表
typedef struct Sqlist
{
DataType* arr;
int size;
int capacity;
}SL;
动态顺序表的实现
总体思路
由于该项目带行数较多,为了方便管理代码,并且提高代码的可读性,咱们分成三个文件来完成
第一个文件为SqList.h
,用于告知读者该顺序表具有的功能,可理解为是说明书
第二个文件为SqList.c
,用于实现顺序表的具体功能
第三个文件为test.c
,用于各种功能的测试
文章的末尾将展示所有的源代码
顺序表的声明
typedef int DataType;
//声明顺序表
typedef struct Sqlist
{
DataType* arr;
int size;
int capacity;
}SL;
顺序表声明过程中三个妙的点
- 数组(其实是指针)类型可变,将
arr
创建为DataType*
类型,使得顺序表更加灵活 - size用于记录顺序表当前的元素个数
- capacity用于记录顺序表的实际大小
顺序表的初始化
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->size = 0;
ps->capacity = 0;
}
顺序表初始化过程中两个注意的点
- 函数的参数类型是
SL*
,结构体指针类型,这里要进行传址调用,因为要具体修改已声明顺序表的内容,若只是传值调用,形参只会是实参的一份零时拷贝,修改形参的内容不影响实参 - 用->访问结构体中的成员,因为ps是结构体指针类型
顺序表容量判断
在对顺序表进行插入操作之前,需要先判断顺序表的容量是否充足
void SLCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
DataType* tmp = (DataType*)realloc(ps->arr, newcapacity * sizeof(DataType));
if (tmp == NULL)
{
perror("realloc");
exit(1);
}
ps->arr = tmp;
ps->capacity = newcapacity;//别忘了这个函数的意义
}
}
- 在创建顺序表的过程中,我们给
capacity
初始化为0,不能直接对其进行乘法运算,所以这里要创建临时变量newcapacity
,并且用到三目表达式 - 使用
realloc
函数对其进行扩容,并赋值给临时变量tmp
,防止扩容失败导致数据丢失 - 及时更新
capacity
的值
顺序表元素增加
向顺序表插入数据大体可以分为以下三种情况:头插,尾插,还有在指定位置插入
头插
void SLPushFront(SL* ps, DataType x)
{
assert(ps);
SLCheckCapacity(ps);
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[0] = x;
ps->size++;
}
- 使用
assert
断言,防止传入空指针 - 及时更新
size
的值
尾插
void SLPushBack(SL* ps, DataType x)
{
assert(ps);
SLCheckCapacity(ps);
ps->arr[ps->size++] = x;
}
指定位置插入
void SLInsert(SL* ps, DataType x, int pos)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
for (int i = ps->size; i >= pos; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos-1] = x;
ps->size++;
}
- 使用
assert
断言,确保插入的数据合法
顺序表元素打印
为了方便我们观察顺序表中的数据,还要实现一个打印函数
void SLPrint(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
- 参数为了和上面的插入保持统一,也设置成
*p
顺序表元素删除
删除和插入同理,也可分为头删,尾删,和指定位置删除
我们需要理解计算机对删除操作的逻辑是“覆盖”,就是说操作系统并没有删除你所写入的程序,而是允许你访问这篇空间,对这篇空间重新操作
头删
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--;
}
for
循环中的代买块就可显示出“覆盖”的逻辑,最后不要忘记size--
尾删
void SLPopBake(SL* ps)
{
assert(ps);
assert(ps->size);
ps->size--;
}
assert
首先要断言ps
,防止传入空指针assert
还要断言size
,确保顺序表中有数据可以删除
指定位置删除
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(ps->size);
assert(pos >= 0 && pos < ps->size);
for (int i = pos - 1; i < ps->size; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
assert
断言删除的位置是否合理
顺序表元素查找
int SLFind(SL* ps, DataType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
return i;
}
}
return -1;
}
- 若存在要查找的数据,则返回该数据所在的下标,否则返回-1,表示顺序表中不存在该数据
顺序表的销毁
void SLDestroy(SL* ps)
{
if (ps->arr)
{
free(ps->arr);
}
ps->arr = NULL;
ps->capacity = 0;
ps->size = 0;
}
- 顺序表是动态申请的内存空间,所以要用
free
函数来释放空间 - 将
arr
置为空指针,防止野指针出现
动态顺序表的源代码
SqList.h
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//该文件用于声明顺序表的功能
typedef int DataType;
//声明顺序表
typedef struct Sqlist
{
DataType* arr;
int size;
int capacity;
}SL;
//初始化顺序表
void SLInit(SL* ps);
//顺序表的插入
void SLPushBack(SL* ps, DataType x);
void SLPushFront(SL* ps, DataType x);
void SLInsert(SL* ps, DataType x, int pos);
//顺序表元素打印
void SLPrint(SL* ps);
//顺序表的删除
void SLPopBake(SL* ps);
void SLPopFront(SL* ps);
void SLErase(SL* ps, int pos);
//顺序表元素查找
int SLFind(SL* ps, DataType x);
//顺序表的销毁
void SLDestroy(SL* ps);
SqList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SqList.h"
//该文件用于实现具体功能
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->size = 0;
ps->capacity = 0;
}
void SLCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
DataType* tmp = (DataType*)realloc(ps->arr, newcapacity * sizeof(DataType));
if (tmp == NULL)
{
perror("realloc");
exit(1);
}
ps->arr = tmp;
ps->capacity = newcapacity;//别忘了这个函数的意义
}
}
void SLPushBack(SL* ps, DataType x)
{
assert(ps);
SLCheckCapacity(ps);
ps->arr[ps->size++] = x;
}
void SLPushFront(SL* ps, DataType x)
{
assert(ps);
SLCheckCapacity(ps);
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[0] = x;
ps->size++;
}
void SLInsert(SL* ps, DataType x, int pos)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
for (int i = ps->size; i >= pos; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos-1] = x;
ps->size++;
}
void SLPrint(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
void SLPopBake(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--;
}
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(ps->size);
assert(pos >= 0 && pos < ps->size);
for (int i = pos - 1; i < ps->size; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
int SLFind(SL* ps, DataType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
return i;
}
}
return -1;
}
void SLDestroy(SL* ps)
{
if (ps->arr)
{
free(ps->arr);
}
ps->arr = NULL;
ps->capacity = 0;
ps->size = 0;
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SqList.h"
//该文件用于测试顺序表的各项功能
void test()
{
SL s;
SLInit(&s);
SLPushBack(&s, 1);
SLPushBack(&s, 2);
SLPushBack(&s, 3);
SLPushBack(&s, 4);
SLPushBack(&s, 5);
SLPushFront(&s, 6);
SLInsert(&s, 7, 2);
SLPrint(&s);
SLPopBake(&s);
SLPrint(&s);
SLPopFront(&s);
SLPrint(&s);
SLErase(&s, 3);
SLPrint(&s);
int pos = SLFind(&s, 7);
printf("%d ", pos);
SLDestroy(&s);
}
int main()
{
test();
}
总结
以上是数据结构中顺序表的相关的知识,我们要像捡贝壳一样捡到自己的小篮子里哦,有不明白的地方欢迎留言,作者水平有限,文章不妥部分还请各位读者指正。