预备知识:
C语言常见概念,循环,数组,函数,指针,结构体指针,动态内存管理
最简单的数据结构:数组
数组作为最简单的数据结构,具有存储数据的功能,但是使用数组存储数据时,无法动态的定义数组的长度,如果给小了,容易出现数组空间不够的情况,如果给大了,又有可能会导致空间的浪费,而且对数组中的元素进行插入删除等操作时比较麻烦。
顺序表的概念及结构
顺序表是线性表的一种,所谓线性表,就是指数据以连续的直线存储,顺序表在物理结构和逻辑结构上都是线性的。顺序表分为两种,静态顺序表和动态数据表,本文主要介绍动态数据表及其实现。
顺序表的实现
接下来将实现以下功能
//顺序表的初始化
void SLInit(SL* ps);
//顺序表的销毁
void SLDestroy(SL* ps);
//顺序表的打印
void SLPrint(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);
//指定位置之前插入
void SLInsert(SL* ps, int pos, SLDataType x);
//指定位置删除
void SLErase(SL* ps, int pos);
//元素查找
int SLFind(SL* ps, SLDataType x);
顺序表的创建
对于一个数组,插入和删除元素比较麻烦,但是对于线性表,这个问题就迎刃而解了。首先,我们先来创建一个顺序表SeqList,一个顺序表应包含一个指针,一个变量size记录有效的数据个数,另一个变量capacity记录当前顺序表的容量,并且是以结构体的方式来实现的。
例如,我需要创建一个int类型的顺序表用来存储int类型的数据
struct Seqlist
{
int* arr;
int size; //有效数据个数
int capacity; //顺序表容量
};
那么既然能存int类型的数据,当然也可以存储char类型,float类型,double类型的数据等,为了避免后期修改工程过于庞大,所以对顺序表的定义进行一些优化
typedef int SLDataType;
struct Seqlist
{
SLDataType* arr;
int size; //有效数据个数
int capacity; //顺序表容量
};
如此一来,当我们想要改变数据类型时,只需要改变typedef后的类型,就可以改变之后所有使用到的类型(注:size 和 capacity 并不需要用SLDataType代替,因为不管前面的变量怎么变这两个变量都是用来计数的,所以用int类型)
同理,我们将结构体也进行typedef定义
typedef int SLDataType;
typedef struct Seqlist
{
SLDataType* arr;
int size; //有效数据个数
int capacity; //顺序表容量
}SL;
顺序表的初始化
当我们传入结构体指针 *ps时,第一个问题就是传入的指针不能是NULL,否则就会报错,
所以第一步就要进行assert断言。然后我们对两个计数器置零,对指针置空,这样初始化就完成了。
//顺序表的初始化
void SLInit(SL* ps)
{
assert(ps);
ps->size = ps->capacity = 0;
ps->arr = NULL;
}
顺序表的尾插
首先,还是要进行assert断言,判断是否尾空指针。
在插入数据前,我们要先判断是否有足够的空间来插入数据,如果空间不够,我们就要手动来开辟空间来存放数据,那么就存在一个问题,如果空间不够,我们应该开辟多大的空间呢?如果开辟的过多,会造成空间的浪费,如果开辟的太少,又有可能导致空间不足。一般来说,开辟的空间为原空间的2~3倍左右是最佳的。
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
if (ps->size == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLdataType* temp = (SLdataType*)realloc(ps->arr, newcapacity * sizeof(SLDataType);
assert(temp);
}
}
当有效的数据个数已经达到了顺序表容量的时候,我们就要对顺序表进行扩容
此时,如果直接对capacity进行操作会改变原来capacity的值,所以我们建立一个新的变量来表示
需要扩容的数量,如果capacity的值为0,那么我们将其置为4,反之,我们将其空间乘2。
在确定了需要扩容的数量后,我们使用realloc进行空间开辟,由于realloc的返回值为void* 类型,所以我们将其强制类型转化为我们自定义的类型SLDataType* ,此外,需要注意realloc的空间开辟有可能会失败,当空间开辟失败时会返回空指针,所以要对temp进行assert断言。
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
if (ps->size == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLdataType* temp = (SLdataType*)realloc(ps->arr, 2 * newcapacity * sizeof(SLDataType);
assert(temp);
ps->arr = temp; //将开辟好的空间赋给arr
ps->capacity = newcapacity;
}
ps->arr[ps->size++] = x;
}
在开辟好空间之后,让arr去接收这个空间,并且更新新的容量newcapacity,再将需要写入的数据写入创建好的数组,最后让size自增。
顺序表的销毁
在使用完顺序表后,需要将其进行销毁
void SLDestroy(SL* ps)
{
assert (ps);
free(ps->arr);
ps = NULL;
ps->capacity = ps->size = 0;
}
首先进行assert断言,在之前我们使用realloc函数开辟了一块空间,那么在销毁的时候就要对空间释放归还给操作系统, 然后将ps置为空指针,此处应该free(ps->arr)而不是free(ps)因为ps中只有arr开辟了新的空间,而size和capacity并没有申请空间。然后将两个计数器归零即可。
顺序表中元素的打印
void SLPrint(SL ps)
{
for (int i = 0; i < ps.size; i++)
{
printf("%d", ps.arr[i]);
}
printf("\n");
}
此处直接传入结构体,通过循环打印顺序表中的元素。
顺序表的头插
不同于尾插,头插需要将所有的数字向后移动一位,此时又需要判断空间是否足够,所以我们可以将空间判断作为一个单独的函数,每次使用调用即可。
顺序表的空间容量检查并扩容
void SLCheckCapacity(SL* ps)
{
assert(ps);
if (ps->size == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDataType* temp = (SLDataType*)realloc(ps->arr, sizeof(SLDataType) * newcapacity;
ps->arr = temp;
ps->capacity = newcapacity;
}
}
头插操作
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
for (int i = ps->size; i >= 0; --i)
{
ps->arr[i + 1] = ps->arr[i];
}
ps->arr[0] = x;
ps->size++;
}
在检查完空间后,我们从最后一位开始依次往前吧把前一位的数赋给后一位,实现后移,最后在第一位插入需要插入的数字,最后将有效位数size自增。
顺序表的尾部删除
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size);
ps->size--;
}
在尾部删除时,首先断言ps,在尾删时,我们要确保顺序表中又元素,所以需要size不能为0,要删除一个数据,一种方式是将数据直接删除,另一种是将该数据标记为可覆盖空间,也就是直接将size--,之后再往顺序表中插入数据时,相当于直接覆盖了需要删除的数据。
顺序表的头部删除
void PspFront(SL* ps, SLDataType x)
{
assert(ps);
for (int i = 1; i < ps->size; i++)
{
ps-arr[i - 1] = ps->arr[i];
}
ps->size--;
}
对于头删,我们只需要把第二个数据覆盖到第一个数据,依次直到最后。
在指定位置之前插入
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
assert(pos >= 0 && pos <= ps->capacity); //检查需要插入的位置
for (int i = ps->size; i > pos; i++)
{
ps-arr[i] = ps-arr[i - 1];
}
ps->arr[pos] = x;
ps->size++;
}
首先我们要保证插入的位置是在我们开辟的空间capacity内,然后将和头插的方法类似,只不过头插是从第一个数开始往后移动,而这个是在指定位置之后的数往后移动。
在指定位置删除数据
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 & pos < ps->size);
for (int i = pos; i < ps->size; i++)
//ps->arr[i - 1] = ps->arr[i];//第一位是记为1
ps->arr[i] = ps->arr[i + 1];//数组位置,第一位是0
}
ps->size--;
}
和插入数据略有不同,在删除数据是,应该要使pos小于size,因为删除只能删除已有的数据。通过循环将需要删除的数据进行覆盖(此处给出两种位置),最后别忘了有效个数size--。
元素查找
int SLFind(SL* ps, SLDataType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (x == ps->arr[i])
{
return i;
}
}
return -1;
}
查找元素只需要对数组进行遍历即可,返回-1表示没找到。
源代码
头文件SeqList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDataType;
typedef struct Seqlist
{
SLDataType* 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, SLDataType x);
//顺序表的尾部删除
void SLPopBack(SL* ps);
//顺序表的头部插入
void SLPushFront(SL* ps, SLDataType x);
//顺序表的头部删除
void SLPopFront(SL* ps);
//指定位置之前插入
void SLInsert(SL* ps, int pos, SLDataType x);
//指定位置删除
void SLErase(SL* ps, int pos);
//元素查找
int SLFind(SL* ps, SLDataType x);
函数实现文件SeqList.c
#include"SeqList.h"
int i = 0;//全局变量
void SLInit(SL* ps)
{
assert(ps);
ps->capacity = ps->size = 0;
ps->arr = NULL;
}
void SLDestroy(SL* ps)
{
assert(ps);
free(ps->arr);
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
void SLPrint(SL ps)
{
for (i = 0; i < ps.size; i++)
{
printf("%d ", ps.arr[i]);
}
printf("\n");
}
void SLCheckCapacity(SL* ps)
{
assert(ps);
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
if (ps->size == ps->capacity)
{
SLDataType* temp = (SLDataType*)realloc(ps->arr, newcapacity * sizeof(SLDataType));
assert(temp);
ps->arr = temp;
ps->capacity = newcapacity;
}
}
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
ps->arr[ps->size++] = x;
}
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size);
--ps->size;
}
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
for (i = ps->size; i >= 0; i--)
{
ps->arr[i + 1] = ps->arr[i];
}
ps->arr[0] = x;
ps->size++;
}
void SLPopFront(SL* ps)
{
assert(ps);
for (i = 1; i < ps->size; i++)
{
ps->arr[i - 1] = ps->arr[i];
}
--ps->size;
}
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
assert(pos <= ps->capacity && pos >= 0);//检查需要插入的位置
for (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 (i = pos; i < ps->size; i++)
{
//ps->arr[i - 1] = ps->arr[i];//第一位记为1
ps->arr[i] = ps->arr[i + 1];//数组位置,第一位是0
}
--ps->size;
}
int SLFind(SL* ps, SLDataType x)
{
assert(ps);
for (i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
return i;
}
}
return -1;
}
测试代码test.c
#include"SeqList.h"
void SqLtest()
{
SL st;
SLInit(&st);//创建
for (int i = 1; i <= 10; i++)
{
SLPushBack(&st, i);//插入十个元素
}
SLPrint(st);
SLPushFront(&st, 0);//头部插入
SLPrint(st);
SLPopBack(&st);//尾删
SLPrint(st);
SLPopFront(&st);//头删
SLPrint(st);
SLInsert(&st, 1, 2);//指定位置插入
SLPrint(st);
SLErase(&st, 1);//指定位置删除
SLPrint(st);
int find = 0;
printf("请输入要查找的数字\n");
scanf("%d", &find);
int ret = SLFind(&st, find);
if (ret >= 0)
printf("找到了,在第%d位", find);
else
printf("没找到!");
SLDestroy(&st);//销毁
}
int main()
{
SqLtest();
return 0;
}
测试结果