文章目录
1.顺序表的概念
在认识顺序表之前,让我们先认识下线性表。
线性表
线性表是由n个相同类型的数据所组成的有限序列,线性表是一种在现实里被广泛使用的,常见的线性表有:顺序表、链表、栈、队列等等。
线性表在逻辑结构一定连续,但在物理结构中并不一定是连续的。
物理结构
物理结构就是数据存储于内存的结构,就那数组来说,数组开辟的空间是连续的,第x个数据的空间的下一块空间就是第x+1个数据,这就是物理结构连续。
如果我在设计表的时候,专门设计一个指针用来存放下一个数据的地址。
如下图:
第x数据和第x+1数据的空间并不是连续的,而是被指针位连接的,是像一个绳子,每个数据位就相当于绳结,指针位相当于绳结后面的绳子,这就是物理结构不连续。
逻辑结构
逻辑结构是我们自己抽象出来的,对于线性表来说,它的逻辑结构我们就可以把他想象成直线的线性结构,就拿上面物理结构不连续的例子来说,尽管它第一个数据和第二个数据在空间上并不是连续,但是我们可以把它们想象是连续的。
就类似于下图:
顺序表是线性表的一种,顺序表的特点就是物理层面和逻辑层面都连续,数据与数据之间都是相邻的
顺序表的底层是数组!!!
2.顺序表的分类
顺序表可以分为静态和动态两种
2.1静态顺序表
在创建之前将大小固定,这样虽然很简单,但是有局限性;如果给定的数据多了,就无法完整的存储数据;如果给定的数据少了,就会造成空间的浪费
typedef int SQLDATATYPE;
typedef struct SQList
{
SQLDATATYPE a[100];//给定了100个数据,后续无法改动大小
int size;//当前数据的个数
}SQList;
哪有什么方法可以解决呢?
2.2动态顺序表
内存大小是不固定的,需要使用时再进行动态开辟,如果后面有多的数据要存储,就可以使用realloc
函数来进行增容,所以就更加的灵活
typedef int SQLDATATYPE;
typedef struct SQList
{
SQLDATATYPE* a;
int size;//当前数据的个数
int capacity;//当前顺序表的容量
}SQList;
我们本章讲解的是动态顺序表
3.顺序表接口的实现
首先我们需要一个头文件和一个源文件;
头文件是用来声明顺序表函数的,源文件是用来实现顺序表函数的实现
头文件(SQList.h)如下
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//typedef int SQLDATATYPE;
//typedef struct SQList
//{
// SQLDATATYPE a[100];//给定了100个数据,后续无法改动大小
// int size;//当前数据的个数
//}SQList;
typedef int SQLDATATYPE;
typedef struct SQList
{
SQLDATATYPE* a;
int size;//当前数据的个数
int capacity;//当前顺序表的容量
}SQList;
//初始化顺序表
void SQListInit(SQList* plist);
//销毁顺序表
void SQListDestroy(SQList* plist);
//插入(尾部)
void SQListPushBack(SQList* plist, SQLDATATYPE x);
//删除(尾部)
void SQListPopBack(SQList* plist);
//插入(头部)
void SQListPushFront(SQList* plist, SQLDATATYPE x);
//删除(头部)
void SQListPopFront(SQList* plist);
//查找元素
int SQListFind(SQList* plist, SQLDATATYPE x);
//插入(指定位置前)
void SQListInsect(SQList* plist, int pst, SQLDATATYPE x);
//删除(指定位置)
void SQListDelete(SQList* plist, int pst);
//打印顺序表
void SQListPrint(SQList* plist);
源文件
记住要包含头文件
#include"SQList.h"
初始化顺序表
//初始化顺序表
void SQListInit(SQList* plist)
{
plist->a = NULL;
//其实也可以在初始化的时候就给a开辟空间(看个人喜好)
plist->size = plist->capacity = 0;
}
一定要传址调用,这样才能真正的改变到顺序表
销毁顺序表
//销毁顺序表
void SQListDestroy(SQList* plist)
{
assert(plist);
free(plist->a);
plist->a = NULL;//这一步是为了让a不会野指针
//也可以避免a被重复释放(free函数不会对NULL进行操作)
plist->size = plist->capacity = 0;
}
插入
插入就意味这内容的增加,就必然会导致内容空间的不足,这时候我们就需要进行扩容。
扩容
我们在插入之前要看,内部是否还有空间可以让我们插入,当size==capacity时,就代表表内已经填满数据了,我们就需要进行扩容
扩容用到的是realloc
函数,我们扩容是默认是以两倍来扩容
尾插
void SQListPushBack(SQList* plist, SQLDATATYPE x)
{
assert(plist);//检查plist是否为空指针
if (plist->size == plist->capacity)//判断空间是否满了
{
int NewCapacity = plist->capacity == 0 ? 4 : 2 * plist->capacity;//此处为一个三目表达式
//如果plist->capacity==0为真,将4赋给NewCapacity;反之则赋值2*plist->capacity
SQLDATATYPE* tmp = (SQLDATATYPE*)realloc(plist->a, NewCapacity * sizeof(SQLDATATYPE));
if (tmp == NULL)
{
perror("realloc fail");
exit(1);
}
plist