数据结构 - 顺序表

490 阅读5分钟

一. 顺序表的定义

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构, 一般情况下采用数组存储, 在数组上完成数据的增删查改.

二. 顺序表的分类

1. 静态顺序表

使用定长数组存储元素.

image.png

2. 动态顺序表

使用动态开辟的数组存储元素.

image.png

三. 顺序表各种接口的实现

静态顺序表只适用于确定知道需要存储多少数据的场景. 对于静态顺序表, 若空间开多了会浪费,开少了则不够用. 所以现实中基本都是使用动态顺序表, 根据需要动态地分配空间大小, 所以下面实现动态顺序表.

image.png

typedef int SLDataType;
#define INIT_CAPACITY 4

// 动态顺序表 -- 按需申请
typedef struct SeqList
{
    SLDataType* a;  // 指向动态开辟的数组的指针
    int size;  // 有效数据的个数
    int capacity;  // 空间容量的大小
}SL;

1. 初始化

创建好结构体后, 对其进行初始化.

void SLInit(SL* ps)
{
    assert(ps);

    ps->a = (SLDataType*)malloc(sizeof(SLDataType) * INIT_CAPACITY);
    if (ps->a == NULL) {
        perror("malloc fail");
        return;
    }

    ps->size = 0;
    ps->capacity = INIT_CAPACITY;
}

2. 销毁

当顺序表不再使用时, 将动态开辟的数组销毁.

void SLDestroy(SL* ps)
{
    assert(ps);

    free(ps->a);
    ps->a = NULL;
    ps->capacity = ps->size = 0;
}

3. 打印

在进行各部分功能的实现时, 为了直观地看到顺序表的变化, 我们可以将顺序表打印出来.

void SLPrint(SL* ps)
{
    assert(ps);

    for (int i = 0; i < ps->size; ++i) {
        printf("%d ", ps->a[i]);
    }
    printf("\n");
}

4. 扩容

在实现插入的功能时, 顺序表中存储的数据不断增多, 而当有效数据的个数与空间容量大小相等时, 我们需要对动态开辟的数组进行扩容.

对动态开辟的数组进行扩容时, 需要使用 realloc 函数.

为了避免多次扩容效率降低, 以及扩容后数组空间过大导致空间浪费的状况, 我们选择进行指数型扩容, 在这里就选择将其容量扩增到原来的2倍.

image.png

void SLCheckCapacity(SL* ps)
{
    assert(ps);

    if (ps->size == ps->capacity) {
        SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * ps->capacity * 2);
        if (tmp == NULL) {
            perror("realloc fail");
            return;
        }

        ps->a = tmp;
        ps->capacity *= 2;
    }
}

5. 尾插

尾插, 即在顺序表的最后增加一个数据.

image.png

注意: 在尾插前先检查是否需要扩容.

void SLPushBack(SL* ps, SLDataType x)
{
    assert(ps);

    SLCheckCapacity(ps);

    ps->a[ps->size++] = x;
}

6. 尾删

尾删, 即删除顺序表中的最后一个数据.

然而顺序表中数据的个数是有限的, 当其中不存在数据时, 不能进行尾删.

image.png

void SLPopBack(SL* ps)
{
    assert(ps);
    assert(ps->size > 0);

    ps->size--;
}

7. 头插

头插, 即在顺序表的开头增加一个数据.

注意: 同尾插一样, 在头插前先检查是否需要扩容.

image.png

void SLPushFront(SL* ps, SLDataType x)
{
    assert(ps);

    SLCheckCapacity(ps);

    for (int end = ps->size; end > 0; --end) {
        ps->a[end] = ps->a[end - 1];
    }

    ps->a[0] = x;
    ps->size++;
}

8. 头删

头删, 即删除顺序表中的第一个数据.

image.png

void SLPopFront(SL* ps)
{
    assert(ps);
    assert(ps->size > 0);

    for (int begin = 0; begin < ps->size - 1; ++begin) {
        ps->a[begin] = ps->a[begin + 1];
    }

    ps->size--;
}

9. pos 位置插入

在下标为 pos 的位置插入数据.

image.png

void SLInsert(SL* ps, int pos, SLDataType x)
{
    assert(ps);
    assert(pos >= 0 && pos <= ps->size);
	
    SLCheckCapacity(ps);

    for (int end = ps->size; end > pos; --end) {
        ps->a[end] = ps->a[end - 1];
    }
	
    ps->a[pos] = x;
    ps->size++;
}

10. pos 位置删除

删除下标为 pos 的数据.

image.png

void SLErase(SL* ps, int pos)
{
    assert(ps);
    assert(pos >= 0 && pos < ps->size);

    for (int begin = pos; begin < ps->size - 1; ++begin) {
        ps->a[begin] = ps->a[begin + 1];
    }

    ps->size--;
}

11. 查找

通常, 元素的查找与数据的插入与删除配合使用, 所以我们选择函数的返回类型为 int (与数组的下标一致)

