目录
一、什么是顺序表?
二、顺序表的动态实现
1、顺序表初始化
2、顺序表打印
3、顺序表检查空间
4、顺序表尾插
5、顺序表尾删
6、顺序表头插
7、顺序表头删
8、顺序表指定位置插入
9、顺序表指定位置删除
10、顺序表查找
11、顺序表销毁
三、源代码
1、SeqList.h
2、SeqList.c
3、test.c
一、什么是顺序表?
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存 储。在数组上完成数据的增删查改。
顺序表的结构如下:
顺序表一般分为两种:
1、静态顺序表:使用定长数组存储元素。
#define Max 10
typedef int SLDataType;
typedef struct SeqList
{
SLDataType data[Max]; //定长数组
size_t size; //有效数据的个数
}SeqList;
2、动态顺序表:使用动态开辟的数组存储。
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a; // 指向动态开辟数组的指针
size_t size; // 有效数据个数
size_t capicity; // 容量空间的大小
}SeqList;
本文主要使用的是动态顺序表,因为静态顺序表是事先已经设置好数组空间的大小,万一需要的空间大小比事先设置的大,此时就会出现溢出的情况,使用不方便,所以在这里使用动态顺序表,用多少开辟多少的数组空间。
二、顺序表的动态实现
1、顺序表初始化
//初始化
void SLInit(SL* ps)
{
assert(ps);
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
初始化的时候可以给顺序表开辟空间,当然也可以直接将指针a给设置为NULL,size和capacity同样设置为0。
2、顺序表打印
//打印
void SLPrint(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
3、顺序表检查空间
在顺序表插入和删除的时候会涉及到顺序表空间大小的问题,在插入的时候首先要判断一下,空间是否已满,满的情况要继续开辟空间,才能够插入。
void SLCheckCapacity(SL* ps)
{
assert(ps);
//扩容
if (ps->size == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapacity * sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity = newCapacity;
}
}
在这里之所以将检查封装为一个检查空间的函数,就是为了后面在插入的时候方便使用。
这个顺序表在刚创建的时候,其容量是0,此时我们就给它开辟4个元素大小的空间,当容量满的时候,我们就直接将容量翻倍。当我们在realloc函数中传入的是一个空指针的时候,此时的realloc函数就相当于一个malloc函数,所以在这里直接使用realloc函数也是可以的。
4、顺序表尾插
//尾插 O(1)
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
首先我们要检查一下数组空间的大小,如果容量不满可以直接插入,如果容量已满就需要先扩容,在进行插入。 SLCheckCapacity()这个函数就是要检查容量是否已满,满的情况就会立即扩容。
5、顺序表尾删
//尾删
void SLPopBack(SL* ps)
{
assert(ps);
///暴力检查
assert(ps->size > 0);
ps->size--;
}
尾删其实只用size--就行,但是要注意一个问题就是在size = 0 的时候,顺序表本身就为空,那么就不能再减了,防止越界访问,因此在这里面加了两个断言就可以防止这种情况的发生。
6、顺序表头插
//头插 O(N)
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++;
}
头插首先肯定也要检查容量的大小, 保证容量大小充足,然后再将存入的数据整体向后移动一位,将第一个位置给空出来,最后插入新的元素。
7、顺序表头删
//头删
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size > 0);
//挪动数据
int begin = 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
begin++;
}
//暴力检查
ps->size--;
}
头删和尾删的大体逻辑是相同的,我们首先需要检查一下size是否为0。接下来的操作我们只需要让后面的数据覆盖前面的数据,最终就完成了头删的效果。
8、顺序表指定位置插入
任意位置插入就像是头插和尾插的升级版,只不过多了一个参数pos,如果pos在开始位置,相当于头插,如果pos在最后面,就相当于尾插。
//在pos的位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0);
assert(pos <= ps->size);
//检查容量够不够
SLCheckCapacity(ps);
int end = ps->size - 1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[pos] = x;
ps->size++;
}
假设插入位置的下标是pos,那么我们只需要将pos位置到最后的所有数据整体向后移动,然后把pos的位置空出来,再在pos的位置插入一个新的元素,最后size++。
注意:assert(pos >= 0);assert(pos <= ps->size);这两句代码就是限制插入的元素是在0到size之间,而不是在其他地方,保证元素与元素之间是连续存储的。
9、顺序表指定位置删除
//在pos的位置删除数据
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0);
assert(pos < ps->size);
//挪动数据覆盖
int begin = pos + 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
begin++;
}
ps->size--;
}
首先需要断言一下size是否为0。然后将pos后面的数据整体向前移动,覆盖原来pos位置所在的数据,最终size–。
10、顺序表查找
int SLFind(SL* ps, SLDataType x, int begin)
{
assert(ps);
for (int i = begin; i < ps->size; ++i)
{
if (ps->a[i] == x)
{
return i;
}
}
return -1;
}
这个函数接口就很简单了,只需遍历一遍数组就行,找到就返回元素的下标,找不到返回-1。
11、顺序表销毁
//销毁
void SLDestory(SL* ps)
{
assert(ps);
//if (ps->a != NULL)
if (ps->a)
{
free(ps->a);
ps->a = NULL;
ps->capacity = 0;
ps->size = 0;
}
}
我们开始的时候动态开辟的空间是在堆区申请的,我们用完之后一定要将它释放,防止内存泄漏。
三、源代码
1、SeqList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//动态的顺序表 - 按需扩空间
#define N 10
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a;
int size;//记录存储多少个有效数据
int capacity;//空间容量大小
}SL;
void SLPrint(SL* ps);//打印
void SLInit(SL* ps);//初始化
void SLDestory(SL* ps);//销毁
void SLCheckCapacity(SL* ps);//检查空间大小
//尾插
void SLPushBack(SL* ps, SLDataType x);
//尾删
void SLPopBack(SL* ps);
//头插
void SLPushFront(SL* ps, SLDataType x);
//头删
void SLPopFront(SL* ps);
//中间的插入
//在pos位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x);
//中间的删除
//删除pos位置的数据
void SLErase(SL* ps, int pos);
//查找
int SLFind(SL* ps, SLDataType x);
2、SeqList.c
#include "SeqList.h"
//打印
void SLPrint(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
//初始化
void SLInit(SL* ps)
{
assert(ps);
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
//销毁
void SLDestory(SL* ps)
{
assert(ps);
//if (ps->a != NULL)
if (ps->a)
{
free(ps->a);
ps->a = NULL;
ps->capacity = 0;
ps->size = 0;
}
}
void SLCheckCapacity(SL* ps)
{
assert(ps);
//扩容
if (ps->size == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapacity * sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity = newCapacity;
}
}
//尾插 O(1)
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
//尾删
void SLPopBack(SL* ps)
{
assert(ps);
//暴力检查
assert(ps->size > 0);
ps->size--;
}
//头插 O(N)
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);
//挪动数据
int begin = 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
begin++;
}
//暴力检查
ps->size--;
}
//在pos的位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0);
assert(pos <= ps->size);
//检查容量够不够
SLCheckCapacity(ps);
int end = ps->size - 1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[pos] = x;
ps->size++;
}
//在pos的位置删除数据
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0);
assert(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);
for (int i = 0; i < ps->size; ++i)
{
if (ps->a[i] == x)
{
//找到
return i;
}
}
//没找到
return -1;
}
3、test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
void menu()
{
printf("*******************************\n");
printf("****** 0.退出 1.打印 ******\n");
printf("****** 2.尾插 3.尾删 ******\n");
printf("****** 4.头插 5.头删 ******\n");
printf("****** 6.任意插 7.任意删******\n");
printf("****** 8.在pos位置插入 ******\n");
printf("****** 9.删除pos元素值 ******\n");
printf("*******************************\n");
}
int main()
{
int input = 1;
SL s;
SLInit(&s); //创建一个顺序表
while (input)
{
int pos = 0;
SLDatatype x = 0;
SLDatatype y = 0;
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 0:
printf("退出顺序表!\n");
SLDestroy(&s);
break;
case 1:
SLPrint(&s);
break;
case 2:
printf("请输入一个值:>");
scanf("%d", &x);
SLPushBack(&s, x);
break;
case 3:
SLPopBack(&s);
break;
case 4:
printf("请输入一个值:>");
scanf("%d", &x);
SLPushFront(&s, x);
break;
case 5:
SLPopFront(&s);
break;
case 6:
printf("请输入下标和目标值:>");
scanf("%d %d", &pos, &x);
SLInsert(&s, pos, x);
break;
case 7:
printf("请输入下标:>");
scanf("%d", &pos);
SLErase(&s, pos);
break;
case 8:
printf("请输入要插入元素值和目标值:>");
scanf("%d %d", &y, &x);
SLInsert(&s, SeqListFind(&s, y), x);
break;
case 9:
printf("请输入要删除元素值:>");
scanf("%d", &y);
SLErase(&s, SeqListFind(&s, y));
break;
default:
printf("选择错误,请重新选择!\n");
break;
}
}
return 0;
}
本文要是有不足的地方,欢迎大家在下面评论,我会在第一时间更正。