01数据结构 - 顺序表

这里是只讲干货不讲废话的炽念,这个系列的文章是为了我自己以后复习数据结构而写,所以可能会用一种我自己能够听懂的方式来描述,不会像书本上那么枯燥和无聊,且全系列的代码均是可运行的代码,关键地方会给出注释^_^

全文近8000字

版本:C++17

编译器:Clion 2023.3.24

暂时只给出代码,不会涉及到基础知识的讲解

1.线性表的定义

//定义顺序表的表头结构
typedef struct {
    Element* data;              // 指向顺序表的数据区域
    int len;                    // 该区域能够访问的边界条件,目前内容器存储数据的数量
    int cap;                    // 该区域最大容量,超过这个容量就需要扩容
}SeqList;

// len 始终指向待插入位置

2.线性表的实现函数

2.1.表的创建以及删除

// 表的创建以及删除
SeqList *createSeqList(int n);                  // 创造一个表并初始化

void releaseSeqList(SeqList *seqList);          // 释放掉表头以及表内的数据

 2.2.扩容函数

// 辅助函数
int enLargerSeq(SeqList *seqList);                  // 扩容函数

2.3.向表中插入元素

// 三种插入方式
int pushBackSeq(SeqList *seqList, Element val);         // 往尾部插入一个元素

int headInsertSeq(SeqList *seqList, Element val);       // 往头部插入一个元素

int insertSeq(SeqList *seqList, int pos, Element val);  // 按指定值来插入元素

2.4.表的展示以及查找

// 展示以及查找
void showSeqList(SeqList *seqList);                // 遍历并显示数据

int findSeq(SeqList *seqList, Element val);        // 按值查找

2.5.删除表中的元素

// 三种方式删除
int deleteSpeSeq(SeqList *seqList, Element val);   // 删除特定值的元素

int deleteHeadSeq(SeqList *seqList);               // 删除首元素

int deleteTailSeq(SeqList *seqList);               // 删除尾元素

3.实现函数的代码

3.1.createSeqList函数的实现

函数定义:
SeqList *createSeqList(int n);                  // 创造一个表并初始化

函数功能:
该函数用于创建一个长度为n的顺序表,并返回指向该顺序表的指针

实现思路
a.申请表头空间

b.申请表内元素的空间

c.初始化 len(length 长度) 和 cap(capacity 容量)

d.对申请的空间进行检查,如果申请空间失败,则需要返回NULL

具体实现代码:
SeqList *createSeqList(int n) {
    SeqList *seqList;
    seqList = (SeqList *)malloc(sizeof(SeqList));               //申请表头
    if (seqList == NULL){
        printf("Malloc seqlist failed\n");
        return NULL;
    }

    seqList->data = (Element *) malloc(sizeof(Element) * n);    //申请空间
    if (seqList->data == NULL){
        printf("Malloc data failed");
        return NULL;
    }

    seqList->len = 0;
    seqList->cap = n;

    return seqList;
}

测试代码:
int main() {
    SeqList *list = createSeqList(10);
    if (list == NULL) {
        printf("创建顺序表失败\n");
        return 1;
    }
    printf("顺序表创建成功\n");
    free(list->data);
    free(list);

    return 0;
}

测试结果:

3.2 releaseSeqList函数的实现

函数定义:
void releaseSeqList(SeqList *seqList);          // 释放掉表头以及表内的数据

函数功能:
该函数用于释放一个顺序表所占用的内存空间,防止内存泄漏

实现思路:
a.先判断表头再判断数据域是否为空

b.如果数据域不为空,则释放掉表内的数据域空间

c.释放掉表头空间

 具体实现代码:
void releaseSeqList(SeqList *seqList) {
    if (seqList){
        if (seqList->data){
            free(seqList->data);
        }
        free(seqList);
        printf("Release success!\n");
    }
}

测试代码:
int main() {
    SeqList *list = createSeqList(10);
    if (list == NULL) {
        printf("创建顺序表失败\n");
        return 1;
    }
    printf("顺序表创建成功\n");

    releaseSeqList(list);  // 释放顺序表

    return 0;
}

测试结果:

3.3 enLargerSeq 函数的实现

函数定义:
int enLargerSeq(SeqList *seqList);                  // 扩容函数

函数功能:
该函数用于对顺序表进行扩容,将顺序表的大小扩展为原来的两倍

实现思路:
a.先申请一个新空间 tmp,新空间的大小为原来空间大小(seqList->len / seqList->cap)的两倍
b.将原来空间的值通过 for 循环赋值给新空间
c.释放掉原来表头指向的空间 seqList->data
d.将表头的 seqList->data 指向新申请的空间 tmp
e.将 seqList->cap 大小变为原来的两倍

具体实现代码:
int enLargerSeq(SeqList *seqList) {
    Element *tmp = (Element *) malloc(sizeof (Element) * 2 * seqList->cap);
    if (!tmp){
        printf("Enlarger element failed!\n");
        return -1;
    }

    for (int i = 0; i < seqList->len; i++){
        tmp[i] = seqList->data[i];
    }

    free(seqList->data);    // 这两句是否有先后?
    seqList->data = tmp;    // 有先后,如果调换顺序就会先指向新的内存空间,然后再释放老的内存空间,此时老的内存空间已经找不到了,会发生内存泄露
    seqList->cap *= 2;
    printf("Enlarge success!\n");
    return 0;
}

