一、线性表
在实现顺序表之前我们先了解以下线性表。线性表(liner list)实际上就是一些有着相同特性的数据元素的有限队列,其在逻辑上是线性结构,这也是为什么被称为线性表的原因,但是需要注意的是其在物理结构上并不一定是线性连续的,在物理存储时通常就是数组和链式结构的存储。我们常见的线性表有顺序表、链表、栈、队列、字符串等。
顺序表
链表
二、顺序表
1、什么是顺序表
顺序表就是利用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,后面的接口实现就是在数组的基础上实现的。
2、创建顺序表(顺序表的分类)
顺序表一般可以分为静态的和动态的,这里的区别仅是在数组的大小是静态的(提前固定好的大小,以后不能改变大小,不利于增删数据)还是动态的(根据增删的数据个数动态改变数组的大小)。
在创建顺序表时,我们需要一个存储数据的数组,另外还需要一个变量用来存储数组中有效数据的个数,如果是动态顺序表的话我们还需要一个变量来存储顺序表的容量,以便容量满的时候进行增容。所以这里我们需要创建一个结构体,结构体成员分别是数组、有效数据个数、数组容量大小。
静态顺序表
#define N 10
typedef int SLDataType;//数据类型定义方便更改
typedef struct SeqList
{
SLDataType array[N]; //定长数组
size_t size; //有效数据个数
}SeqList;
动态顺序表
typedef int SLDataType; //类型重命名,后续要存储其它类型时方便更改
typedef struct SeqList
{
SLDataType* a; //指向动态开辟的数组
size_t size; //有效数据个数
size_t capacity; //容量大小
}SeqList;
三、顺序表的接口实现
静态顺序表因为其数组定长的特点,容易出现浪费空间或者空间不够的情况,为了方便数据的增删查改,我们实现动态的顺序表。
1、头文件
头文件中包含了顺序表中的所有功能的实现函数,如下
#pragma once//防止重复包含头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* array;
size_t size;
size_t capicity;
}SeqList;
void SeqListInit(SeqList* ps1);//初始化
void SeqListCheck(SeqList* ps1);//检查空间,若满,增容
void SeqListPrint(SeqList* ps1);//打印顺序表
void SeqListPushBack(SeqList* ps1, SLDataType x);//尾插
void SeqListPophBack(SeqList* ps1);//尾删
void SeqListPushFront(SeqList* ps1, SLDataType x);//头插
void SeqListPopFront(SeqList* ps1);//头删
void SeqListInsert(SeqList* ps1, SLDataType x,int pos);//在位置pos处插入
void SeqListErase(SeqList* ps1,int pos);//删除pos处的值
int SeqListFind(SeqList* ps1, SLDataType x);//查找x
void SeqListDestory(SeqList* psl);//销毁顺序表
2、顺序表各功能实现
1)、初始化
当创建一个顺序表时,则需要对它进行初始化,先对结构体的数组来说开辟一块空间。并将有效数据元素置为0,数组容量即刚刚开辟出来的空间大小。
void SeqListInit(SeqList* ps1)
{
assert(ps1);//断言,防止传进来空指针
ps1->array = (SLDataType*)malloc(sizeof(SLDataType) * 4);//开辟空间
if (ps1->array == NULL)//判断开辟空间是否成功
{
printf("申请内存失败\n");
}
ps1->size = 0;
ps1->capicity = 4;
}
2)、销毁
顺序表创建完使用完到最后一定要将其销毁,否则会造成内存泄漏。
void SeqListDestory(SeqList* psl)
{
assert(psl != NULL);
free(psl->array);
psl->array = NULL;
psl->size = 0;
psl->capicity = 0;
}
3)、判断顺序表容量是否满
因为我们实现的是动态的顺序表,在容量不够的情况下我们要适当进行增容,所以判断是否满容量函数是必要的。
当有效数据个数和当前容量大小相同则进行扩容,为了防止浪费我们只扩容当前容量的两倍,且使用relloc函数,防止后面找不到一块物理连续的空间来存放数据。
void SeqListCheck(SeqList* ps1)
{
//如果容量满了,需要扩容
if (ps1->size >= ps1->capicity)
{
ps1->capicity *= 2;
ps1->array = (SLDataType*)realloc(ps1->array, sizeof(SLDataType) * ps1->capicity);
}
if (ps1->array == NULL)
{
printf("扩容失败");
exit(-1);
}
}
4)、打印
void SeqListPrint(SeqList* ps1)
{
assert(ps1);
int i = 0;
for (i = 0; i < ps1->size; ++i)
{
printf("%d", ps1->array[i]);
}
printf("\n");
}
5)、尾插
在当前顺序表的尾部插入,先判断容量,再将元素插入到最后。
void SeqListPushBack(SeqList* ps1, SLDataType x)
{
assert(ps1);
SeqListCheck(ps1);
ps1->array[ps1->size] = x;
ps1->size++;
}
6)、头插
在顺序表头部插入时,先检查容量,再用循环从后向前将每一个元素向后移动一位,再将元素插入
void SeqListPushFront(SeqList* ps1, SLDataType x)
{
assert(ps1);
SeqListCheck(ps1);
int end = ps1->size - 1;
while (end >= 0)
{
ps1->array[end + 1] = ps1->array[end];
--end;
}
ps1->array[0] = x;
ps1->size++;
}
7)、尾删
删除的本质实际上是让计算机找不到这个元素,所以我们可以直接将其地址抹除。
void SeqListPophBack(SeqList* ps1)
{
assert(ps1);
ps1->size--;//把地址抹除,让找不到这个数据,并不是删除掉这个数据
}
8)、头删
要删除头元素直接让头元素后面的每一位元素向前移动一位覆盖掉头元素,最后令有效数字个数减一即可。
void SeqListPopFront(SeqList* ps1)
{
assert(ps1);
int start = 0;
while(start<ps1->size-1)
{
ps1->array[start] = ps1->array[start + 1];
++start;
}
ps1->size--;
}
9)、寻找元素
int SeqListFind(SeqList* ps1, SLDataType x)
{
assert(ps1);
int i = 0;
for (i = 0; i < ps1->size; i++)
{
if (x == ps1->array[i])
{
return i;
}
}
return -1;//没找到
}
10)、插入中间元素
void SeqListInsert(SeqList* ps1, SLDataType x,int pos)
{
assert(ps1);
SeqListCheck(ps1);
int end = ps1->size;
while (end>pos)
{
ps1->array[end] = ps1->array[end - 1];
end--;
}
ps1->array[pos] = x;
ps1->size++;
}
11)、删除中间元素
void SeqListErase(SeqList* ps1,int pos)
{
assert(ps1);
while (pos < ps1->size-1)
{
ps1->array[pos] = ps1->array[pos + 1];
pos++;
}
ps1->size--;
}