目录
这篇文章主要来讲一下模拟实现顺序表,那么什么是顺序表呢?我们先来看一下它的概念
顺序表是用一块物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成增删改查。
顺序表又分为静态顺序表与动态顺序表,静态顺序表是用定长的数组存储的, 在现实场景中数组大小具有不确定性,定长的话会造成空间不够用或者浪费,因此我们多采用动态顺序表,这篇文章也将采用动态顺序表讲述。
先创建一个结构体 struct SeqList 用于存储顺序表的信息, typedef 定义为 SeqList 方便后续使用,结构如下
typedef int SLDateType; //将数组中存储的数据类型重定义,若以后要将int型改为char型,
在此处修改即可
typedef struct SeqList
{
SLDateType* a; //指向动态开辟的数组
int size; //顺序表当前存储数据个数
int capacity; //顺序表当前存储容量
}SeqList;
顺序表基本增删查改的接口有如下函数,接下来我们将一一实现
//顺序表的初始化
void SeqListInit(SeqList* ps);
//顺序表的销毁
void SeqListDestroy(SeqList* ps);
//顺序表的打印
void SeqListPrint(SeqList* ps);
//检查容量
void SeqCheckCapacity(SeqList* ps);
//顺序表的尾插
void SeqListPushBack(SeqList* ps, SLDateType x);
//顺序表的头插
void SeqListPushFront(SeqList* ps, SLDateType x);
//顺序表的头删
void SeqListPopFront(SeqList* ps);
//顺序表的尾删
void SeqListPopBack(SeqList* ps);
// 顺序表查找
int SeqListFind(SeqList* ps, SLDateType x);
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SLDateType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, int pos);
顺序表的初始化 SeqListInit
给数组开辟空间,同时将当前顺序表存储数据个数置为0,容量置为4
如果开辟空间失败,那么打印错误原因并返回-1
void SeqListInit(SeqList* ps)
{
ps->a = (SLDateType*)malloc(sizeof(SLDateType) * 4);
if (ps->a == NULL)
{
perror("malloc");
exit(-1);
}
ps->size = 0;
ps->capacity = 4;
}
顺序表的销毁SeqListDestroy
void SeqListDestroy(SeqList* ps)
{
assert(ps);
if (ps->a != NULL)
{
free(ps->a);
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
}
顺序表的打印SeqListPrint
为了便于后面函数的检验,我们先实现打印函数
void SeqListPrint(SeqList* ps)
{
assert(ps);
int i = 0;
for (i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
接下来我们要来实现插入删除等函数,在插入数据时不是直接插入,还应判断空间是否足够,不够的话扩容后再执行插入操作。
我们先封装一个函数 SeqCheckCapacity 以实现检查内容及扩容的要求。当 size 值和 capacity 值相等时则扩容。我们采用 realloc 来实现扩充容量,设置一个新的指针,将扩充后的空间的地址赋给它。注意不要直接赋给 ps->a ,万一开辟失败就无法找到该空间了。
void SeqCheckCapacity(SeqList* ps)
{
assert(ps);
if (ps->size == ps->capacity)
{
int newcapacity = (ps->capacity) * 2;
SLDateType* new_ps = (SLDateType*)realloc(ps->a, sizeof(SLDateType) * newcapacity);
if (new_ps == NULL)
{
perror("realloc fail");
return ;
}
ps->a = new_ps;
ps->capacity = newcapacity;
}
return;
}
顺序表的尾插SeqListPushBack
尾插函数是比较容易实现的,它只需要在数组尾部添加一个数据,记得添加完后将 size 值加1
void SeqListPushBack(SeqList* ps, SLDateType x)
{
assert(ps);
//先判断size与capacity的大小,若空间不够,则申请空间
SeqCheckCapacity(ps);
//插入数据
ps->a[ps->size] = x;
ps->size++;
}
顺序表的头插SeqListPushFront
与尾插函数相比,执行头插操作时多了一个步骤,那就是将所有元素后移一位
void SeqListPushFront(SeqList* ps, SLDateType x)
{
assert(ps);
//先判断size与capacity的大小,若空间不够,则申请空间
SeqCheckCapacity(ps);
//先把所有元素后移一位
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end+1] = ps->a[end];
--end;
}
ps->a[0] = x;
ps->size++;
return;
}
顺序表的尾删SeqListPopBack
尾删操作也很简单,只需要将 size 值减 1 即可,因为 size 改变意味着数组大小改变,那么也就访问不到删除前的位置了
void SeqListPopBack(SeqList* ps)
{
assert(ps);
//直接删 size-1
assert(ps->size > 0);
ps->size--;
}
测试:
void test1()
{
SeqList s;
SeqListInit(&s);
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPushBack(&s, 4);
SeqListPushBack(&s, 5);
SeqListPrint(&s);
SeqListPopBack(&s);
SeqListPopBack(&s);
SeqListPopBack(&s);
SeqListPrint(&s);
}
int main()
{
test1(); //测试尾部插入删除
//test2(); //测试头部插入删除
//test3(); //测试查找函数
//test4(); //测试插入函数
//test5(); //测试删除函数
return 0;
}
顺序表的头删SeqListPopFront
头删操作只需将第一个数据后的所有数据前移,必须从前往后移动,否则会导致数组中数据全部相同
void SeqListPopFront(SeqList* ps)
{
assert(ps);
//元素前移,将其覆盖
int front = 0;
while (front <= ps->size - 1)
{
ps->a[front - 1] = ps->a[front];
front++;
}
ps->size--;
}
测试:
void test2()
{
SeqList s;
SeqListInit(&s);
SeqListPushFront(&s, 1);
SeqListPushFront(&s, 2);
SeqListPushFront(&s, 3);
SeqListPushFront(&s, 4);
SeqListPushFront(&s, 5);
SeqListPrint(&s);
SeqListPopFront(&s);
SeqListPopFront(&s);
SeqListPrint(&s);
}
顺序表查找SeqListFind
在执行前要对数组大小进行判断
int SeqListFind(SeqList* ps, SLDateType x)
{
assert(ps);
assert(ps->size > 0);
int i = 0;
for (i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
return i; //找到 返回下标
}
return -1; //未找到
}
测试:
void test3()
{
SeqList s;
SeqListInit(&s);
SeqListPushBack(&s, 11);
SeqListPushBack(&s, 22);
SeqListPushBack(&s, 33);
SeqListPushBack(&s, 44);
SeqListPushBack(&s, 55);
SeqListPrint(&s);
int x;
printf("输入要查找的值->");
scanf("%d", &x);
int ret = SeqListFind(&s, x);
printf("该数所在下标为->%d\n",ret);
}
顺序表在pos位置插入x SeqListInsert
(文章中 pos 所指非下标)
头插尾插是该函数的特殊情况,当在其他位置插入时我们需先判断插入位置是否合法,合法后再挪数据将其放入插入位置
void SeqListInsert(SeqList* ps, int pos, SLDateType x)
{
assert(ps);
assert(pos > 0 && pos <= ps->size); //插入位置要合法
SeqCheckCapacity(ps);
int end = ps->size - 1;
//挪数据
while (end >= pos-1)
{
ps->a[end + 1] = ps->a[end];
--end;
}
ps->a[pos-1] = x;
ps->size++;
}
测试:
void test4()
{
SeqList s;
SeqListInit(&s);
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPushBack(&s, 4);
SeqListPushBack(&s, 5);
SeqListPrint(&s);
int pos = 0;
int x = 0;
printf("输入要插入的位置以及要插入的数据->");
scanf("%d %d", &pos, &x);
SeqListInsert(&s, pos, x);
SeqListPrint(&s);
}
顺序表删除pos位置的值SeqListErase
同上面插入函数一样,也需判断位置的合法性 ,之后再进行挪数据操作
void SeqListErase(SeqList* ps, int pos)
{
assert(ps);
assert(pos > 0 && pos <= ps->size);//删除位置要合法
//挪动元素覆盖
int front = pos - 1;
while (front < ps->size)
{
ps->a[front] = ps->a[front + 1];
front++;
}
ps->size--;
}
测试:
void test5()
{
SeqList s;
SeqListInit(&s);
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPushBack(&s, 4);
SeqListPushBack(&s, 5);
SeqListPrint(&s);
int pos = 0;
printf("输入要删除的位置->");
scanf("%d", &pos);
SeqListErase(&s, pos);
SeqListPrint(&s);
}
源代码
1.SeqList.h
#pragma once
#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
typedef int SLDateType;
typedef struct SeqList
{
SLDateType* a;//指向动态开辟的数组
int size; //顺序表当前存储数据个数
int capacity; //顺序表当前存储容量
}SeqList;
//顺序表的初始化
void SeqListInit(SeqList* ps);
//顺序表的销毁
void SeqListDestroy(SeqList* ps);
//顺序表的打印
void SeqListPrint(SeqList* ps);
//检查容量
void SeqCheckCapacity(SeqList* ps);
//顺序表的尾插
void SeqListPushBack(SeqList* ps, SLDateType x);
//顺序表的头插
void SeqListPushFront(SeqList* ps, SLDateType x);
//顺序表的尾删
void SeqListPopBack(SeqList* ps);
//顺序表的头删
void SeqListPopFront(SeqList* ps);
// 顺序表查找
int SeqListFind(SeqList* ps, SLDateType x);
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SLDateType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, int pos);
2.Seqlist.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
//顺序表的初始化
void SeqListInit(SeqList* ps)
{
ps->a = (SLDateType*)malloc(sizeof(SLDateType) * 4);
if (ps->a == NULL)
{
perror("malloc");
exit(-1);
}
ps->size = 0;
ps->capacity = 4;
}
//顺序表的销毁
void SeqListDestroy(SeqList* ps)
{
assert(ps);
if (ps->a != NULL)
{
free(ps->a);
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
}
//顺序表的打印
void SeqListPrint(SeqList* ps)
{
assert(ps);
int i = 0;
for (i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
//顺序表的尾插
void SeqCheckCapacity(SeqList* ps)
{
assert(ps);
if (ps->size == ps->capacity)
{
int newcapacity = (ps->capacity) * 2;
SLDateType* new_ps = (SLDateType*)realloc(ps->a, sizeof(SLDateType) * newcapacity);
if (new_ps == NULL)
{
perror("realloc:fail");
return ;
}
ps->a = new_ps;
ps->capacity = newcapacity;
}
return;
}
void SeqListPushBack(SeqList* ps, SLDateType x)
{
assert(ps);
//先判断size与capacity的大小,若空间不够,则申请空间
SeqCheckCapacity(ps);
//插入数据
ps->a[ps->size] = x;
ps->size++;
}
//顺序表的头插
void SeqListPushFront(SeqList* ps, SLDateType x)
{
assert(ps);
//先判断size与capacity的大小,若空间不够,则申请空间
SeqCheckCapacity(ps);
//先把所有元素后移一位
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end+1] = ps->a[end];
--end;
}
ps->a[0] = x;
ps->size++;
return;
}
//顺序表的尾删
void SeqListPopBack(SeqList* ps)
{
assert(ps);
//直接删 size-1
assert(ps->size > 0);
ps->size--;
}
//顺序表的头删
void SeqListPopFront(SeqList* ps)
{
assert(ps);
//元素前移,将其覆盖
int front = 0;
while (front <= ps->size - 1)
{
ps->a[front - 1] = ps->a[front];
front++;
}
ps->size--;
}
// 顺序表查找
int SeqListFind(SeqList* ps, SLDateType x)
{
assert(ps);
assert(ps->size > 0);
int i = 0;
for (i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
return i; //找到 返回下标
}
return -1; //未找到
}
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SLDateType x)
{
assert(ps);
assert(pos > 0 && pos <= ps->size);//插入位置要合法
SeqCheckCapacity(ps);
int end = ps->size - 1;
//挪数据
while (end >= pos-1)
{
ps->a[end + 1] = ps->a[end];
--end;
}
ps->a[pos-1] = x;
ps->size++;
}
// 顺序表删除pos位置的值,pos不是下标
void SeqListErase(SeqList* ps, int pos)
{
assert(ps);
assert(pos > 0 && pos <= ps->size);//删除位置要合法
//挪动元素覆盖
int front = pos - 1;
while (front < ps->size)
{
ps->a[front] = ps->a[front + 1];
front++;
}
ps->size--;
}
3.test.c
#include"SeqList.h"
#include<stdio.h>
void test1()
{
SeqList s;
SeqListInit(&s);
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPushBack(&s, 4);
SeqListPushBack(&s, 5);
SeqListPrint(&s);
SeqListPopBack(&s);
SeqListPopBack(&s);
SeqListPopBack(&s);
SeqListPrint(&s);
}
void test2()
{
SeqList s;
SeqListInit(&s);
SeqListPushFront(&s, 1);
SeqListPushFront(&s, 2);
SeqListPushFront(&s, 3);
SeqListPushFront(&s, 4);
SeqListPushFront(&s, 5);
SeqListPrint(&s);
SeqListPopFront(&s);
SeqListPopFront(&s);
SeqListPrint(&s);
}
void test3()
{
SeqList s;
SeqListInit(&s);
SeqListPushBack(&s, 11);
SeqListPushBack(&s, 22);
SeqListPushBack(&s, 33);
SeqListPushBack(&s, 44);
SeqListPushBack(&s, 55);
SeqListPrint(&s);
int x;
printf("输入要查找的值->");
scanf("%d", &x);
int ret = SeqListFind(&s, x);
printf("该数所在下标为->%d\n",ret);
}
void test4()
{
SeqList s;
SeqListInit(&s);
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPushBack(&s, 4);
SeqListPushBack(&s, 5);
SeqListPrint(&s);
int pos = 0;
int x = 0;
printf("输入要插入的位置以及要插入的数据->");
scanf("%d %d", &pos, &x);
SeqListInsert(&s, pos, x);
SeqListPrint(&s);
}
void test5()
{
SeqList s;
SeqListInit(&s);
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPushBack(&s, 4);
SeqListPushBack(&s, 5);
SeqListPrint(&s);
int pos = 0;
printf("输入要删除的位置->");
scanf("%d", &pos);
SeqListErase(&s, pos);
SeqListPrint(&s);
}
int main()
{
test1(); //测试尾部插入删除
test2(); //测试头部插入删除
test3(); //测试查找函数
test4(); //测试插入函数
test5(); //测试删除函数
return 0;
}