꧁ 各位大佬们好!很荣幸能够得到您的访问,让我们一起在编程道路上任重道远!꧂
☙ 博客专栏:【数据结构初阶】❧
⛅ 本篇内容简介:数据结构初阶中的线性表之顺序表的实现!
⭐ 了解作者:励志成为一名编程大牛的学子,目前正在升大二的编程小白。
✍励志术语:编程道路的乏味,让我们一起学习变得有趣!
✂ 正文开始
文章目录
𝅬 什么是线性表?
线性表(linear list)是n个具有相同特性的数据元素的有限序列。线性表是一个在实际中广泛使用的数据结构,常见的线性表有:顺序表,链表,队列,字符串等等,今天我们实现的就是其中最简单的顺序表。
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理结构上存储时,通常以数组和链式结构的形式存储。
我们来用图解一下什么是顺序表,什么是链表:
𝅫 顺序表
𝅪 概念及结构
顺序表是用一段物理地址连续的存放存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般可分为:
𝅬 静态顺序表
使用定长数组存储元素(代码表示):
#pragma once
#include<stdio.h>
//静态顺序表
#define N 7
typedef int SLDataType; //顺序表中的内容可能是其他类型的数据
typedef struct SeqList
{
SLDataType arr[N];//定长数组
size_t size;//有效数据的个数
}SeqList;
图形演示静态顺序表结构:
𝅬 动态顺序表
使用动态开辟的数组存储(代码表示):
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* arr;//指向动态开辟的数组
size_t size;//有效数据个数
int capacity;//容量空间的大小
}SeqList;
图形演示动态顺序表结构:
𝅫 动态顺序表的接口实现
静态顺序表只适用于确定了知道存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以实现中基本都是使用动态顺序表,根据需要动态分配空间大小,所以下面我们来实现操作顺序表的接口——(增删查改)!
𝅪 顺序表初始化
void SeqListInit(SeqList* ps);
想一想我们为什么要用指针来接受呢?因为如果不传递地址,只传递值,实参只是形参的一份临时拷贝。,所以要改变顺序表中的内容,只能传递地址。
//顺序表的初始化
void SeqListInit(SeqList* ps)
{
assert(ps);//断言 预防空指针
ps->arr = NULL;
ps->capicity = ps->size = 0;
}
我们调式测试一下看,是否初始化的内容:
说明初始化函数将指针,数值,容量全部初始化了!!!
𝅪 顺序表销毁
void SeqListDestory(SeqList* ps);
//顺序表的销毁
void SeqListDestory(SeqList* ps)
{
assert(ps->arr);
free(ps);
ps->arr = NULL;
ps->capicity = ps->size = 0;
}
因为是动态开辟的内存空间,所以一定要结束程序之前销毁,否则会造成内存泄漏!
𝅪 顺序表扩容
void CheckCapacity(SeqList* ps);
扩容的思路(当容量==有效数据):
先给定一个初始化的容量,如果有容量,直接扩容到两倍,将这个容量给一个新的容量表达式。
为什么要扩容到两倍?
一次扩多了,存在空间浪费,扩少了,会造成频繁扩容,效率损失。
//顺序表的扩容
void CheckCapacity(SeqList* ps)
{
assert(ps);
if (ps->capacity == ps->size)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps->arr, newcapacity * sizeof(SLDataType));
if (tmp == NULL)
{
//扩容失败
ferror("realloc fail");
return;
}
else
{
//扩容成功
ps->arr = tmp;
ps->capacity = newcapacity;
}
}
}
𝅪 顺序表打印
void SeqListPrint(SeqList* ps);
//顺序表的打印
void SeqListPrint(SeqList* ps)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");//换行
}
𝅪 顺序表头插
void SeqListPushFront(SeqList* ps, SLDataType x);
实现思路:
先判断是否需要扩容,再将后面的数据从后往前挪动,最后将下标为0的位置赋值为x。
图解:
代码实现:
//顺序表的头插
void SeqListPushFront(SeqList* ps, SLDataType x)
{
assert(ps);
CheckCapacity(ps);
int end = ps->size;
while (end >= 0)
{
ps->arr[end] = ps->arr[end - 1];
end--;
}
ps->arr[0] = x;
ps->size++;
}
运行结果测试头插跟打印函数功能实现!!!
𝅪 顺序表头删
void SeqListPopFront(SeqList* ps);
实现思路:
不需要free掉内存,也不需要在开辟一块新的地址。将下标为1的数据往前覆盖即可。
图解:
代码实现:
//顺序表的头删
void SeqListPopFront(SeqList* ps)
{
assert(ps);
//判断size是否>0
assert(ps->size > 0);
int begin = 1;
while (begin < ps->size)
{
ps->arr[begin - 1] = ps->arr[begin];
begin++;
}
ps->size--;
}
运行结果测试(成功头删):
𝅪 顺序表尾插
void SeqListPushBack(SeqList* ps,SLDataType x);
实现思路:
先检查是否需要增容,再在下标为size的位置插入数据,size++。
代码实现:
//顺序表的尾插
void SeqListPushBack(SeqList* ps,SLDataType x)
{
assert(ps);
CheckCapacity(ps);
ps->arr[ps->size] = x;
ps->size++;
}
运行结果测试(成功尾插):
𝅪 顺序表尾删
void SeqListPopBack(SeqList* ps);
实现思路:
先判断ps->size是否大于0,再size--就删除了。
代码实现:
//顺序表的尾删
void SeqListPopBack(SeqList* ps)
{
assert(ps);
assert(ps->size > 0);
ps->size--;
}
运行结果测试(成功尾删):
𝅪 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, size_t pos, SLDataType x);
说明一下:为什么pos位置的类型是size_t (无符号的整型)?
因为在库里面(string)中它是无符号的实现方式。
实现思路:
因为是在pos位置前面插入的,所以需要后挪动数据,我们需要定义一个尾部的下标,如果我们定义end=size-1的话,在比较pos大小时,会发生整型提升,所以我们把尾部下标定义在size的位置。(定义size-1位置强制类型转化pos就可以了)。
图解:
代码实现:
//顺序表在pos的位置插入x
void SeqListInsert(SeqList* ps, size_t pos, SLDataType x)
{
assert(ps);
CheckCapacity(ps);
int end = ps->size;
while (end > pos)
{
ps->arr[end] = ps->arr[end - 1];
end--;
}
ps->arr[pos] = x;
ps->size++;
}
运行结果测试 —— (成功插入):
𝅪 顺序表查找
int SeqListFind(SeqList* ps, SLDataType x);
实现思路:
在顺序表中查找某个数,找到了,返回下标,找不到返回-1。
代码实现:
//顺序表的查找
int SeqListFind(SeqList* ps, SLDataType x)
{
assert(ps);
int i = 0;
for (i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
return i;
}
}
return -1;
}
运行结果测试 —— 一般查找配合其他功能函数一起使用,比如我们测试配合在pos位置插入功能实现:
𝅪 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, size_t pos);
实现思路:
定义一个begin下标在pos+1的位置,从前往后覆盖pos的数据。
图解:
代码实现:
//顺序表删除pos位置的值
void SeqListErase(SeqList* ps, size_t pos)
{
assert(ps);
//判断size是否大于0
assert(ps->size > 0);
int begin = pos + 1;
while (begin < ps->size)
{
ps->arr[begin - 1] = ps->arr[begin];
begin++;
}
ps->size--;
}
测试删除pos下标的值,这里我们配合SeqListFind函数功能使用:
𝅪 顺序表修改
void SeqListModify(SeqList* ps, size_t pos,SLDataType x);
实现思路:
直接将pos下标的值修改成x,就行。
代码实现:
//顺序表修改
void SeqListModify(SeqList* ps, size_t pos, SLDataType x)
{
assert(ps);
//判断pos是否 < ps->size
assert(pos < ps->size);
ps->arr[pos] = x;
}
测试修改功能 —— 搭配SeqListFind函数使用(成功修改):
𝅫 动态顺序表的接口改进
𝅪 顺序表头删
//顺序表的头删改进
void SeqListPopFront(SeqList* ps)
{
//assert(ps);
判断size是否>0
//assert(ps->size > 0);
//int begin = 1;
//while (begin < ps->size)
//{
// ps->arr[begin - 1] = ps->arr[begin];
// begin++;
//}
//ps->size--;
//头删改进
SeqListErase(ps, 0);
}
𝅪 顺序表头插
//顺序表的头插改进
void SeqListPushFront(SeqList* ps, SLDataType x)
{
/*assert(ps);
CheckCapacity(ps);
int end = ps->size;
while (end >= 0)
{
ps->arr[end] = ps->arr[end - 1];
end--;
}
ps->arr[0] = x;
ps->size++;*/
//头插改进
SeqListInsert(ps, 0, x);
}
𝅪 顺序表尾插
//顺序表的尾插
void SeqListPushBack(SeqList* ps,SLDataType x)
{
/*assert(ps);
CheckCapacity(ps);
ps->arr[ps->size] = x;
ps->size++;*/
//尾插改进
SeqListInsert(ps, ps->size, x);
}
𝅪 顺序表尾删
//顺序表的尾删
void SeqListPopBack(SeqList* ps)
{
/*assert(ps);
assert(ps->size > 0);
ps->size--;*/
//尾删改进
SeqListErase(ps, ps->size - 1);
}
𝅫 程序文件中源代码
𝅪 SeqList.h
用于存放顺序表,函数功能的定义,头文件的引用等。
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#pragma once
//静态顺序表
//
//#define N 10
//
//typedef int SLDataType; //顺序表中的内容可能是其他类型的数据
//
//typedef struct SeqList
//{
// SLDataType arr[N];//定长数组
// size_t size;//有效数据的个数
//}SeqList;
//顺序表的动态存储
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* arr;//指向动态开辟的数组
size_t size;//有效数据个数
int capacity;//容量空间的大小
}SeqList;
//顺序表的初始化
void SeqListInit(SeqList* ps);
//顺序表的销毁
void SeqListDestory(SeqList* ps);
//顺序表的扩容
void CheckCapacity(SeqList* ps);
//顺序表的打印
void SeqListPrint(SeqList* ps);
//顺序表的头插
void SeqListPushFront(SeqList* ps, SLDataType x);
//顺序表的头删
void SeqListPopFront(SeqList* ps);
//顺序表的尾插
void SeqListPushBack(SeqList* ps,SLDataType x);
//顺序表的尾删
void SeqListPopBack(SeqList* ps);
//顺序表的查找
int SeqListFind(SeqList* ps, SLDataType x);
//顺序表在pos的位置插入x
void SeqListInsert(SeqList* ps, size_t pos, SLDataType x);
//顺序表删除pos位置的值
void SeqListErase(SeqList* ps, size_t pos);
//顺序表修改
void SeqListModify(SeqList* ps, size_t pos,SLDataType x);
𝅪 SeqList.c
用于实现 顺序表 中函数功能的定义。
#include"SeqList.h"
//用于定义顺序表接口函数
//顺序表的初始化
void SeqListInit(SeqList* ps)
{
assert(ps);//断言 预防空指针
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
//顺序表的销毁
void SeqListDestory(SeqList* ps)
{
assert(ps);
free(ps->arr);
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
//顺序表的扩容
void CheckCapacity(SeqList* ps)
{
assert(ps);
if (ps->capacity == ps->size)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps->arr, newcapacity * sizeof(SLDataType));
if (tmp == NULL)
{
//扩容失败
ferror("realloc fail");
return;
}
else
{
//扩容成功
ps->arr = tmp;
ps->capacity = newcapacity;
}
}
}
//顺序表的打印
void SeqListPrint(SeqList* ps)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");//换行
}
//顺序表的头插改进
void SeqListPushFront(SeqList* ps, SLDataType x)
{
/*assert(ps);
CheckCapacity(ps);
int end = ps->size;
while (end >= 0)
{
ps->arr[end] = ps->arr[end - 1];
end--;
}
ps->arr[0] = x;
ps->size++;*/
//头插改进
SeqListInsert(ps, 0, x);
}
//顺序表的头删改进
void SeqListPopFront(SeqList* ps)
{
//assert(ps);
判断size是否>0
//assert(ps->size > 0);
//int begin = 1;
//while (begin < ps->size)
//{
// ps->arr[begin - 1] = ps->arr[begin];
// begin++;
//}
//ps->size--;
//头删改进
SeqListErase(ps, 0);
}
//顺序表的尾插
void SeqListPushBack(SeqList* ps,SLDataType x)
{
/*assert(ps);
CheckCapacity(ps);
ps->arr[ps->size] = x;
ps->size++;*/
//尾插改进
SeqListInsert(ps, ps->size, x);
}
//顺序表的尾删
void SeqListPopBack(SeqList* ps)
{
/*assert(ps);
assert(ps->size > 0);
ps->size--;*/
//尾删改进
SeqListErase(ps, ps->size - 1);
}
//顺序表的查找
int SeqListFind(SeqList* ps, SLDataType x)
{
assert(ps);
int i = 0;
for (i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
return i;
}
}
return -1;
}
//顺序表在pos的位置插入x
void SeqListInsert(SeqList* ps, size_t pos, SLDataType x)
{
assert(ps);
assert(pos < ps->size);
CheckCapacity(ps);
int end = ps->size;
while (end > pos)
{
ps->arr[end] = ps->arr[end - 1];
end--;
}
ps->arr[pos] = x;
ps->size++;
}
//顺序表删除pos位置的值
void SeqListErase(SeqList* ps, size_t pos)
{
assert(ps);
assert(pos < ps->size);
//判断size是否大于0
assert(ps->size > 0);
int begin = pos + 1;
while (begin < ps->size)
{
ps->arr[begin - 1] = ps->arr[begin];
begin++;
}
ps->size--;
}
//顺序表修改
void SeqListModify(SeqList* ps, size_t pos, SLDataType x)
{
assert(ps);
//判断pos是否 < ps->size
assert(pos < ps->size);
ps->arr[pos] = x;
}
𝅪 test.c
用于存放顺序表功能函数的测试,可以写成菜单形式,但是我这里就不写了,只写成测试形式。
#include"SeqList.h"
//用于测试顺序表的函数功能
void test1()
{
//测试初始化
SeqList s1;
SeqListInit(&s1);
//测试头插
SeqListPushFront(&s1, 1);
SeqListPushFront(&s1, 2);
SeqListPushFront(&s1, 3);
SeqListPushFront(&s1, 4);
SeqListPrint(&s1);//打印一下
//测试头删
SeqListPopFront(&s1);
SeqListPopFront(&s1);
SeqListPrint(&s1);
//测试尾插
SeqListPushBack(&s1, 20);
SeqListPushBack(&s1, 30);
SeqListPushBack(&s1, 40);
SeqListPrint(&s1);
//测试尾删
SeqListPopBack(&s1);
SeqListPrint(&s1);
//测试pos位置插入
SeqListInsert(&s1, 3, 60);
SeqListPrint(&s1);
//测试查找
SeqListInsert(&s1, SeqListFind(&s1,60), 88);
SeqListPrint(&s1);
//测试删除pos的值
SeqListErase(&s1, SeqListFind(&s1, 88));
SeqListPrint(&s1);
//测试修改
SeqListModify(&s1, SeqListFind(&s1, 1), 100);
SeqListPrint(&s1);
SeqListDestory(&s1);
}
int main()
{
test1();
return 0;
}
𝅫 结束语
数据结构之线性表中的顺序表就已经改进好了,希望这篇8000多字的博客能够帮助到你更加深刻的理解顺序表,以及顺序表的实现,创作不易,给博客来个三连叭!!!