int SLFind(SL* ps, SLDataType x)
{
    assert(ps);

    for (int i = 0; i < ps->size; ++i) {
        // 查找到返回下标 i
        if (ps->a[i] == x) {
            return i;
        }
    }
    
    // 查找不到返回 -1
    return -1;
}

12. 头插, 头删, 尾插, 尾删的常态化

我们可以认为, 头插、尾插是任意下标位置插入的特殊化, 头删、尾删是任意下标位置删除的特殊化.

头插和尾插分别是下标位置为 0 和 size 的插入,

头删和尾删分别是下标位置为 0 和 size - 1 的删除.

如此, 我们便可以将头插、头删、尾插、尾删常态化.

// 尾插
void SLPushBack(SL* ps, SLDataType x)
{
    SLInsert(ps, ps->size, x);
}

// 尾删
void SLPopBack(SL* ps)
{
    SLErase(ps, ps->size - 1);
}

// 头插
void SLPushFront(SL* ps, SLDataType x)
{
    SLInsert(ps, 0, x);
}

// 头删
void SLPopFront(SL* ps)
{
    SLErase(ps, 0);
}

四. 顺序表的源码

SeqList.h

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

typedef int SLDataType;
#define INIT_CAPACITY 4

// 动态顺序表 -- 按需申请
typedef struct SeqList
{
    SLDataType* a;  // 指向动态开辟的数组的指针
    int size;  // 有效数据的个数
    int capacity;  // 空间容量的大小
}SL;

// 初始化
void SLInit(SL* ps);

// 销毁
void SLDestroy(SL* ps);

// 打印
void SLPrint(SL* ps);

// 扩容
void SLCheckCapacity(SL* ps);

// 尾插
void SLPushBack(SL* ps, SLDataType x);

// 尾删
void SLPopBack(SL* ps);

// 头插
void SLPushFront(SL* ps, SLDataType x);

// 头删
void SLPopFront(SL* ps);

// pos位置插入
void SLInsert(SL* ps, int pos, SLDataType x);

// pos位置删除
void SLErase(SL* ps, int pos);

// 查找
int SLFind(SL* ps, SLDataType x);

SeqList.c

#include "SeqList.h"

void SLInit(SL* ps)
{
    assert(ps);

    ps->a = (SLDataType*)malloc(sizeof(SLDataType) * INIT_CAPACITY);
    if (ps->a == NULL) {
        perror("malloc fail");
        return;
    }

    ps->size = 0;
    ps->capacity = INIT_CAPACITY;
}

void SLDestroy(SL* ps)
{
    assert(ps);

    free(ps->a);
    ps->a == NULL;
    ps->capacity = ps->size = 0;
}

void SLPrint(SL* ps)
{
    assert(ps);

    for (int i = 0; i < ps->size; ++i) {
        printf("%d ", ps->a[i]);
    }
    printf("\n");
}

void SLCheckCapacity(SL* ps)
{
    assert(ps);

    if (ps->size == ps->capacity) {
        SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * ps->capacity * 2);
        if (tmp == NULL) {
            perror("realloc fail");
            return;
        }

        ps->a = tmp;
        ps->capacity *= 2;
    }
}

void SLPushBack(SL* ps, SLDataType x)
{
    //assert(ps);

    //SLCheckCapacity(ps);

    //ps->a[ps->size++] = x;

    SLInsert(ps, ps->size, x);

}

void SLPopBack(SL* ps)
{
    //assert(ps);
    //assert(ps->size > 0);

    //ps->size--;

    SLErase(ps, ps->size - 1);
}

void SLPushFront(SL* ps, SLDataType x)
{
    //assert(ps);

    //SLCheckCapacity(ps);

    //for (int end = ps->size; end > 0; --end) {
    //    ps->a[end] = ps->a[end - 1];
    //}

    //ps->a[0] = x;
    //ps->size++;

    SLInsert(ps, 0, x);
}

void SLPopFront(SL* ps)
{
    //assert(ps);
    //assert(ps->size > 0);

    //for (int begin = 0; begin < ps->size - 1; ++begin) {
    //    ps->a[begin] = ps->a[begin + 1];
    //}

    //ps->size--;

    SLErase(ps, 0);
}

void SLInsert(SL* ps, int pos, SLDataType x)
{
    assert(ps);
    assert(pos >= 0 && pos <= ps->size);
	
    SLCheckCapacity(ps);

    for (int end = ps->size; end > pos; --end) {
        ps->a[end] = ps->a[end - 1];
    }
	
    ps->a[pos] = x;
    ps->size++;
}

void SLErase(SL* ps, int pos)
{
    assert(ps);
    assert(pos >= 0 && pos < ps->size);

    for (int begin = pos; begin < ps->size - 1; ++begin) {
        ps->a[begin] = ps->a[begin + 1];
    }

    ps->size--;
}

int SLFind(SL* ps, SLDataType x)
{
    assert(ps);

    for (int i = 0; i < ps->size; ++i) {
        // 查找到返回下标 i
        if (ps->a[i] == x) {
            return i;
        }
    }

    // 查找不到返回 -1
    return -1;
}