数据结构——顺序表

目录

一、线性表

二、顺序表

2.1 顺序表的定义

2.2 顺序表的种类

2.3 顺序表的增删查改接口实现

(1)顺序表初始化

(2)检查顺序表容量,如果满了,进行增容

(3)顺序表尾插

(4)顺序表头插

(5)顺序表尾删

(6)顺序表头删

(7)顺序表中查找目标值

(8)顺序表指定位置插入数据

(9)顺序表删除指定位置的数据

(10)顺序表销毁

(11)顺序表打印


一、线性表

顺序表是线性表的一种。线性表(linear list)是一种具有n个相同特性的数据元素的有限序列,是一种被广泛运用的数据结构,常见的线性表有:顺序表、链表、栈、队列、数组、字符串

顾名思义,线性表在逻辑上是线性结构,就像一串珠子穿在一根绳上。但是线性表在物理结构上不一定是连续的,线性表以数组的形式存储时和以链式结构存储时在物理内存上的布局是不同的

线性表以数组的形式存储时在内存中是连续的,而线性表以链式结构存储时在内存中不一定连续


二、顺序表

2.1 顺序表的定义

线性表的顺序存储又称为顺序表,顺序表是用一段地址连续的存储单元来依次存储数据元素的线性结构,从而使得逻辑上相邻的两个元素在物理位置上也相邻,也就是说我们可以通过下标来依次访问结构中的内容。平时我们使用的数组也是顺序表的一种实现方式。

2.2 顺序表的种类

顺序表一般可以分为:

        (1)静态顺序表:使用定长数组存储元素

首先我们要知道怎么创建一个静态顺序表

#define N 10
typedef int SLDataType;

typedef struct SeqList
{
    SLDataType arr[N]; //定长数组
    size_t size; //有效数据的个数
} SeqList;

我们可以看到,静态顺序表中定义了一个定长数组,也就是说数组一旦开辟就无法再更改大小了。

所以静态顺序表只适用于已知需要存储多少数据的场景,如果定长数组过大,就会浪费空间,而太小了又不够用。所以日常中我们基本使用动态顺序表,可以根据需要来动态分配空间,接下来我们再认识一下动态顺序表

        (2)动态顺序表:使用动态开辟的数组存储

typedef int SLDataType;

typedef struct SeqList
{
    SLDataType* arr; //指向动态开辟的空间
    size_t size; //有效数据个数
    size_t capicity; //开辟空间的容量大小
} SeqList;

动态顺序表中存储数据的空间需要我们进行动态内存开辟,以上便是动态顺序表的结构

2.3 顺序表的增删查改接口实现

接下来我们手把手逐步的来实现顺序表的增删查改接口,当然此处使用的是动态顺序表

此次演示使用的是vs2019,我们先创建一个新工程,并新建一个头文件"SeqList.h"和两个源文件"SeqList.c"和"test.c",具体作用为:

  • SeqList.h:顺序表的定义,头文件的引用和接口函数的声明
  • SeqList.c:接口函数的实现
  • test.c:测试各个函数

首先我们展示"SeqList.h"的完整代码,不要忘记在两个源文件中引用"SeqList.h"

#pragma once //防止头文件被二次引用

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int SLDataType; //如果要修改存储的数据类型可直接在此修改

typedef struct SeqList
{
    SLDataType* a;
    size_t size;
    size_t capicity;
} SeqList;

// 基本增删查改接口
// 顺序表初始化
void SeqListInit(SeqList* psl);
// 检查顺序表容量,如果满了,进行增容
void CheckCapacity(SeqList* psl);
// 顺序表尾插
void SeqListPushBack(SeqList* psl, SLDataType x);
// 顺序表尾删
void SeqListPopBack(SeqList* psl);
// 顺序表头插
void SeqListPushFront(SeqList* psl, SLDataType x);
// 顺序表头删
void SeqListPopFront(SeqList* psl);
// 顺序表中查找目标值
int SeqListFind(SeqList* psl, SLDataType x);
// 顺序表指定位置插入数据
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x);
// 顺序表删除指定位置的数据
void SeqListErase(SeqList* psl, size_t pos);
// 顺序表销毁
void SeqListDestory(SeqList* psl);
// 顺序表打印
void SeqListPrint(SeqList* psl);

接下来,我们逐个的实现各个接口函数,每一步都进行详细讲解,必须让你学会

(1)顺序表初始化

void SeqListInit(SeqList* psl)
{
    assert(psl); //断言,防止传入空指针
    psl->a = (SLDataType*)malloc(sizeof(SLDataType) * 4); //初始化空间大小,可自己定义
    if (psl->a == NULL) //防止空间开辟失败
    {
        perror("malloc fail");
        return;
    }
    psl->size = 0; //初始化有效元素个数
    psl->capicity = 4; //初始化空间容量大小
}

(2)检查顺序表容量,如果满了,进行增容

