数据结构-顺序表
文章目录
目录
- 线性表
- 顺序表
- 链表
- 顺序表和链表的优缺点
线性表
线性表是n个具有相同特性的数据元素
的有限序列,逻辑上是线性结构
,是一条连续的直线。物理结构上不一定连续
,因为通常线性表在内存中是以数组和链式的形式存储
,以数组形式存储时是连续的
,但是以链式形式存储时是不连续的
。常见的线性表:顺序表、链表、栈、队列… 这篇文章先给大家介绍顺序表,链表、栈和队列会在后续文章更新。
顺序表
定义
顺序表是用一段物理地址连续
的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组 上完成数据的增删查改。
- 静态顺序表和动态顺序表
通过上述可知,静态顺序表为定长的数组,导致
MaxSize
定大了,空间开多了浪费,开少了不够用,实际使用场景有限。所以现实中基本都是使用动态顺序表,根据需要可以用realloc
动态地分配空间大小,所以下面我们实现动态顺序表。本次代码的实现共创建了三个文件,分别是SeqList.h
,SeqList.c
和main.c
文件。下面我们先把结构体定义和各个函数接口实现,最后再把三个文件的代码全部展示出来。
定义结构体
typedef int SLDataType; // 重新定义数据类型名
typedef struct SeqList
{
SLDataType* array; // 定义一个指针,指向开辟出来的数组
int capacity; // 顺序表能够容纳的最大元素个数大小,后期可以改变
int size; // 有效元素的个数大小
}SeqList;
为什么要重新定义数据类型名?因为顺序表储存的元素类型不单单是
int
型,后面要存储char
型的或者其他类型的数据,需要把代码里面的int
都改一遍,非常麻烦。如果我们重新定义了类型名,并且在代码里用重新定义好的名字,下次需要存储其他类型的数据,直接在重定义那里把int
改成想存储的类型就好了。
初始化
// 顺序表初始化
void SeqListInit(SeqList* sl)
{
// 断言,防止传进来的是空指针
assert(sl != NULL); // 也可以写成 assert(sl);
sl->array = NULL;
sl->capacity = 0;
sl->size = 0;
}
assert
是一个断言函数,程序运行的时候,当括号里面的结果为假时,就会停止运行并且报错。报错显示的信息包括断言的内容和断言的位置,还有一个错误框,如下图所示。断言能够快速地帮我们定位程序的错误,在实际开发中可以减少很多不必要的麻烦,所以建议大家在写代码的时候也尽量在需要的时候加上断言。温馨提示在使用
assert
函数时,记得包含一下assert.h
这个头文件。
打印
// 顺序表打印
void SeqListPrint(SeqList* sl)
{
assert(sl);
int i = 0;
for (i = 0; i < sl->size; i++)
{
printf("%d ", sl->array[i]);
}
printf("\n");
}
检查和增容
// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* sl)
{
assert(sl);
// 判断空间是否满了,如果是就增容
if (sl->capacity == sl->size)
{
// 当capacity为0时,赋值为4,不为0时,赋值为capacity的两倍
int newcapacity = sl->capacity == 0 ? 4 : sl->capacity * 2;
// 用realloc增容,当sl->array指向NULL时,会像malloc那样去申请空间
SLDataType* temp = (SLDataType*)realloc(sl->array, sizeof(SLDataType) * newcapacity);
// 判断申请空间是否成功
assert(temp);
// 让array指向增容之后的空间地址
sl->array = temp;
sl->capacity = newcapacity;
}
}
增删查改
头插
// 顺序表头插
void SeqListPushFront(SeqList* sl, SLDataType x)
{
assert(sl);
// 检查空间,不够就增容
CheckCapacity(sl);
// 将所有元素往后移一个位置
int i = 0;
for (i = sl->size; i > 0; i--)
{
sl->array[i] = sl->array[i - 1];
}
// 插入数据
sl->array[0] = x;
// size加1
sl->size += 1;
// 这里空表也适用,所以不用考虑空表
}
尾插
// 顺序表尾插
void SeqListPushBack(SeqList* sl, SLDataType x)
{
assert(sl);
CheckCapacity(sl);
// 插入元素
sl->array[sl->size] = x;
// size加1
sl->size += 1;
}
在pos位置插入
// 顺序表在下标为pos的位置插入x
void SeqListInsert(SeqList* sl, size_t pos, SLDataType x)
{
assert(sl);
CheckCapacity(sl);
// 检查pos位置是否有效
assert(pos >= 0 && pos <= sl->size);
// 把pos位置开始的所有元素往后移一个位置
int i = 0;
for (i = sl->size; i > pos; i--)
{
sl->array[i] = sl->array[i - 1];
}
// 插入数据
sl->array[pos] = x;
// size加一
sl->size += 1;
}
掌握了在pos位置插入的方法后,前面的头插和尾插也可以写成下面这个样子
// 头插
void SeqListPushFront(SeqList* sl, SLDataType x)
{
// 调用函数接口插入数据
SeqListInsert(sl, 0, x);
}
// 尾插
void SeqListPushBack(SeqList* sl, SLDataType x)
{
SeqListInsert(sl, sl->size, x);
}
头删
// 顺序表头删
void SeqListPopFront(SeqList* sl)
{
assert(sl);
// 判断空表
assert(sl->size);
// 把0后面位置的元素都往前移一个位置,即完成头删
int i = 0;
for (i = 0; i < sl->size; i++)
{
sl->array[i] = sl->array[i + 1];
}
// size减1
sl->size -= 1;
}
尾删
// 顺序表尾删
void SeqListPopBack(SeqList* sl)
{
assert(sl);
// 判断空表
assert(sl->size);
// 把size减1即可实现尾删
sl->size -= 1;
}
删除pos位置的元素
// 顺序表删除pos位置的值
void SeqListErase(SeqList* sl, int pos)
{
assert(sl);
// 判断空表
assert(sl->size);
// 判断pos位置是否有效
assert(pos >= 0 && pos < sl->size);
// 把pos位置之后的元素都往前移一个位置,即可实现删除
int i = 0;
for (i = pos; i < sl->size - 1; i++)
{
sl->array[i] = sl->array[i + 1];
}
// size减1
sl->size -= 1;
}
掌握了删除pos位置元素这个方法后,前面的头删和尾删也可以写成下面这个样子
// 头删
void SeqListPopFront(SeqList* sl)
{
SeqListErase(sl, 0);
}
// 尾删
void SeqListPopBack(SeqList* sl)
{
SeqListErase(sl, sl->size-1);
}
查找
// 顺序表查找
int SeqListFind(SeqList* sl, SLDataType x)
{
assert(sl);
int i = 0;
for (i = 0; i < sl->size; i++)
{
if (sl->array[i] == x)
{
// 找到返回下标值
return i;
}
}
// 找不到返回-1
return -1;
}
修改
// 顺序表修改
void SeqListModify(SeqList* sl, int pos, SLDataType x)
{
assert(sl);
// 判断pos的位置是否有效
assert(pos >= 0 && pos < sl->size);
// 修改元素
sl->array[pos] = x;
}
销毁
// 顺序表销毁
void SeqListDestory(SeqList* sl)
{
assert(sl);
// 释放申请的空间
free(sl->array); // 这里申请的空间是一次性申请一整块的,所以可以一次性释放
// 指针置空
sl->array = NULL;
// 大小和空间置0
sl->capacity = 0;
sl->size = 0;
}
SeqList.h文件代码
#pragma once // 防止头文件被重复包含
// 包含头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
// 重新定义数据类型名
typedef int SLDataType;
// 定义结构体
typedef struct SeqList
{
SLDataType* array; // 定义一个指针,指向开辟出来的数组
int capacity; // 顺序表能够容纳的最大元素个数大小,后期可以改变
int size; // 有效元素的个数大小
}SeqList;
// 函数接口
// 顺序表初始化
void SeqListInit(SeqList* sl);
// 顺序表打印
void SeqListPrint(SeqList* sl);
// 检查和增容
void CheckCapacity(SeqList* sl);
// 顺序表尾插
void SeqListPushBack(SeqList* sl, SLDataType x);
// 顺序表尾删
void SeqListPopBack(SeqList* sl);
// 顺序表头插
void SeqListPushFront(SeqList* sl, SLDataType x);
// 顺序表头删
void SeqListPopFront(SeqList* sl);
// 顺序表查找
int SeqListFind(SeqList* sl, SLDataType x);
// 顺序表修改
void SeqListModify(SeqList* sl, int pos, SLDataType x);
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* sl, int pos, SLDataType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* sl, int pos);
// 顺序表销毁
void SeqListDestory(SeqList* sl);
SeqList.c文件代码
#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"
// 顺序表初始化
void SeqListInit(SeqList* sl)
{
// 断言,防止传进来空指针
assert(sl != NULL); // 也可以写成 assert(sl);
sl->array = NULL;
sl->capacity = 0;
sl->size = 0;
}
// 顺序表打印
void SeqListPrint(SeqList* sl)
{
assert(sl);
int i = 0;
for (i = 0; i < sl->size; i++)
{
printf("%d ", sl->array[i]);
}
printf("\n");
}
// 检查和增容
void CheckCapacity(SeqList* sl)
{
assert(sl);
// 判断空间是否满了,如果是就增容
if (sl->capacity == sl->size)
{
// 当capacity为0时,赋值为4,不为0时,赋值为capacity的两倍
int newcapacity = sl->capacity == 0 ? 4 : sl->capacity * 2;
// 用realloc增容,当sl->array指向NULL时,会像malloc那样去申请空间
SLDataType* temp = (SLDataType*)realloc(sl->array, sizeof(SLDataType) * newcapacity);
// 判断申请空间是否成功
assert(temp);
// 让array指向增容之后的空间地址
sl->array = temp;
sl->capacity = newcapacity;
}
}
// 顺序表头插
// 方法一
void SeqListPushFront(SeqList* sl, SLDataType x)
{
assert(sl);
// 检查空间,不够就增容
CheckCapacity(sl);
// 将所有元素往后移一个位置
int i = 0;
for (i = sl->size; i > 0; i--)
{
sl->array[i] = sl->array[i - 1];
}
// 插入数据
sl->array[0] = x;
// size加1
sl->size += 1;
// 这里空表也适用,所以不用考虑空表
}
// 方法二
//void SeqListPushFront(SeqList* sl, SLDataType x)
//{
// // 调用函数接口插入数据
// SeqListInsert(sl, 0, x);
//}
// 顺序表尾插
// 方法一
void SeqListPushBack(SeqList* sl, SLDataType x)
{
assert(sl);
CheckCapacity(sl);
// 插入数据
sl->array[sl->size] = x;
// size加一
sl->size += 1;
}
// 方法二
//void SeqListPushBack(SeqList* sl, SLDataType x)
//{
// SeqListInsert(sl, sl->size, x);
//}
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* sl, int pos, SLDataType x)
{
assert(sl);
CheckCapacity(sl);
// 检查pos位置是否有效
assert(pos >= 0 && pos <= sl->size);
// 把pos位置开始的所有元素往后移一个位置
int i = 0;
for (i = sl->size; i > pos; i--)
{
sl->array[i] = sl->array[i - 1];
}
// 插入数据
sl->array[pos] = x;
// size加一
sl->size += 1;
}
// 顺序表头删
// 方法一
//void SeqListPopFront(SeqList* sl)
//{
// assert(sl);
//
// // 判断空表
// assert(sl->size);
//
// // 把0后面位置的元素都往前移一个位置,即完成头删
// int i = 0;
// for (i = 0; i < sl->size; i++)
// {
// sl->array[i] = sl->array[i + 1];
// }
//
// // size减1
// sl->size -= 1;
//}
// 方法二
void SeqListPopFront(SeqList* sl)
{
SeqListErase(sl, 0);
}
// 顺序表尾删
// 方法一
//void SeqListPopBack(SeqList* sl)
//{
// assert(sl);
//
// // 判断空表
// assert(sl->size);
//
// // 把size减1即可实现尾删
// sl->size -= 1;
//}
// 方法二
void SeqListPopBack(SeqList* sl)
{
SeqListErase(sl, sl->size-1);
}
// 顺序表删除pos位置的值
void SeqListErase(SeqList* sl, int pos)
{
assert(sl);
// 判断空表
assert(sl->size);
// 判断pos位置是否有效
assert(pos >= 0 && pos < sl->size);
// 把pos位置之后的元素都往前移一个位置,即可实现删除
int i = 0;
for (i = pos; i < sl->size - 1; i++)
{
sl->array[i] = sl->array[i + 1];
}
// size减1
sl->size -= 1;
}
// 顺序表查找
int SeqListFind(SeqList* sl, SLDataType x)
{
assert(sl);
int i = 0;
for (i = 0; i < sl->size; i++)
{
if (sl->array[i] == x)
{
// 找到返回下标值
return i;
}
}
// 找不到返回-1
return -1;
}
// 顺序表修改
void SeqListModify(SeqList* sl, int pos, SLDataType x)
{
assert(sl);
// 判断pos的位置是否有效
assert(pos >= 0 && pos < sl->size);
sl->array[pos] = x;
}
// 顺序表销毁
void SeqListDestory(SeqList* sl)
{
assert(sl);
// 释放申请的空间
free(sl->array); // 这里申请的空间是一次性申请一整块的,所以可以一次性释放
// 指针置空
sl->array = NULL;
// 大小和空间置0
sl->capacity = 0;
sl->size = 0;
}
main.c文件代码
#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"
// 测试插入接口
void test1()
{
// 定义一个结构体变量
SeqList sl;
SeqListInit(&sl);
SeqListPushFront(&sl, 1);
SeqListPushFront(&sl, 2);
SeqListPushFront(&sl, 3);
SeqListPushFront(&sl, 4);
SeqListInsert(&sl, 0, 6);
SeqListPushBack(&sl, 5);
SeqListPrint(&sl);
// 销毁顺序表
SeqListDestory(&sl);
}
// 测试删除接口
void test2()
{
SeqList sl;
SeqListInit(&sl);
SeqListPushFront(&sl, 1);
SeqListPushFront(&sl, 2);
//SeqListPopFront(&sl);
//SeqListPopFront(&sl);
//SeqListPopFront(&sl);
SeqListPopBack(&sl);
SeqListPopBack(&sl);
SeqListPopBack(&sl);
//SeqListErase(&sl, 0);
//SeqListErase(&sl, 1);
//SeqListErase(&sl, 2);
SeqListPrint(&sl);
SeqListDestory(&sl);
}
// 测试查找和修改
void test3()
{
SeqList sl;
SeqListInit(&sl);
SeqListPushFront(&sl, 1);
SeqListPushFront(&sl, 3);
SeqListPushFront(&sl, 2);
SeqListPushFront(&sl, 3);
SeqListPushFront(&sl, 4);
// 一般修改和查找是配合使用的,比如说想把表中的第一个3改为300
int pos = SeqListFind(&sl, 3);
SeqListModify(&sl, pos, 300);
SeqListPrint(&sl);
SeqListDestory(&sl);
}
int main()
{
//test1();
//test2();
test3();
return 0;
}
结语
顺序表是我们接触数据结构的第一个需要我们用C语言去实现的内容,刚开始学的时候很多人会觉得比较难,特别是各个
函数接口
的命名,实在太难记了。很多初学者会自己起一些简单的名字,这也可以,但是不赞成这样做。因为你自己起的名字你看得懂,别人不一定懂,说不定时间久了自己都忘记了。所以建议大家一开始就规范命名,养成良好的习惯。其实这个命名是有规律的,大多数情况是结构体名+功能
,使用的是大驼峰命名法(每个单词首字母大写,包括缩写的)。比如上文里的尾插接口SeqListPushBack
,可以拆成SeqList+PushBack
。另外数据结构的实现,需要熟练掌握C语言的函数,指针,结构体,动态内存规划这些知识。所以建议大家在学习数据结构之前,把C语言的这部分知识学得扎实一点,这样数据结构学起来就轻松很多了。最后,本文是我学完这章内容后的个人总结,要是文章里有什么错误还望各位大神指正。或者对我的文章排版和其他方面有什么建议,也可以在评论区告诉我。如果我的文章对你的学习有帮助,或者觉得写得不错的话记得分享给你的朋友,非常感谢。