目录
前言
米娜桑,这里是小鸥,我们又见面啦!
本篇博客总结了顺序表相关的知识点,将一步步带你写出顺序表,如果有帮助到你就支持一下吧~
(本篇相关代码为C语言,最后附有顺序表的完整代码)
初步了解顺序表
1.什么是顺序表
顺序表的本质就是一种数组,数组就是一种很简单的数据结构,顺序表就是在数组的基础上,将数组打包,使其拥有各种各样的功能。
而顺序表又是线性表的一种,那么什么又是线性表呢?
线性表:
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是⼀种在实际中⼴泛使 ⽤的数据结构,常⻅的线性表:顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储。
顺序表与在物理与逻辑上都是线性的,而线性表在物理上则不一定是线性,而逻辑上一定是线性的;这就像我们日常中排队一样,虽然排队时我们会占成一排,但事实是有时候并不是真正的直线,这就是所谓在逻辑上是线性,而物理上不一定是线性。
2.静态顺序表创建
使用定长数组储存元素的顺序表就是静态顺序表
typedef int SLDateType;
#define N 10
typedef struct SeqList
{
SLDateType arr[N];
int size;//有效数据个数
}SL;
SLDateType为顺序表中存储的数据类型;
静态顺序表缺陷:空间给少了不够用,给大了又会导致空间浪费。
3.动态顺序表创建
动态顺序表则可以做到按需申请空间,减少空间的浪费
typedef int SLDateType;
//顺序表声明
typedef struct SeqList
{
SLDateType* arr;//动态数组
int size;//有效数据个数
int capacity;//空间容量
}SL;
动态顺序表会额外需要一个变量来记录当前的空间容量。
后文介绍的相关内容都为动态顺序表。
顺序表的初始化和销毁
顺序表的初始化
//初始化
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->size = 0;//有效数据个数
ps->capacity = 0;//空间容量
}
先将各个结构体成员初始化备用。
顺序表的销毁
//销毁
void SLDestroy(SL* ps)
{
if (ps->arr)
free(ps->arr);
ps->arr = NULL;
ps->size = 0;
ps->capacity = 0;
}
先使用if语句判断数组是否开辟了动态内存空间,之后就和初始化一样,将各成员都置为0
顺序表的扩容
动态顺序表之所以可以控制大小就是应因为使用了动态内存管理函数。
//扩容
void SLCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)//判断空间是否足够,不够再申请扩容
{
// 三目操作符判断capacity是否为空
// 1.空则先申请四个数据的空间
// 2.不为空则以二倍的方式扩容
int newcapacity = (ps->capacity == 0 ? 4 : 2 * ps->capacity);
SLDateType* tmp = (SLDateType*)realloc(ps->arr, sizeof(SLDateType) * newcapacity);
if (tmp == NULL)//判断空间是否申请成功
{
perror("realloc");
exit(1);
}
//空间申请成功后,将新空间的地址给到数组arr,将新空间大小给到capacity
ps->arr = tmp;
ps->capacity = newcapacity;
}
}
顺序表在初始化后,还没有动态申请空间,结构体中还没有存放数据的空间,所以在进行插入删除数据之前都应该先申请扩容,不足时再补充空间大小。
这里使用的是realloc函数,方便后续直接进行扩容。
顺序表数据的头尾插入和删除
顺序表各种操作的实现都是相通的,只要理解了一个,其他的也就不难解决了。
顺序表的头插和尾插都有两个参数,一个是顺序表存储数据类型的指针,一个是要插入的数据;
而头删和尾删,只需要指针就可以进行数据的删除了。
1.顺序表的尾插
图解:
假设此时有四个有效数据
//尾插
void SLPushBack(SL* ps, SLDateType x)
{
//断言防止指针为NULL
assert(ps);
//申请空间
SLCheckCapacity(ps);
//将插入的数放到下标size的位置,并给有效数据个数size++
ps->arr[ps->size++] = x;
}
注意:
要进行顺序表的各种操作,首先就要是初始化好了的顺序表才行,不然将会导致结构体中的指针为野指针,这是很危险的。所以无论那个操作都要首先进行assert断言。
2.顺序表的头插
想要进行头插,重点就是将现有的所有数据向后移动一位,将顺序表的第一个位置空出来,再将数据插入到首位即为头插;
由图可知,每次都将下标size-1位置上的数据移动到size位置上,所以最后是下标0移到1的位置,所以i>0
//头插
void SLPushFront(SL* ps, SLDateType x)
{
//
assert(ps);
//申请空间
SLCheckCapacity(ps);
//将已有数据往后移动
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
//将插入数据放到首位
ps->arr[0] = x;
//有效数据个数size++
ps->size++;
}
最后也不要忘了给有效数据个数++
3.顺序表的尾删
//尾删
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size);
ps->size--;
}
尾删直接将有效数据个数size直接--,这样顺序表就访问不到最后一个数据了,也就实现了”删除“操作。
4.顺序表的头删
//头删
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位置开始,将后面的所有数据往前移动一位,第一位也就被覆盖掉,实现了”删除“操作;切记最后要size--
顺序表数据的定位插入和删除
1.指定位置前插入数据
相比起上文中的头插和尾插,头删和尾删,指定位置的插入和删除数据也是必不可少的。
指定位置之前插入指的是下标位置:
指定位置的插入会因pos的不同而移动不同个数的数据位置
结合图可知,和头插一样,是从后往前移动数据,所以是下标size-1位置移动到size位置,而最后是将下标pos位置的数据移动到pos+1位置,也就是说最后一次移动size-1>=pos,size>=pos+1,也就是size>pos,将ps->size赋给i,所以for循环判断语句为i>pos
//指定位置之前插入(下标位置)
void SLInsert(SL* ps, int pos, SLDateType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);//防止指定位置pos越界
//申请空间
SLCheckCapacity(ps);
for (int i = ps->size; i > pos; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;
ps->size++;
}
移动结束后将数据插入到下标迫使pos位置即可;最后记得有效数据size++;
注意:
第二个断言语句assert中pos可以等于ps->size,此时实现的就是尾插操作,而等于0则是头插操作
2.指定位置删除数据
指定位置的删除也和头删类似,头删是从下标1开始往前移动一位;指定位置删除就是从参数pos下标的位置开始后面的数据往前移动一位。
和头插一样,从前面的数据开始往前移动;也就是下标pos+1位置移动到下标pos位置,最后一次是下标size-1位置移动到下标size-2位置,所以pos>=size-2,即pos>size-1,所以for循环的判断语句为 i>pa->size-1
//指定位置删除数据
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--;
}
注意:
当pos=0时,就是头删的效果;但此时pos不能等于size,因为在下标size的位置没有数据存储,所以第二个断言语句assert中pos < ps->size,这点和指定位置前插入不同。
后记
总的来说,如果小伙伴们第一次接触数据结构的话,可能会有一点点迷糊,但是其实顺序表是非常简单的,只要理解了它各种操作的原理就没有什么问题了。
那么本篇就到这里了,有不足的地方欢迎大家指出,相互促进!
顺序表完整代码:
.h头文件:
#pragma once
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLDateType;
//顺序表的创建
typedef struct SeqList
{
SLDateType* arr;
int size;//有效数据个数
int capacity;//空间容量
}SL;
//初始化
void SLInit(SL* ps);
//销毁
void SLDestroy(SL* ps);
//打印
void SLPrint(SL* ps);
//扩容
void SLCheckCapacity(SL* ps);
//尾插
void SLPushBack(SL* ps, SLDateType x);
//头插
void SLPushFront(SL* ps, SLDateType x);
//尾删
void SLPopBack(SL* ps);
//头删
void SLPopFront(SL* ps);
//指定位置之前插入(下标位置)
void SLInsert(SL* ps, int pos, SLDateType x);
//指定位置之前删除
void SLErase(SL* ps, int pos);
//查找
int SLFind(SL* ps, SLDateType x);
.c文件
#include "SeqList.h"
//初始化
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
//销毁
void SLDestroy(SL* ps)
{
if(ps->arr)
free(ps->arr);
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
//打印
void SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
fputs("\n", stdout);
}
//扩容
void SLCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
int newcapacity = (ps->capacity == 0 ? 4 : 2 * ps->capacity);
SLDateType* tmp = (SLDateType*)realloc(ps->arr, newcapacity * sizeof(SLDateType));
if (tmp == NULL)//动态内存开辟失败
{
perror("realloc fail!");
exit(1);
}
//动态内存开辟成功
ps->arr = tmp;
ps->capacity = newcapacity;
}
}
//尾插
void SLPushBack(SL* ps, SLDateType x)
{
assert(ps);
//扩容
SLCheckCapacity(ps);
ps->arr[ps->size++] = x;
}
//头插
void SLPushFront(SL* ps, SLDateType 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 SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size);
ps->size--;
}
//头删
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size);
//数据往前移动一位
for (int i = 0; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
//有效数据--
ps->size--;
}
//指定位置之前插入(下标位置)
void SLInsert(SL* ps, int pos, SLDateType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);//防止指定位置pos越界
//申请空间
SLCheckCapacity(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--;
}
//查找
int SLFind(SL* ps,SLDateType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
return i;//找到后返回下标
}
return -1;//没找到返回一个不可能的下标
}
つづく。