数据结构就是定义出某种结构:像数组结构、链表结构、树形结构等,实现数据结构就是我们主动去管理增删查改的实现函数
顺序表的概念
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改
下面在vs环境下编程实现动态顺序表,这里我们分文件编写,在头文件 SeqList.h进行声明,在 SeqList.c 当中进行具体的函数定义,在 test.c 当中的做具体的测试实现
先来了解一下头文件 SeqList.h 当中接口
#pragma once//防止重复使用
#include<stdio.h>//所需要的头文件
#include<stdlib.h>//开辟内存所需头文件
#include<assert.h>//断言头文件
//typedef 使用typedef想换类型时可以将int改成double、char等
typedef int SLDateType;
typedef struct SeqList//定义结构
{
SLDateType* a;//指向动态开辟的数组
int size;//记录存储数据的个数
int capacity;//记录存储空间容量
}SeqList;
函数的声明
void SeqListInit(SeqList* ps);//初始化函数
void SeqListCheckCapacity(SeqList* ps);//动态扩容函数
void SeqListPushBack(SeqList* ps,SLDateType x);//尾插函数
void SeqListPopBack(SeqList* ps);//尾删函数
void SeqListprint(SeqList* ps);//打印函数
void SeqListDestroy(SeqList* ps);//置空函数,释放空间
void SeqListPushFront(SeqList* ps,SLDateType x);//头插函数
void SeqListPopFront(SeqList* ps);//头删函数
int SeqListFind(SeqList* ps, SLDateType x);//查找函数,查找位置,返回下标
void SeqListInsert(SeqList* ps, int pos, SLDateType x);//指定pos位置插入数据
void SeqListErase(SeqList* ps, int pos);//指定位置插入删除
我们知道,函数的定义方法是非常重要的,也是我们需要深入理解的,下面我们详细学习在 SeqList.c 当中具体的函数实现
初始化函数定义
void SeqListInit(SeqList* ps)
{
//我们先将大小和容量初始化0
ps->a=NULL;
ps->size = 0;
ps->capacity = 0;
}
动态扩容函数定义
在下面头部、尾部和指定位置插入数据时,如果数据个数(size)和容量(capacity)相等,这时我们就需要扩容空间才可以插入数据,因此下方很多函数都需要申请空间,所以我们先封装好一个动态扩容函数。
void SeqListCheckCapacity(SeqList* ps) //动态扩容函数定义
{
if (ps->size == ps->capacity)
{
//注意初始化是0,我们先默认给4个,不够在开空间
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDateType* tmp =
(SLDateType*)realloc(ps->a, newcapacity * sizeof(SLDateType));
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);//异常退出,直接终止
}
//到这里开空间成功
ps->a = tmp;//把空间给a
ps->capacity = newcapacity;//空间容量更新,此时size不变
}
}
尾插函数定义
我们知道数组元素最后的下标都是比数据个数小一个,所以此时的尾部插入函数就是在size的位置
void SeqListPushBack(SeqList* ps, SLDateType x)//尾插
{
SeqListCheckCapacity(ps);//先调用上面我们的动态扩容函数,使容量够用
ps->a[ps->size] = x;//在size位置尾插数据
ps->size++;//在使size存储数据个数更新
}
尾删函数定义
void SeqListPopBack(SeqList* ps)//尾删
{
assert(ps->size>0);//判断size不为空
ps->size--;//让size--删除数据
}
打印函数定义
void SeqListprint(SeqList* ps)//打印函数实现
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
置空函数定义
置空函数一般会放在我们进行插入或删除的函数最后,释放我们在堆上申请的空间,将其还给操作系统,另外也会相应的进行检查越界等问题
void SeqListDestroy(SeqList* ps)
{
//开辟空间需要置空,使用该函数
free(ps->a);
ps->a = NULL;
ps->capacity = ps->size = 0;
}
我们先用上面接口实现一个在 test.c 当中的第一个测试案例void TestSeqList 1
void TestSeqList1()
{
SeqList s;
SeqListInit(&s);//初始化
//尾部插入
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListprint(&s);//打印1 2 3
//尾部删除
SeqListPopBack(&s);
SeqListPopBack(&s);
SeqListprint(&s);//打印 1
//开辟空间需要置空,放在最后
SeqListDestroy(&s);
}
int main()
{
TestSeqList1();
return 0;
}
虽然是结构体,我们可以直接修改和访问,但是规范方法是直接去调用函数
下面我们我们接着详细学习剩下函数接口在 SeqList.c 当中具体的函数实现
头插函数定义
void SeqListPushFront(SeqList* ps, SLDateType x)//头插
{
SeqListCheckCapacity(ps);//先调用上面我们的动态扩容函数,使容量够用
//头插的话数据要先往后挪动
//使用一个end指针 指向最后一个数据位置(下标-1)
int end = ps->size - 1;
while (end >= 0)//
{
ps->a[end + 1] = ps->a[end];//依次往后挪动一个数据
--end;
}
ps->a[0] = x;//在头上0位置插入数据x
ps->size++;
}
头删函数定义
void SeqListPopFront(SeqList* ps)//头删
{
assert(ps->size > 0);
//往前挪,在--size
//定义一个begin指针
int begin = 1;//使其先指向第二个数据
assert(ps->size > 0);
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];//依次往前挪
++begin;
}
ps->size--;
}
查找函数定义
注意这里的返回值,前面的都是viod类型,因为要返回下标,这里我们使用int类型
int SeqListFind(SeqList* ps, SLDateType x)//查找位置,返回下标,没有返回-1
{
for (int i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
return i;
}
}
return -1;
}
指定插入函数定义
void SeqListInsert(SeqList* ps, int pos, SLDateType x)//在指定pos位置插入数据
{
//先检查
assert(pos>=0&&pos<=ps->size);//判断位置要在size连续空间
SeqListCheckCapacity(ps);//先调用上面我们的动态扩容函数,使容量够用
int end = ps->size - 1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
-end;
}
ps->a[pos] = x;
ps->size++;
}
指定删除函数定义
这里最需要注意的就是不要越界,只能在原有的数据当中进行删除,也不能等于size,此时的size位置是没有数据的
void SeqListErase(SeqList* ps, int pos)//在指定位置插入删除
{
assert(pos >= 0 && pos < ps->size);//删除的范围是0到size-1
int begin = pos+1;//使其指向pos位置+1
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
++begin;
}
ps->size--;
}
我们再用上面这几个接口实现在 test.c 当中的第2个测试案例void TestSeqList 2
void TestSeqList2()
{
SeqList s;
SeqListInit(&s);//初始化
SeqListPushFront(&s, 1);//头插1 2 3 4
SeqListPushFront(&s, 2);
SeqListPushFront(&s, 3);
SeqListPushFront(&s, 4);
SeqListprint(&s);//打印4 3 2 1
SeqListInsert(&s,2,30);//指定在下标2的前面位置插入30
SeqListprint(&s);//打印4 3 30 2 1
int pos = SeqListFind(&s, 4);
if (pos != -1)
{
SeqListInsert(&s, pos, 40);//在数据4的下标前插入数据
}
SeqListprint(&s);//打印40 4 3 30 2 1
SeqListErase(&s, 1);//指定在下标1的位置删除数据
SeqListprint(&s);//打印4 3 30 2 1
SeqListDestroy(&s);//开辟空间需要置空,放在最后
}
int main()
{
TestSeqList2();
return 0;
}
在Java和C++的学习当中,前期学习数据结构当中的顺序表、链表、二叉树等便于我们后面更好的学习容器,后面会继续分享链表、二叉树的实现
希望这篇文章大家有所收获,我们下篇见