测试代码:
int main(){
    SeqList *list = createSeqList(5);
    for (int i = 0; i < 5; i++){
        pushBackSeq(list, i);
    }

    pushBackSeq(list,666);
    showSeqList(list);
    printf("%d", list->cap);
    return 0;
}

测试结果:

3.4 showSeqList && findSeq 函数的实现

函数定义:
void showSeqList(SeqList *seqList);                // 遍历并显示数据

int findSeq(SeqList *seqList, Element val);        // 按值查找

函数功能:

1.showSeqList 函数

该函数用于遍历并显示顺序表中的所有数据

2.findSeq 函数 

该函数用于在顺序表中查找指定的值,并返回该值的索引。如果未找到,则返回 -1

实现思路:

1.showSeqList 函数

a.首先判断传入的表是否为空
b.使用 for 循环遍历整个表并打印数据

2.findSeq 函数

a.判断传染的表是否为空且表的长度是否合法(seqList->len > 0)
b.使用for循环遍历表,如果找到了val就直接返回该数的下标
c.如果没找到,打印提示信息,返回-1

具体实现代码:
// showSeqList 函数
void showSeqList(SeqList *seqList) {
    if (seqList == NULL || seqList->data == NULL){
        printf("SeqList is null.Show error\n");
        return;
    }
    for (int i = 0; i < seqList->len; i++){
        printf("%d ", seqList->data[i]);
    }
    printf("\n");
}

// findSeq 函数
int findSeq(SeqList *seqList, Element val) {
    if (!seqList || !seqList->data || seqList->len <= 0){
        printf("SeqList is null!\n");
        return -1;
    }

     for (int i = 0; i < seqList->len; i++){
         if (seqList->data[i] == val){
             return i;
         }

     }

     printf("Find value error!\n");
     return -1;
}

测试代码:
int main(){
    SeqList *list = createSeqList(5);
    for (int i = 0; i < 5; i++){
        pushBackSeq(list, i);
    }

    showSeqList(list);

    int index1 = findSeq(list, 666);
    int index2 = findSeq(list,0);
    printf("%d\n", index1);
    printf("%d\n", index2);

    releaseSeqList(list);
    return 0;
}

测试结果:

3.4 pushBackSeq && headInsertSeq && insertSeq 函数的实现

函数定义:
int pushBackSeq(SeqList *seqList, Element val);         // 往尾部插入一个元素

int headInsertSeq(SeqList *seqList, Element val);       // 往头部插入一个元素

int insertSeq(SeqList *seqList, int pos, Element val);  // 按指定位置来插入元素

函数功能:
1.pushBackSeq 函数
在顺序表尾部插入一个元素
2.headInsertSeq 函数
在顺序表头部插入一个元素
3.insertSeq 函数
在顺序表指定位置插入一个元素

实现思路:

1.pushBackSeq 函数

a.判断传入的链表以及数据域是否为空

b.判断当前元素是否到达空间上限,如果是则需要扩容

c.往 seqList->len 的位置放入元素

d.seqList->len++

 2.headInsertSeq 函数

a.判断传入的链表以及数据域是否为空

b.判断当前元素是否到达空间上限,如果是则需要扩容

c.从第 1 个元素开始,依次往后移动元素

d.往下标为 0 的位置放入元素

e.seqList->len++

3.insertSeq 函数

a.判断传入的链表以及数据域是否为空

b.判断当前元素是否到达空间上限,如果是则需要扩容

c.从第 seqList->len-1 个元素开始,直到第 pos 个元素(包含),从后往前移动元素

d.往下标为 pos 的位置放入元素

e.seqList->len++

具体实现代码:

1.pushBackSeq 函数

// pushBackSeq 函数
int pushBackSeq(SeqList *seqList, Element val) {
    if (!seqList || !seqList->data){
        printf("Seqlist is null");
        return -1;
    }

    if (seqList->len >= seqList->cap){
        printf("Insertion overflow. Consider enlarging the structure!\n");
        if (enLargerSeq(seqList) == -1){
            printf("Enlarge failed!\n");
            return -1;
        }
    }

    seqList->data[seqList->len] = val;
    seqList->len++;

    return 0;
}

  2.headInsertSeq 函数

// headInsertSeq 函数
int headInsertSeq(SeqList *seqList, Element val) {
    if (!seqList || !seqList->data){
        printf("Seqlist is null");
        return -1;
    }

    if (seqList->len >= seqList->cap){
        printf("Insertion overflow. Consider enlarging the structure!\n");
        if (enLargerSeq(seqList) == -1){
            printf("Enlarge failed!\n");
            return -1;
        }
    }

    for (int i = seqList->len; i > 0; i--) {        // 核心移动逻辑
        seqList->data[i] = seqList->data[i-1];
    }

    seqList->data[0] = val;
    seqList->len++;
    return 0;
}

 3.insertSeq 函数

