数据结构——顺序表
🏖️专题:数据结构
🙈作者:暴躁小程序猿
⛺简介:双非本科大二小菜鸡一枚,希望和大家一同进步~
文章目录
一、线性表的概念
线性表是最基本、最简单、也是最常用的一种数据结构。线性表(linear list)是数据结构的一种,一个线性表是n个具有相同特性的数据元素的有限序列。
线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的(注意,这句话只适用大部分线性表,而不是全部。比如,循环链表逻辑层次上也是一种线性表(存储层次上属于链式存储,但是把最后一个数据元素的尾指针指向了首位结点)。
二、顺序表的概念
顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表中的各个元素、使得线性表中在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系,采用顺序存储结构的线性表通常称为顺序表。顺序表是将表中的结点依次存放在计算机内存中一组地址连续的存储单元中。
2.1顺序表的分类
顺序表一般可以分为:
- 静态顺序表:使用定长数组存储。
- 动态顺序表:使用动态开辟的数组存储。
2.2顺序表的静态版本
代码如下(示例):
#define N 100
typedef int SLDataType;
typedef struct SeqList
{
SLDataType array[N]; // 定长数组
size_t size; // 有效数据的个数
}SeqList;
2.2顺序表的动态版本
代码如下(示例):
// 顺序表的动态存储
typedef struct SeqList
{
SLDataType* array; // 指向动态开辟的数组
size_t size ; // 有效数据个数
size_t capicity ; // 容量空间的大小
}SeqList;
三、顺序表的增删改查操作
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表
3.1头文件
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SQType;
typedef struct SQdate
{
SQType* arr;
int size;
int capacity;
}SQ;
//初始化顺序表
void SLInit(SQ* pc);
//打印顺序表
void SQprint(SQ* pc);
//检查顺序表
void checkCapacity(SQ* pc);
//释放顺序表
void Destory(SQ* pc);
//尾插法
void SQPushBack(SQ* pc, SQType x);
//头插法
void SQPushFront(SQ* pc, SQType x);
在头文件里我们定义了结点的结构体,同时声明了我们动态顺序表需要实现的一些功能。功能的具体实现还是放在我们的SQList.c文件中。
3.2功能实现文件
#define _CRT_SECURE_NO_WARNINGS
#include"SQL.h"
void SQInit(SQ* pc)
{
assert(pc);
pc->arr = NULL;
pc->size = pc->capacity = 0;
}
void SQprint(SQ* pc)
{
assert(pc);
int i = 0;
for (i = 0; i < pc->size; ++i)
{
printf("%d ", pc->arr[i]);
}
printf("\n");
}
void checkCapacity(SQ* pc)
{
if (pc->size == pc->capacity)
{
int newCapacity = pc->capacity == 0 ? 4 : pc->capacity * 2;
SQType* tmp = (SQType*)realloc(pc->arr, newCapacity * sizeof(SQType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
pc->capacity = newCapacity;
pc->arr = tmp;
}
}
void Destory(SQ* pc)
{
assert(pc);
free(pc);
pc->arr = NULL;
pc->size = pc->capacity = 0;
}
void SQPushBack(SQ* pc,SQType x)
{
assert(pc);
checkCapacity(pc);
pc->arr[pc->size] = x;
pc->size++;
}
void SQPushFront(SQ* pc, SQType x)
{
assert(pc);
checkCapacity(pc);
int end = pc->size - 1;
while (end >= 0)
{
pc->arr[end + 1] = pc->arr[end];
--end;
}
pc->arr[0] = x;
pc->size++;
}
void SQPopback(SQ* pc,SQType x)
{
assert(pc->arr);
while (x)
{
pc->size--;
x--;
}
}
void SQPopFront(SQ* pc)
{
assert(pc);
assert(pc->arr);
int beagin = 0;
while (beagin<pc->size-1)
{
pc->arr[beagin] = pc->arr[beagin + 1];
++beagin;
}
pc->size--;
}
void SQFind(SQ* pc, SQType x)
{
assert(pc);
for (int i = 0; i < pc->size; i++)
{
if (pc->arr[i] == x)
{
return i;
}
}
printf("没有找到");
return -1;
}
void SQInsert(SQ* pc, size_t pos, SQType x)
{
assert(pc);
assert(pos < pc->size);
checkCapacity(pc);
int end = pc->size - 1;
while (pc->size >= pos)
{
pc->arr[end + 1] = pc->arr[end];
--end;
}
pc->arr[pos] = x;
pc->size++;
}
void SQErase(SQ* pc, size_t pos, SQType x)
{
assert(pc);
assert(pos < pc->size);
int end = pc->size-1;
while (end >= pos)
{
pc->arr[pos] = pc->arr[pos + 1];
--end;
}
pc->size--;
}
void SQModify(SQ* pc, size_t pos, SQType x)
{
assert(pc);
assert(pos < pc->size);
pc->arr[pos] = x;
}
我们在这个文件中实现了增删改查等操作,增加使用了头插法和尾插法,删除使用了头删法和尾删法,要注意的是我们在进行增加操作的时候一定要先检查一下我们的空间是否足够,如果不够的话就要使用动态内存分配增加空间来存放数据。
3.1检查空间是否足够功能的实现
void checkCapacity(SQ* pc)
{
if (pc->size == pc->capacity)
{
int newCapacity = pc->capacity == 0 ? 4 : pc->capacity * 2;
SQType* tmp = (SQType*)realloc(pc->arr, newCapacity * sizeof(SQType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
pc->capacity = newCapacity;
pc->arr = tmp;
}
}
我们在各种操作之前都应该判断一下是否有足够的空间可以让我们进行操作,而且还有一种极端情况就是刚开始这个顺序表的容量就为0,我们就应该先判断容量是否为0,如果为0我们先将这个顺序表的容量置为4,如果容量不为0,我们就将容量扩大为原来的2倍用来存储新加入的数据,这里就要用到动态内存分配的知识,这里使用的函数是realloc函数,它的功能就是扩容,我们先用一个结构体指针来接收扩容之后空间的地址,如果扩容失败就返回realloc fail,如果扩容成功我们就将这个指针给结构体指针所指向的arr,同时将新的容量给结构体指针所指向的容量,更新数据。
3.2头插法功能实现
void SQPushFront(SQ* pc, SQType x)
{
assert(pc);
checkCapacity(pc);
int end = pc->size - 1;
while (end >= 0)
{
pc->arr[end + 1] = pc->arr[end];
--end;
}
pc->arr[0] = x;
pc->size++;
}
我们头插法是要在顺序表的头部插入数据,我们首先还是要判断这个顺序表的容量是否可以容纳我们插入后的所有数据,之后我们确定一下数组末尾的位置,从后往前将原有的数据依次向后移动一位,最后将我们要插入的数据插入到头部,即可完成头部插入。
3.3测试文件
#define _CRT_SECURE_NO_WARNINGS
#include"SQL.h"
int main()
{
SQ s;
SQInit(&s);
SQPushFront(&s, 3);
SQprint(&s);
}
这里我们就简单的测试一下头插法然后将顺序表打印出来,其余功能直接调用函数即可。
总结
本篇博客涉及顺序表的定义,顺序表的静态实现和动态实现,还有顺序表增删改查等基本操作的实现,希望对大家有所帮助,欢迎大家私信,我们明天见~