前言 :从这篇博客开始,我会进行数据结构(用C语言实现)有关内容的记录与分享。对于我们而言,数据结构的知识难度较大并且十分重要,希望我的分享给各位带来一些帮助。而今天要分享的就是数据结构中最简单的知识——顺序表的增删查改。
注:数据结构的学习中,画图是十分重要的,可以简化和加深对相关内容的理解,可能会花费较长时间,但是非常值得我们认真去做,这里分享一个有关数据结构的动图网站,希望也能对各位有所帮助:数据结构动图
目录
一、顺序表的简单介绍
(1) 概述
(2) 图示(动态顺序表)
二、动态顺序表实现简单的增删查改
(1) 大致框架的搭建
1. 工程的构建
2. 基本框架的搭建
3. 预备功能的实现——初始化,扩容,打印与销毁
(2) 尾插尾删的实现
(3) 头插头删的实现
(4) 数据坐标的查找
(5) 定点位置的插入与删除
三:完整代码的展示
(1) Test.c
(2) SeqList.h
(3) SeqList.c
一、顺序表的简单介绍
(1) 概述
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删查改。
顺序表一般分为静态顺序表(使用定长数组存储元素) 和 动态顺序表(使用动态开辟的数组存储),由于静态顺序表缺陷太多,所以在这里主要是对动态顺序表的介绍。
(2) 图示(动态顺序表)
注意:我们要明确,顺序表的本质还是数组,其增删查改就是在连续的数组上实现,不过就是数组数据的挪动,增加,删除,不用想的太过复杂。
二、动态顺序表实现简单的增删查改
(1) 大致框架的搭建
1. 工程的构建:
首先我们要建立一个简单的工程文件,包括三部分:
(1) Test.c——用于测试各个接口函数的源文件(自行控制接口函数的测试);
(2) SeqList.c——用于实现相关接口函数功能的源文件;
(3)SeqList.h——用于定义各个接口函数,顺序表结构以及各个库函数头文件的包含。
(说明:这样构建工程可以使得代码的可读性更强,逻辑更清晰,以后的文章大部分程序的基本构建都是如此,不会再重复说明)
2. 基本框架的搭建
(1) Test.c
#define _CRT_SECURE_NO_WARNINGS #include "SeqList.h" void Test1()//举个测试的例子 { SL a; SeqListInit(&a); SeqListPushBack(&a, 1); SeqListPushBack(&a, 2); SeqListPushBack(&a, 3); SeqListPushBack(&a, 4); SeqListPushBack(&a, 5); SeqListPrint(&a); SeqListPopBack(&a); SeqListPopBack(&a); SeqListPopBack(&a); SeqListPrint(&a); SeqListDestroy(&a); } void Test2() { } void Test3() { } //实现动态顺序表的增删查改(分三部分测试,方便反复测试,查找错误) int main() { //Test1();//尾插尾删 //Test2();//头插头删 Test3();//定点坐标的插入与删除 return 0; }
(2) SeqList.c
//各个接口函数的功能实现 void SeqListInit(SL* pa)//初始化 { } void CheckCapacity(SL* pa)//扩容 { } void SeqListPrint(SL* pa)//打印 { } void SeqListPushBack(SL* pa, SLDatatype x)//尾插 { } void SeqListPopBack(SL* pa)//尾删 { } void SeqListPushFront(SL* pa, SLDatatype x)//头插 { } void SeqListPopFront(SL* pa)//头删 { } int SeqListFind(SL* pa, SLDatatype x)//查找数据x,并返回其下标 { } void SeqListInsert(SL* pa, int pos, SLDatatype x)//在pos下标处插入x { } void SeqListErase(SL* pa, int pos)//删除pos下标处数据 { } void SeqListDestroy(SL* pa)//销毁 { }
(3) SeqList.h
#pragma once #include <stdio.h> #include <stdlib.h> #include <assert.h> typedef int SLDatatype;//将int类型重命名为SLDatatype(可以表示顺序表内数据的类型),方便更改 //顺序表结构的构建 typedef struct SeqList { SLDatatype* a;//指向动态开辟的数组 int sz;//数组中有效数据个数 int capacity;//数组容量空间大小 } SL; //接口函数的定义 void SeqListInit(SL* pa);//初始化 void CheckCapacity(SL* pa);//扩容 void SeqListPrint(SL* pa);//打印 void SeqListDestroy(SL* pa);//销毁 void SeqListPushBack(SL* pa, SLDatatype x);//尾插 void SeqListPopBack(SL* pa);//尾删 void SeqListPushFront(SL* pa, SLDatatype x);//头插 void SeqListPopFront(SL* pa);//头删 int SeqListFind(SL* pa, SLDatatype x);//查找数据x,并返回其下标 void SeqListInsert(SL* pa, int pos, SLDatatype x);//在pos下标处插入x void SeqListErase(SL* pa, int pos);//删除pos下标处数据
3. 预备功能的实现——初始化,扩容,打印与销毁
(1) 顺序表的初始化,打印与销毁
比较简单,不多多介绍:
void SeqListInit(SL* pa)//初始化 { assert(pa); pa->a = NULL; pa->capacity = pa->sz = 0; } void SeqListPrint(SL* pa)//打印———遍历数组即可 { int i = 0; for (i = 0;i < pa->sz;i++) { printf("%d ", pa->a[i]); } printf("\n"); } void SeqListDestroy(SL* pa)//销毁顺序表 { assert(pa); free(pa->a); pa->a = NULL; }
(2) 顺序表的扩容操作
由于是动态实现顺序表,所以空间是动态开辟出来的,在进行数据插入时就会涉及到一个重要的步骤——空间不够,要进行扩容。
那就要使用我们在C语言学习中涉及的一个函数realloc,而扩容操作要思考全面,这里一共有三种情况,简单介绍一下:
1. 有效元素个数(sz)等于开辟空间大小(capacity)等于0,此时没有数据空间,需要开辟空间,而当realloc要扩容的空间为NULL时,其作用相当于malloc,即直接开辟设置的字节大小的空间即可。
2. 有效元素个数(sz)等于开辟空间大小(capacity)大于0,此时数据空间不够,需要进行扩容,直接使用realloc函数进行对应字节大小空间的扩容即可。
3. 有效元素个数(sz)不等于开辟空间大小(capacity)——此时不需要扩容,直接插入数据即可。
void CheckCapacity(SL* pa)//扩容 { if (pa->sz == pa->capacity)//前两种情况 { int newcapacity = pa->capacity == 0 ? 4 : pa->capacity * 2; SLDatatype* tmp = realloc(pa->a, sizeof(SLDatatype) * newcapacity); if (tmp == NULL) { printf("realloc fail\n"); exit(-1); } pa->a = tmp; pa->capacity = newcapacity; } }
(2) 尾插尾删的实现
void SeqListPushBack(SL* pa, SLDatatype x)//尾插
{
assert(pa);
CheckCapacity(pa);//插入数据前要先判断是否需要扩容
pa->a[pa->sz] = x;//最后一个有效元素之后插入x
pa->sz++;
}
void SeqListPopBack(SL* pa)//尾删
{
assert(pa);
assert(pa->sz > 0);//删除数据的前提是顺序表中有有效元素
pa->sz--;
}
这里是尾插尾删的简单实现,后面进一步实现一个接口函数后可以对其进行简化。
(3) 头插头删的实现
//头插与头删的原理都是对数据进行适当的挪动,要注意的是边界的控制,可以画图理解
void SeqListPushFront(SL* pa, SLDatatype x)//头插
{
assert(pa);
CheckCapacity(pa);
int end = pa->sz - 1;
while (end >= 0)
{
pa->a[end + 1] = pa->a[end];//挪动数据的过程
end--;
}
pa->a[0] = x;
pa->sz++;
}
void SeqListPopFront(SL* pa)//头删
{
assert(pa);
assert(pa->sz > 0);//无数据,不可删
int begin = 0;
while (begin < pa->sz - 1)
{
pa->a[begin] = pa->a[begin + 1];//挪动数据的过程
begin++;
}
pa->sz--;
}
(4) 数据坐标的查找
//遍历数组,查找数据x,并返回其下标
int SeqListFind(SL* pa, SLDatatype x)
{
assert(pa);
int i = 0;
for (i = 0;i < pa->sz;i++)
{
if (pa->a[i] == x)
{
return i;
}
}
return -1;
}
(5) 定点位置的插入与删除
void SeqListInsert(SL* pa, int pos, SLDatatype x)//在pos下标处插入数据x
{
assert(pa);
CheckCapacity(pa);//扩容
int i = 0;
int end = pa->sz - 1;
for (i = end;i >= pos;i--)//挪动数据的思想————前向后移
{
pa->a[i + 1] = pa->a[i];
}
pa->a[pos] = x;
pa->sz++;
}
void SeqListErase(SL* pa,int pos)//删除pos下标处数据
{
assert(pa);
assert(pa->sz > 0);//判空
int i = 0;
for (i = pos;i < pa->sz - 1;i++)//挪动数据的思想————后向前移,覆盖pos位置的数据
{
pa->a[i] = pa->a[i + 1];
}
pa->sz--;
}
注意:这两个函数接口是顺序表中较为重要的两个,调用这两个函数接口可以解决顺序表的插入与删除。同时,用这两个函数接口可以对头插头删,尾插尾删进行简化:
void SeqListPushBack(SL* pa, SLDatatype x)//尾插
{
SeqListInsert(pa, pa->sz, x);//调用SeqListInsert函数简化尾插,在最后一个元素后插入元素
}
void SeqListPopBack(SL* pa)//尾删
{
SeqListErase(pa, pa->sz - 1);//调用SeqListErase函数简化尾删,删除最后一个元素
}
void SeqListPushFront(SL* pa, SLDatatype x)//头插
{
SeqListInsert(pa, 0, x);//调用SeqListInsert函数简化头插,在第一个元素处插入元素
}
void SeqListPopFront(SL* pa)//头删
{
SeqListErase(pa, 0);//调用SeqListErase函数简化头删,删除首位位置元素
}
三:完整代码的展示
(1) Test.c
#define _CRT_SECURE_NO_WARNINGS
#include "SeqList.h"
void Test1()//尾插尾删的测试
{
SL a;
SeqListInit(&a);
SeqListPushBack(&a, 1);
SeqListPushBack(&a, 2);
SeqListPushBack(&a, 3);
SeqListPushBack(&a, 4);
SeqListPushBack(&a, 5);
SeqListPrint(&a);
SeqListPopBack(&a);
SeqListPopBack(&a);
SeqListPopBack(&a);
SeqListPrint(&a);
SeqListDestroy(&a);
}
void Test2()//头插头删的测试
{
SL a;
SeqListInit(&a);
SeqListPushFront(&a, 1);
SeqListPushFront(&a, 2);
SeqListPushFront(&a, 3);
SeqListPushFront(&a, 4);
SeqListPushFront(&a, 5);
SeqListPrint(&a);
SeqListPopFront(&a);
SeqListPopFront(&a);
SeqListPrint(&a);
SeqListDestroy(&a);
}
void Test3()//定点坐标的插入与删除的测试
{
SL a;
SeqListInit(&a);
SeqListPushFront(&a, 1);
SeqListPushFront(&a, 2);
SeqListPushBack(&a, 3);
SeqListPushBack(&a, 4);
SeqListPrint(&a);
int pos = SeqListFind(&a, 1);//找到原顺序表中是否有1,有则在其下标处插入5
if (pos != -1)
{
SeqListInsert(&a, pos, 5);
}
SeqListPrint(&a);
pos = SeqListFind(&a, 2);//找到原顺序表中是否有2,有则在其下标处插入6
if (pos != -1)
{
SeqListInsert(&a, pos, 6);
}
SeqListPrint(&a);
pos = SeqListFind(&a, 2);//找到原顺序表中是否有2,有则删除
if (pos != -1)
{
SeqListErase(&a, pos);
}
SeqListPrint(&a);
pos = SeqListFind(&a, 1);//找到原顺序表中是否有1,有则删除
if (pos != -1)
{
SeqListErase(&a, pos);
}
SeqListPrint(&a);
SeqListDestroy(&a);
}
//实现动态顺序表的增删查改(分三部分测试,方便反复测试,查找错误)
int main()
{
//Test1();//尾插尾删
//Test2();//头插头删
Test3();//定点坐标的插入与删除
return 0;
}
(2) SeqList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLDatatype;//将int类型重命名为SLDatatype(可以表示顺序表内数据的类型),方便更改
//顺序表结构的构建
typedef struct SeqList
{
SLDatatype* a;//指向动态开辟的数组
int sz;//数组中有效数据个数
int capacity;//数组容量空间大小
} SL;
void SeqListInit(SL* pa);//初始化
void CheckCapacity(SL* pa);//扩容
void SeqListPrint(SL* pa);//打印
void SeqListDestroy(SL* pa);//销毁
void SeqListPushBack(SL* pa, SLDatatype x);//尾插
void SeqListPopBack(SL* pa);//尾删
void SeqListPushFront(SL* pa, SLDatatype x);//头插
void SeqListPopFront(SL* pa);//头删
int SeqListFind(SL* pa, SLDatatype x);//查找数据x,并返回其下标
void SeqListInsert(SL* pa, int pos, SLDatatype x);//在pos下标处插入x
void SeqListErase(SL* pa, int pos);//删除pos下标处数据
(3) SeqList.c
#define _CRT_SECURE_NO_WARNINGS
#include "SeqList.h"
void SeqListInit(SL* pa)//初始化
{
assert(pa);
pa->a = NULL;
pa->capacity = pa->sz = 0;
}
void SeqListPrint(SL* pa)//打印———遍历数组即可
{
int i = 0;
for (i = 0;i < pa->sz;i++)
{
printf("%d ", pa->a[i]);
}
printf("\n");
}
void CheckCapacity(SL* pa)//扩容
{
if (pa->sz == pa->capacity)//前两种情况
{
int newcapacity = pa->capacity == 0 ? 4 : pa->capacity * 2;
SLDatatype* tmp = realloc(pa->a, sizeof(SLDatatype) * newcapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
pa->a = tmp;
pa->capacity = newcapacity;
}
}
void SeqListPushBack(SL* pa, SLDatatype x)//尾插
{
/*assert(pa);
CheckCapacity(pa);
pa->a[pa->sz] = x;
pa->sz++;*/
SeqListInsert(pa, pa->sz, x);//调用SeqListInsert函数简化尾插
}
void SeqListPopBack(SL* pa)//尾删
{
/*assert(pa);
assert(pa->sz > 0);
pa->sz--;*/
SeqListErase(pa, pa->sz - 1);//调用SeqListErase函数简化尾删
}
void SeqListPushFront(SL* pa, SLDatatype x)//头插
{
/*assert(pa);
CheckCapacity(pa);
int end = pa->sz - 1;
while (end >= 0)
{
pa->a[end + 1] = pa->a[end];
end--;
}
pa->a[0] = x;
pa->sz++;*/
SeqListInsert(pa, 0, x);//调用SeqListInsert函数简化头插
}
void SeqListPopFront(SL* pa)//头删
{
/*assert(pa);
assert(pa->sz > 0);
int begin = 0;
while (begin < pa->sz - 1)
{
pa->a[begin] = pa->a[begin + 1];
begin++;
}
pa->sz--;*/
SeqListErase(pa, 0);//调用SeqListErase函数简化头删
}
int SeqListFind(SL* pa, SLDatatype x)//查找数据x,并返回其下标
{
assert(pa);
int i = 0;
for (i = 0;i < pa->sz;i++)
{
if (pa->a[i] == x)
{
return i;
}
}
return -1;
}
void SeqListInsert(SL* pa, int pos, SLDatatype x)//在pos下标处插入x
{
assert(pa);
CheckCapacity(pa);
int i = 0;
int end = pa->sz - 1;
for (i = end;i >= pos;i--)//挪动数据的思想————前向后移
{
pa->a[i + 1] = pa->a[i];
}
pa->a[pos] = x;
pa->sz++;
}
void SeqListErase(SL* pa,int pos)//删除pos下标处数据
{
assert(pa);
assert(pa->sz > 0);
int i = 0;
for (i = pos;i < pa->sz - 1;i++)//挪动数据的思想————后向前移,覆盖pos位置的数据
{
pa->a[i] = pa->a[i + 1];
}
pa->sz--;
}
void SeqListDestroy(SL* pa)//销毁顺序表
{
assert(pa);
free(pa->a);
pa->a = NULL;
}
总结
这样,我对动态开辟的顺序表的介绍就结束了,涉及增删查改各个操作共十一个接口函数,大家如果不会的话,可以先参照起始的模板自己尝试进行代码书写,再看看各个接口函数是如何实现的,当然如有错误,还望各位不吝赐教。最后,希望我的总结理解可以给各位带来一定的帮助,谢谢完整看完我文章的每一位,再见。