// insertSeq 函数
int insertSeq(SeqList *seqList, int pos, Element val) {
    if (!seqList || !seqList->data){
        printf("Seqlist is null");
        return -1;
    }
    
    // 为什么是 > seqList->len,而不是 > seqList->cap?
    // 顺序表是一种线性数据结构,它要求元素在内存中连续存储。这意味着元素之间不能有空隙,必须紧密排列
    // 使用 len 而不是 cap 来确定插入位置,可以保证顺序表中的元素始终保持连续存储,这是顺序表的核心特征
    if (pos < 0 || pos > seqList->len){
        printf("Insert value out of bounds\n");
        return -1;
    }

    if (seqList->len >= seqList->cap){
        printf("Insertion overflow. Consider enlarging the structure!\n");
        if (enLargerSeq(seqList) == -1){
            printf("Enlarge failed!\n");
            return -1;
        }
    }
    
    for (int i = seqList->len-1; i >= pos; i--){    // 核心移动逻辑
        seqList->data[i+1] = seqList->data[i];
    }

    seqList->data[pos] = val;
    seqList->len++;

    return 0;
}

测试代码:
int main(){
    SeqList *list = createSeqList(5);
    for (int i = 0; i < 5; i++){
        pushBackSeq(list, i);
    }

    showSeqList(list);

    insertSeq(list, 5, 111);      // 任意位置插入
    showSeqList(list);

    insertSeq(list, 0, 222);      // 任意位置插入
    showSeqList(list);

    insertSeq(list, 999, 333);    // 任意位置插入
    showSeqList(list);
    printf("\n");

    headInsertSeq(list, 888);     // 头插法
    showSeqList(list);

    printf("\n");
    pushBackSeq(list, 666);       // 尾插法
    showSeqList(list);

    releaseSeqList(list);
    return 0;
}

测试结果:

3.5 deleteSpeSeq && deleteHeadSeq && deleteTailSeq 函数的实现

函数定义:
int deleteSpeSeq(SeqList *seqList, Element val);   // 删除特定值的元素

int deleteHeadSeq(SeqList *seqList);               // 删除首元素

int deleteTailSeq(SeqList *seqList);               // 删除尾元素

函数功能:

1.deleteSpeSeq 函数

该函数用于在顺序表中删除指定值的元素

2. deleteHeadSeq 函数  

该函数用于删除顺序表中的首元素

 3.deleteTailSeq 函数 

该函数用于删除顺序表中的尾元素

实现思路:

1.deleteSpeSeq 函数

a.判断传入的表是否为空
b.调用 findSeq 函数,判断要查找的值是否在表中
c.如果不在表中,则返回错误标志
d.从 pos+1 的位置开始,从后往前覆盖元素
e.seqList->len--

2. deleteHeadSeq 函数 

a.判断传入的表是否为空
b.判断表内是否有元素
c.从第二个元素(索引为1)开始,从后往前进行覆盖
d.seqList->len--

3.deleteTailSeq 函数 

a.判断传入的表是否为空
b.判断表内是否有元素
d.seqList->len--

具体实现代码:

1.deleteSpeSeq 函数

int deleteSpeSeq(SeqList *seqList, Element val) {
    if (!seqList || !seqList->data){
        printf("Seqlist is null. Delete failed!\n");
        return -1;
    }

    int pos = findSeq(seqList, val);
    if (pos == -1){
        printf("Delete value error!\n");
        return -1;
    }
    
    for (int i = pos + 1; i < seqList->len; i++){
        seqList->data[i - 1] = seqList->data[i];
    }
    /*for (int i = pos; i < seqList->len; i++){ // 第二种写法可能会有溢出的风险
        seqList->data[i] = seqList->data[i + 1];
    }*/

    seqList->len--;

    return 0;
}

2. deleteHeadSeq 函数

int deleteHeadSeq(SeqList *seqList) {
    if (!seqList || !seqList->data){
        printf("Seqlist is null. Delete failed!\n");
        return -1;
    }

    if (seqList->len <= 0){
        printf("Delete head value failed!");
        return -1;
    }

    for (int i = 1; i < seqList->len; i++){
        seqList->data[i-1] = seqList->data[i];
    }

    seqList->len--;
    return 0;
}

3.deleteTailSeq 函数

int deleteTailSeq(SeqList *seqList) {
    if (!seqList || !seqList->data){
        printf("Seqlist is null. Delete failed!\n");
        return -1;
    }

    if (seqList->len <= 0){
        printf("Delete tail value failed!");
        return -1;
    }

    seqList->len--;

    return 0;
}

测试代码:
int main(){
    SeqList *list = createSeqList(5);
    for (int i = 0; i < 5; i++){
        pushBackSeq(list, i);
    }

    deleteHeadSeq(list);
    showSeqList(list);

    deleteTailSeq(list);
    showSeqList(list);

    deleteSpeSeq(list,999);
    showSeqList(list);

    deleteSpeSeq(list,1);
    showSeqList(list);

   
    releaseSeqList(list);
    return 0;
}

测试结果:

上一篇:unity渲染人物模型透明度问题


下一篇:业务需求方面