初阶数据结构之顺序表的实现
1 线性表
什么是线性表呢?
线性表是n个具有相同特性的数据元素的有限序列。
常见的线性表:顺序表,链表,栈,队列,字符串。
线性表在逻辑上是线性结构,在物理结构上不一定是线性的。线性表在物理存储时,通常是以数组或链式结构形式存储
。
线性表大致分为两种:顺序表和链表。基于这两种最基本的结构才能够实现更复杂的数据结构。今天我们着重来了解顺序表的实现。
2 顺序表
概念:线性表是用一段物理地址连续的存储单元
依次存储数据元素的线性结构,因此一般情况下采用数组存储。
顺序表既然是用数组实现的,那么顺序表和数组有什么区别呢?举个例子大家就明白了。这就好比玉米羹,通过厨师一番操作,给玉米羹加上了料汁,饰品,摆盘等,摇身一变就变成宫廷玉液羹。而在这里,顺序表的底层结构采用数组,对数组进行封装,实现了常用的增删改查等接口,就成为了顺序表。
2.1 顺序表的选择
那么新的问题又来了,我们到底该采用静态顺序表还是动态顺序表?
可以清楚的看到,静态顺序表能够存储的数据个数是有限的。如果数组满了,就无法对新的数据进行存储。可能会有人说,那简单啊,把数组的容量给大一点不就好了。那如果只是多存储了一个数据呢?那岂不是会有很多空间被浪费掉。
静态顺序表的缺陷:空间给少了不够用,给大了空间浪费
。
动态顺序表可根据数据个数对空间大小进行调整。那么到底该如何调整呢?当数组空间满了的时候,我们到底扩几倍呢?如果倍数太大,有可能会造成空间浪费,倍数太小,空间不够用。我们一般扩两倍,因为这样不仅实现了增容,而且有利于减少空间的浪费。
2.2 顺序表的实现
2.1.1 顺序表的定义
typedef int SLDataType;
//动态顺序表
typedef struct SeqList
{
SLDataType* a;
int capacity;//顺序表的容量
int size;//实际存储数据的个数
}SL;
typedef int SLDataType 对int进行类型重命名,如果需要存储
char类型数据,只需要修改这里的int就可以了。十分的方便,
代码也不容易出现错误。
2.1.2 顺序表的接口
//初始化
void SeqListInit(SL* ps);
//打印顺序表
void DisplaySeqList(SL* ps);
//尾插
void SeqListPushBack(SL* ps, SLDataType x);
//尾删
void SeqListPopBack(SL* ps);
//头插
void SeqListPushFront(SL* ps, SLDataType x);
//头删
void SeqListPopFront(SL* ps);
//顺序表查找
int SeqListFind(SL* ps, SLDataType x);
//从指定位置之前插入数据
void SeqListInsert(SL* ps, int pos, SLDataType x);
//从指定位置删除数据
void SeqListErase(SL* ps, int pos);
//从指定位置修改数据
void SeqListModify(SL* ps, int pos, SLDataType x);
//销毁空间
void SeqListDestroy(SL* ps);
2.1.3 顺序表的初始化
//初始化
void SeqListInit(SL* ps)
{
assert(ps != NULL);
ps->a = NULL;
ps->capacity = 0;
ps->size = 0;
}
assert(ps!=NULL);断言ps不能为空指针,否则将导致野指针的出现,
同时会对有问题的文件和行号进行报错。
2.1.4 顺序表的打印
//打印顺序表
void DisplaySeqList(SL* ps)
{
assert(ps);
int i = 0;
while (i < ps->size)
{
printf("%d ", ps->a[i]);
i++;
}
printf("\n");
}
ps->size是顺序表的有效数据的个数。数组下标从0开始,对i进行初
始化为0,使用下标引用操作符[]对顺序表进行访问,循环打印。
2.1.5 检查顺序表的容量
//检查容量
void SeqListCheckCapacity(SL* ps)
{
assert(ps);
if (ps->size == ps->capacity)
{
//初始化时顺序表的容量是0,没有空间,因此无法存储数
//据,使用三目操作符给顺序表一个初始的容量,同时当顺
//序表容量满的时候,进行增容
SLDataType newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
ps->capacity = newcapacity;
//开辟newcapacity*sizeof(SLDataType)个字节空间
SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * newcapacity);
//如果开辟失败,则退出程序
if (NULL == tmp)
{
printf("realloc fail\n");
exit(-1);
}
//开辟成功,将新空间的地址交给ps->a进行维护
ps->a = tmp;
}
}
检查顺序表的容量,当容量满了的时候就进行增容。
2.1.6 顺序表尾插
//尾插
void SeqListPushBack(SL* ps, SLDataType x)
{
assert(ps != NULL);
//检查顺序表的容量,满了就增容
SeqListCheckCapacity(ps);
//插入数据
ps->a[ps->size] = x;
//记录顺序表有效数据的个数
ps->size++;
}
2.1.7 顺序表尾删
//尾删
void SeqListPopBack(SL* ps)
{
assert(ps);
//顺序表不能为空,为空意味着顺序表中没有数据,不需要删除
assert(ps->size > 0);
ps->size--;
}
这里需要注意的是assert(ps->size>0)(断言顺序表不能为空),如果没
有这段代码,顺序表会无限制的删除,极易造成越界。
2.1.8 顺序表头插
//头插
void SeqListPushFront(SL* ps, SLDataType x)
{
assert(ps);
SeqListCheckCapacity(ps);
//end记录数组最后一个元素的下标
int end = ps->size - 1;
//将数组元素整体向后移
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];
end--;
}
//插入数据
ps->a[0] = x;
//更新数组有效数据的个数
ps->size++;
}
每次插入数据之前都要先判断顺序表容量是否足够。
2.1.9 顺序表头删
//头删
void SeqListPopFront(SL* ps)
{
assert(ps);
assert(ps->size > 0);
//开始挪动数据的起始位置
int start = 1;
//将数组元素整体向前移
while (start < ps->size)
{
ps->a[start - 1] = ps->a[start];
start++;
}
//每删除一次数据,顺序表有效数据个数-1
ps->size--;
}
assert(ps->size>0)说明顺序表中有数据才可以删除,否则不需要删除
2.1.10 顺序表查找
//顺序表查找
int SeqListFind(SL* ps, SLDataType x)
{
assert(ps);
int i = 0;
while (i < ps->size)
{
if (ps->a[i] == x)
{
return i;
}
i++;
}
return -1;
}
对数组进行遍历,如果与查找的数字相等,则返回下标,否则继续查找。循环结束之后还未找到,说明顺序表中没有该数据,返回-1。
2.1.11 从指定位置之前插入数据
//从指定位置之前插入数据
void SeqListInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
//判断pos的有效性
assert(pos >= 0 && pos < ps->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++;
}
插入数据之前先判断顺序表的容量和pos位置的有效性,再将元素往后移,为pos位置腾出空位,最后插入元素,更新ps->size。
2.1.12 从指定位置删除数据
//从指定位置删除数据
void SeqListErase(SL* ps, int pos)
{
assert(ps);
//顺序表为空的情况
assert(ps->size > 0);
//判断pos位置的有效性
assert(pos >= 0 && pos < ps->size);
//顺序表至少有一个数据
int start = pos + 1;
while (start < ps->size)
{
ps->a[start - 1] = ps->a[start];
start++;
}
ps->size--;
}
删除数据之后,一定不能忘记ps->size--
2.1.13 从指定位置修改顺序表
//从指定位置修改顺序表
void SeqListModify(SL* ps, int pos, SLDataType x)
{
assert(ps);
//判断pos位置的有效性
assert(pos >= 0 && pos < ps->size);
//顺序表为空的情况
assert(ps->size > 0);
ps->a[pos] = x;
}
使用下标引用操作符[]去访问数组,直接修改就可以了。
2.1.14 销毁顺序表
//销毁空间
void SeqListDestroy(SL* ps)
{
assert(ps);
//ps->a有可能是NULL
if(ps->a!=NULL)
{
free(ps->a);
ps->a = NULL;
}
ps->capacity = 0;
ps->size = 0;
}
数组是动态开辟出来的空间,程序结束之后要还给操作系统。否则会
造成内存泄漏。因为计算机内存空间也是有限的,如果不还给操作系
统,内存就会越来越少。
3 完整代码的实现
seqlist.h头文件
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SLDataType;
//动态顺序表
typedef struct SeqList
{
SLDataType* a;
int capacity;//顺序表的容量
int size;//实际存储数据的个数
}SL;
//初始化
void SeqListInit(SL* ps);
//打印顺序表
void DisplaySeqList(SL* ps);
//尾插
void SeqListPushBack(SL* ps, SLDataType x);
//尾删
void SeqListPopBack(SL* ps);
//头插
void SeqListPushFront(SL* ps, SLDataType x);
//头删
void SeqListPopFront(SL* ps);
//顺序表查找
int SeqListFind(SL* ps, SLDataType x);
//从指定位置插入数据
void SeqListInsert(SL* ps, int pos, SLDataType x);
//从指定位置删除数据
void SeqListErase(SL* ps, int pos);
//从指定位置修改顺序表
void SeqListModify(SL* ps, int pos, SLDataType x);
//销毁空间
void SeqListDestroy(SL* ps);
seqlist.c源文件
#define _CRT_SECURE_NO_WARNINGS
#include"seqlist.h"
//初始化
void SeqListInit(SL* ps)
{
assert(ps != NULL);
ps->a = NULL;
ps->capacity = 0;
ps->size = 0;
}
//打印顺序表
void DisplaySeqList(SL* ps)
{
assert(ps);
int i = 0;
while (i < ps->size)
{
printf("%d ", ps->a[i]);
i++;
}
printf("\n");
}
//检查容量
void SeqListCheckCapacity(SL* ps)
{
assert(ps);
if (ps->size == ps->capacity)
{
SLDataType newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
ps->capacity = newcapacity;
SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * newcapacity);
if (NULL == tmp)
{
printf("realloc fail\n");
exit(-1);
}
ps->a = tmp;
}
}
//销毁空间
void SeqListDestroy(SL* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = 0;
ps->size = 0;
}
//尾插
void SeqListPushBack(SL* ps, SLDataType x)
{
assert(ps != NULL);
SeqListCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
//尾删
void SeqListPopBack(SL* ps)
{
assert(ps);
//顺序表为空的情况
assert(ps->size > 0);
ps->size--;
}
//头插
void SeqListPushFront(SL* ps, SLDataType x)
{
assert(ps);
SeqListCheckCapacity(ps);
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[0] = x;
ps->size++;
}
//头删
void SeqListPopFront(SL* ps)
{
assert(ps);
assert(ps->size > 0);
int start = 1;
while (start < ps->size)
{
ps->a[start - 1] = ps->a[start];
start++;
}
ps->size--;
}
//顺序表查找
int SeqListFind(SL* ps, SLDataType x)
{
assert(ps);
int i = 0;
while (i < ps->size)
{
if (ps->a[i] == x)
{
return i;
}
i++;
}
return -1;
}
//从指定位置之前插入数据
void SeqListInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
SeqListCheckCapacity(ps);
assert(pos >= 0 && pos < ps->size);
int end = ps->size - 1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[pos] = x;
ps->size++;
}
//从指定位置删除数据
void SeqListErase(SL* ps, int pos)
{
assert(ps);
//顺序表为空的情况
assert(ps->size > 0);
//判断pos位置的有效性
assert(pos >= 0 && pos < ps->size);
//顺序表至少有一个数据
int start = pos + 1;
while (start < ps->size)
{
ps->a[start - 1] = ps->a[start];
start++;
}
ps->size--;
}
//从指定位置修改顺序表
void SeqListModify(SL* ps, int pos, SLDataType x)
{
assert(ps);
//判断pos位置的有效性
assert(pos >= 0 && pos < ps->size);
//顺序表为空的情况
assert(ps->size > 0);
ps->a[pos] = x;
}
test.c测试文件
#define _CRT_SECURE_NO_WARNINGS
#include"seqlist.h"
void TestSeqList1()
{
SL s1;
SeqListInit(&s1);
SeqListPushBack(&s1, 1);
SeqListPushBack(&s1, 2);
SeqListPushBack(&s1, 3);
SeqListPushBack(&s1, 4);
SeqListPushBack(&s1, 5);
DisplaySeqList(&s1);
//SeqListDestroy(&s1);
SeqListPopBack(&s1);
SeqListPopBack(&s1);
SeqListPopBack(&s1);
DisplaySeqList(&s1);
SeqListPushFront(&s1, 10);
SeqListPushFront(&s1, 20);
SeqListPushFront(&s1, 30);
SeqListPushFront(&s1, 40);
DisplaySeqList(&s1);
SeqListPopFront(&s1);
SeqListPopFront(&s1);
DisplaySeqList(&s1);
SeqListInsert(&s1, 3, 50);
SeqListInsert(&s1, 2, 60);
SeqListInsert(&s1, 5, 70);
DisplaySeqList(&s1);
int ret = SeqListFind(&s1, 10);
if (ret >= 0)
{
printf("找到了,下标是%d\n", ret);
}
SeqListErase(&s1, 0);
SeqListErase(&s1, 0);
DisplaySeqList(&s1);
SeqListModify(&s1, 3, 80);
DisplaySeqList(&s1);
SeqListDestroy(&s1);
}
int main()
{
TestSeqList1();
return 0;
}
总结:今天的顺序表到这里就结束了。觉得还ok的伙伴点点关注,收藏和点赞吧。