void CheckCapacity(SeqList* psl)
{
    assert(psl); //断言,防止传入空指针
    if (psl->size == psl->capicity) //如果有效数据个数等于空间容量大小则已满,增容
    {
        SLDataType* tmp = (SLDataType*)realloc(psl->a, sizeof(SLDataType) * psl->capicity * 2); 
        //创建一个临时指针变量来存储新空间地址,防止开辟失败
        //realloc重新开辟一块更大的空间,具体大小可自定义,这里取原来空间的2倍
        if (tmp == NULL) //防止空间开辟失败
        {
            perror("realloc fail");
            return;
        }
        psl->a = tmp; //将临时指针变量中存放的新空间地址赋值给a
        psl->capicity *= 2; //空间容量更新
    }
}

(3)顺序表尾插

void SeqListPushBack(SeqList* psl, SLDataType x)
{
    assert(psl); //断言,防止传入空指针
    CheckCapacity(psl); //如果空间已满则增容
    psl->a[psl->size++] = x; //在空间a中的下标为size的地方插入数据x,然后size更新
}

测试一下

(4)顺序表头插

void SeqListPushFront(SeqList* psl, SLDataType x)
{
    assert(psl); //断言,防止传入空指针
    CheckCapacity(psl); //如果空间已满则增容
    for (int end = psl->size - 1; end >= 0; end--) //从后到前,通过循环将所有数据向后移动
    {
        psl->a[end + 1] = psl->a[end];
    }
    psl->a[0] = x; //在头部插入新数据
    psl->size++; //size更新
}

测试一下

(5)顺序表尾删

void SeqListPopBack(SeqList* psl)
{
    assert(psl); //断言,防止传入空指针
    assert(psl->size > 0); //断言,防止愣头青没有数据了还在删
    psl->size--; //通过更新size把尾部数据踢出即可
}

测试一下

(6)顺序表头删

void SeqListPopFront(SeqList* psl)
{
    assert(psl); //断言,防止传入空指针
    assert(psl->size > 0); //断言,防止愣头青没有数据了还在删
    for (int i = 0; i < psl->size - 1; i++) //从前到后,通过循环将所有数据向前移动,把头数据覆盖
    {
        psl->a[i] = psl->a[i + 1];  
    }
    psl->size--; //size更新
}

测试一下

(7)顺序表中查找目标值

int SeqListFind(SeqList* psl, SLDataType x)
{
    assert(psl); //断言,防止传入空指针
    for (int i = 0; i < psl->size; i++) //遍历顺序表
    {
        if (psl->a[i] == x)
        {
            return i; //找到了则返回下标
        }
    }
    return -1; //没有找到则返回-1
}

测试一下

(8)顺序表指定位置插入数据

void SeqListInsert(SeqList* psl, size_t pos, SLDataType x) 
{
    assert(psl); //断言,防止传入空指针
    assert(0 <= pos && pos <= psl->size); //断言,防止pos超出有效数据范围
    CheckCapacity(psl); //如果空间已满则增容
    for (int end = psl->size - 1; end >= pos; end--) //把pos之后的数据向后移动
    {
        psl->a[end + 1] = psl->a[end];
    }
    psl->a[pos] = x; //在pos处插入数据
    psl->size++; //size更新
}

测试一下

细心的人可以发现,上面的头插不就是在0的位置插入数据吗?尾插不就是在size-1的位置插入数据吗?所以实际上,头插和尾插都可以用SeqListInsert这个函数来代替实现

void SeqListPushFront(SeqList *psl, SLDataType x)//头插改造
{
    SeqListInsert(psl, 0, x);
}

void SeqListPushBack(SeqList *psl, SLDataType x)//尾插改造
{
    SeqListInsert(psl, (psl->size)-1, x);
}

(9)顺序表删除指定位置的数据

void SeqListErase(SeqList* psl, size_t pos)
{
    assert(psl); //断言,防止传入空指针
    assert(0 <= pos && pos < psl->size); //断言,防止pos超出有效数据范围
    while (pos < psl->size - 1) //把pos之后的数据向前移,覆盖pos处的数据
    {
        psl->a[pos] = psl->a[pos + 1];
        pos++;
    }
    psl->size--; //size更新
}

测试一下

和上面一样,头删就是在0的位置删除数据,尾插就是在size-1的位置删除数据,所以头删和尾删也可以用SeqListErase来代替实现

void SeqListPopFront(SeqList *psl)//头插改造
{
    SeqListErase(psl, 0);
}

void SeqListPopBack(SeqList *psl)//尾插改造
{
    SeqListErase(psl, (psl->size)-1);
}

(10)顺序表销毁

void SeqListDestory(SeqList* psl)
{
    assert(psl); //断言,防止传入空指针
    free(psl->a); //把a指向的空间释放
    psl->a = NULL; //置空,避免野指针
    psl->capicity = 0; //空间容量为0
    psl->size = 0; //有效数据为0
}

(11)顺序表打印

void SeqListPrint(SeqList* psl)
{
    assert(psl); //断言,防止传入空指针
    for (size_t i = 0; i < psl->size; i++) //遍历顺序表
    {
        printf("%d ", psl->a[i]);
    }
    printf("\n");
}

完.

  • 43
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值