一、顺序表概述
顺序表是我们学完C语言后,接触的第一种数据结构,其逻辑上、物理上(底层)都是线性结构,其底层逻辑是数组(数组地址连续,所以是线性的),但对数组加工包装,更加高效的实现数据管理。其主要功能是:数据增加、数据删除、数据修改、数据查找(增删改查,以后的各种类型数据结构都主要围绕此四大方面展开)。总体功能如下:
//定义数据类型,方便类型替换
typedef int SLDatatype;
//定义动态顺序表结构
typedef struct SeqList
{
SLDatatype* a;
int capicity;//空间容量
int size;//有效数据个数
}SL;
//初始化
void SLInit(SL* ps);
//销毁
void SLDestroy(SL* ps);
//打印数据
void SLPrint(SL* ps);
//查找数据
void SLFind(SL* ps, SLDatatype x);
//插入数据
void SLPushBack(SL* ps,SLDatatype x);//后插
void SLPushFront(SL* ps,SLDatatype x);//前插
void SLInsert(SL* ps, SLDatatype x, int pos);//指定位置插入元素
//删除数据
void SLPopBack(SL* ps);//后删
void SLPopFront(SL* ps);//前删
void SLErase(SL* ps, int pos);//指定位置删除元素
//修改数据
void SLModif(SL* ps, int pos, SLDatatype x);
二、定义顺序表结构
顺序表结构主要由三部分组成:数据类型的指针、空间容量、有效数据;
(1)数据类型的指针:
这里不直接定义数据的类型为int、char是为了以后数据类型的修改方便。所以我们利用typedef来定义数据类型,如:typedef int SLDataType,如果以后数据类型需要需改只需要将int改为其他数据类型,一了百了。注意:这里是SLDataType *a,而不是SL*a,a是各种数据类型的指针,用来存储数据,而不是结构体指针,SL *a就类似于链表了。。。
(2)空间容量
我们创建的顺序表是动态的(指针),指针指向的地址有一个空间容量,在这里我们写为:capicity,容量肯定为整数,所以是int类型。之后的插入操作需要考虑空间是否充足,如果容量不够的话,需要扩容操作,后面会讲。
(3)有效数据
这里的有效数据个数size是最后一个数据的位置(类似于int a[n]中的n,取不到),是为了之后查找数据、指定位置插入数据限定一个范围。
总代码如下:
//定义数据类型,方便类型替换
typedef int SLDatatype;
//定义动态顺序表结构
typedef struct SeqList
{
SLDatatype* a;
int capicity;//空间容量
int size;//有效数据个数
}SL;
三、顺序表初始化
刚才已经定义了顺序表结构,其结构体中包括数据类型指针、空间容量、有效数据,我们需要进行初始化操作,避免产生野指针,主要就就是将指针指向NULL,空间容量、有效数据赋0;代码如下:
//初始化
void SLInit(SL* ps)
{
ps->a = NULL;
ps->capicity = 0;
ps->size = 0;
}
四、顺序表打印
这里主要要强调一下传过来的指针不能为空,要不然ps->a(此时ps=NULL)操纵是不合法的,所以需要添加assert函数(头文件 assert.h不要忘记引用),当指针为空时断言报错提醒。其代码如下:
//打印数据
void SLPrint(SL* ps) {
assert(ps);
for (int i = 0; i < ps->size; i++)
printf("%d ", ps->a[i]);
printf("\n");
}
五、 数据查找
数据查找主要依靠一个循环,将数据都遍历一遍,这里可以考虑引入一个变量j,记录所要查找的数据位置(所要查找的数据可能不止一个)。代码如下:
//查找数据
void SLFind(SL* ps, SLDatatype x)
{
int j = 1;
int p = 1;
for (int i = 0; i < ps->size; i++) {
if (ps->a[i] == x)
{
printf("查找的数据第%d处位置为:第%d个数据", j, i + 1);
printf("\n");
j++;
p = 0;
}
}
if (p == 1)
printf("没有此数据!");
}
将printf中的i+1写为j也是一样的。
问:printf里面也有i+1,for循环里有i++,那i一次循环不是+2吗,有的数据是否没有考虑到?
答:其实printf里面的i+1只在printf中有效,当执行完i+1后便销毁了,i的值在printf执行完毕后值不会改变,自己可以测试一下。
六、插入数据
在插入数据之前,我们需要考虑原来的空间是否足够,如果原来空间满了,是插入不了的(类似int a[n] 已经存了n个数据了),这时候需要我们进行扩容操纵,我们将此操作封装SLCheckCapacit函数中,先看代码:
//插入操作前判断空间是否充足
void SLCheckCapacity(SL* ps)
{
if (ps->capicity == ps->size)
{
int NewCapicity = ps->capicity == 0 ? 4 : 2 * ps->capicity;
SLDatatype* tmp = (SLDatatype*)realloc(ps->a, NewCapicity * sizeof(SLDatatype));
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);
}
ps->a = tmp;
ps->capicity = NewCapicity;
}
利用ps->capicity==ps->size这个条件即可判断原来的空间是否满了,如果满了,需要重新开辟一块空间将此空间地址传给a指针。我们计算新空间的大小一般为原来空间的2倍,如果太大,会造成空间浪费,太小的话插入数据如果较多需要多次进行扩容操作,这会使计算机运行效率变低、性能变低等,所以一般取2倍。注意:原来空间没说不可以为0,所以一定要提前判断!代码中int NewCapicity = ps->capicity == 0 ? 4 : 2 * ps->capicity;就是此作用。开辟一块新地址我们这里用realloc。问:为什么不用malloc函数,而用realloc函数,二者有什么区别?
答:简单来讲:malloc 只负责分配指定字节数的内存块,不对其进行初始化;realloc 用于重新分配内存块的大小,并尝试保留原始内存块中的数据;我们这里开辟新的空间需要保留原有地址的数据,所以用realloc。
1、尾插操作
尾插需要考虑空间是否足够,所以我们需要调用一下SLCheckCapacity函数;既然是插入操作,那原指针的地址肯定不能为空,需要加入assert断言(后面的头插,指定位置插入都需要断言)。代码如下:
//尾插
void SLPushBack(SL* ps, SLDatatype x)
{
assert(ps);
//判断空间是否足够
SLCheckCapacity(ps);
ps->a[ps->size++] = x;
}
别忘记size++,有效数据需要+1。问:ps->a[ps->size++]=x中执行顺序?
答:由于size++是后置++,所以先执行语句后++,即ps->a[ps->size]=x执行完毕后,ps->size++。
2、头插操作
头插操作需要一个while循环,将前面之前所有的数据都往后挪一个,最后别忘记ps->size++。代码如下:
//头插
void SLPushFront(SL* ps, SLDatatype x)
{
assert(ps);
//判断空间是否足够
SLCheckCapacity(ps);
int i = ps->size;
while (i> 0)
{
ps->a[i] = ps->a[i-1];
i--;
}
ps->a[0] = x;
ps->size++;
}
3、指定位置插入数据
首先需要考虑原来的顺序表不能为空,指定数据的位置不能为空,所以引用 assert(ps);
assert(pos >= 0 && pos <= ps->size);往指定位置插入数据需要将指定位置之后的数据全部往后挪一个,这里也需要一个循环,注意循环结束的条件为:i>pos(int i = ps->size;)。代码如下:
void SLInsert(SL* ps, SLDatatype x, int pos)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
int i = ps->size;
while ( i>pos)
{
ps->a[i ] = ps->a[i-1];
i--;
}
ps->a[pos] = x;
ps->size++;
}
ps->size++别忘记!
七、删除数据
所有删除数据都要assert判断,删除数据,原来数不能为空。以及进行ps->size--,不能忘记。
1、数据尾删
尾删最简单,将ps->size--即可。代码如下:
/尾删
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size);
ps->size--;
}
2、数据头删
头删操作将所有数据从后往前覆盖即可,代码如下:
//头删
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size);
for (int i = 0; i < ps->size; i++)
{
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
问:为什么这里需要考虑assert(ps->size)?
答:ps->size为0了,哪里有数据删。
3、指定位置删除数据
原理简单,需要一个循环,先找到指定位置,将指定位置之后的所有数据向前覆盖即可。代码如下:
//删除指定位置元素
void SLErase(SL* ps, int pos) {
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
for (int i = pos ; i < ps->size; i++)
{
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
八、修改数据
一种是修改指定位置的数据,一种是修改所有等于此值的数据,其代码如下:
//修改数据
void SLModif(SL* ps, int pos,SLDatatype x)
{
int y = 0;
printf("1、将某一数据全部修改为新数据;\n");
printf("*****2、修改指定位置数据*******\n");
printf("请输入你的操作:");
int input=scanf("%d", &y);
int m = 0;
if(input==1)
{
SLPrint(ps);
printf("请输入你要修改的数字:");
scanf("%d", &m);
for (int i = 0; i < ps->size; i++)
{
if (ps->a[i] == m)
ps->a[i] = x;
}
}
else if (input == 2)
{
if (pos >= 0 && pos < ps->size)
ps->a[pos] = x;
}
else
printf("输入无效,请重新输入:");
}
九、销毁操作
顺序表的实现指针指向了新的地址,在完成操作后,需要将这些地址销毁,以免内存泄漏等问题,将指针指向NULL。代码如下:
//销毁
void SLDestroy(SL* ps) {
if (ps->a)
{
free(ps->a);
}
ps->capicity = ps->size = 0;
ps->a = NULL;
}
十、总代码
将代码分装到三个文件,一个头文件SeqList.h,实现操纵的文件SeqList.c,测试文件test.c。
头文件SeqList.h主要定义顺序表结构和各种操作函数的声明。
实现操纵的文件SeqList.c主要敲各种代码,实现各种功能。
测试文件test.c对SeqList.c中各种函数进行测试。
1、SeqList.h
代码如下:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//定义指针类型,方便类型替换
typedef int SLDatatype;
//定义动态顺序表结构
typedef struct SeqList
{
SLDatatype* a;
int capicity;//空间容量
int size;//有效数据个数
}SL;
//初始化
void SLInit(SL* ps);
//销毁
void SLDestroy(SL* ps);
//打印数据
void SLPrint(SL* ps);
//查找数据
void SLFind(SL* ps, SLDatatype x);
//插入数据
void SLPushBack(SL* ps,SLDatatype x);//后插
void SLPushFront(SL* ps,SLDatatype x);//前插
void SLInsert(SL* ps, SLDatatype x, int pos);//指定位置插入元素
//删除数据
void SLPopBack(SL* ps);//后删
void SLPopFront(SL* ps);//前删
void SLErase(SL* ps, int pos);//指定位置删除元素
//修改数据
void SLModif(SL* ps, int pos, SLDatatype x);
2、SeqList.c
代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
//初始化
void SLInit(SL* ps)
{
ps->a = NULL;
ps->capicity = 0;
ps->size = 0;
}
//销毁
void SLDestroy(SL* ps) {
if (ps->a)
{
free(ps->a);
}
ps->capicity = ps->size = 0;
ps->a = NULL;
}
//打印数据
void SLPrint(SL* ps) {
assert(ps);
for (int i = 0; i < ps->size; i++)
printf("%d ", ps->a[i]);
printf("\n");
}
//查找数据
void SLFind(SL* ps, SLDatatype x)
{
int j = 1;
int p = 1;
for (int i = 0; i < ps->size; i++) {
if (ps->a[i] == x)
{
printf("查找的数据第%d处位置为:第%d个数据", j, i + 1);
printf("\n");
j++;
p = 0;
}
}
if (p == 1)
printf("没有此数据!");
}
//插入操作前判断空间是否充足
void SLCheckCapacity(SL* ps)
{
if (ps->capicity == ps->size)
{
int NewCapicity = ps->capicity == 0 ? 4 : 2 * ps->capicity;
SLDatatype* tmp = (SLDatatype*)realloc(ps->a, NewCapicity * sizeof(SLDatatype));
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);
}
ps->a = tmp;
ps->capicity = NewCapicity;
}
}
//插入数据
//尾插
void SLPushBack(SL* ps, SLDatatype x)
{
assert(ps);
//判断空间是否足够
SLCheckCapacity(ps);
ps->a[ps->size++] = x;
}
//头插
void SLPushFront(SL* ps, SLDatatype x)
{
assert(ps);
//判断空间是否足够
SLCheckCapacity(ps);
int i = ps->size;
while (i> 0)
{
ps->a[i] = ps->a[i-1];
i--;
}
ps->a[0] = x;
ps->size++;
}
//指定位置插入数据
void SLInsert(SL* ps, SLDatatype x, int pos)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
int i = ps->size;
while ( i>pos)
{
ps->a[i ] = ps->a[i-1];
i--;
}
ps->a[pos] = 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; i++)
{
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
//删除指定位置元素
void SLErase(SL* ps, int pos) {
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
for (int i = pos ; i < ps->size; i++)
{
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
//修改数据
void SLModif(SL* ps, int pos,SLDatatype x)
{
int y = 0;
printf("1、将某一数据全部修改为新数据;\n");
printf("*****2、修改指定位置数据*******\n");
printf("请输入你的操作:");
int input=scanf("%d", &y);
int m = 0;
if(input==1)
{
SLPrint(ps);
printf("请输入你要修改的数字:");
scanf("%d", &m);
for (int i = 0; i < ps->size; i++)
{
if (ps->a[i] == m)
ps->a[i] = x;
}
}
else if (input == 2)
{
if (pos >= 0 && pos < ps->size)
ps->a[pos] = x;
}
else
printf("输入无效,请重新输入:");
}
3、test.c
代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"
void SLtext1() {
SL s;
SLInit(&s);
//尾插测试
SLPushBack(&s, 1);
SLPushBack(&s, 2);
SLPushBack(&s, 3);
SLPushBack(&s, 4);
SLPushBack(&s, 5);
SLPushBack(&s, 5);
SLPushBack(&s, 6);//1 2 3 4 5 6
// //SLPushBack(NULL, 6);//报错
SLPrint(&s);
//头插测试
//SLPushFront(&s, 1);
//SLPushFront(&s, 2);
//SLPushFront(&s, 3);
//SLPushFront(&s, 4);
//SLPrint(&s); //4 3 2 1
//指定位置插入
//SLInsert(&s, 11, 0);
//SLPrint(&s);
//SLInsert(&s, 22, s.size);
//SLPrint(&s);
//SLInsert(&s, 33, 1);
//SLPrint(&s);
//查找数据
SLFind(&s, 5);
//修改数据
SLModif(&s, 1, 5);
SLPrint(&s);
//尾删测试
SLPopBack(&s);
SLPrint(&s);
//头删测试
SLPopFront(&s);
SLPrint(&s);
//指定位置删除数据
SLErase(&s, 2);
SLPrint(&s);
SLDestroy(&s);
}
int main() {
SLtext1();
return 0;
}
有问题多多指教。