0


【极简数据结构】快速了解并实现顺序表,速通玩家的最爱

顺序表目录

前言

从0快速实现顺序表。


一、线性表

① 线性表(Linear list)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表有:顺序表、链表、栈、队列、字符串…
② 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物 理上存储时,通常以数组和链式结构的形式存储。

在这里插入图片描述


二、顺序表

1.顺序表的概念

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

💡顺序表顾名思义,数据是按着顺序连续存储的。
顺序表分为

  1. 静态顺序表

💬 使用定长数组存储元素:

#defineN7typedefint SLDataType;typedefstructSeqList{
    SLDataType array[N];//定长数组size_t size;//有效数据的个数} SL;

在这里插入图片描述

📚静态顺序表的特点:如果满了就不让插入
🆘静态顺序表的缺点:设置的长度是多少呢?不能准确确定。N给小了不够用;N给大了就显得浪费

  1. 动态顺序表

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

typedefint SLDataType;typedefstructSeqList{
    SLDataType* array;//指向动态开辟的数组size_t size;//有效数据的个数size_t capacity;//容量空间的大小} SL;

在这里插入图片描述

动态顺序表可根据需要动态地分配空间,因此在现实中广泛使用。


2.接口函数

接口函数: 某个模块写了(主要)给其它模块用的函数。
📌注意:
① 接口函数的命名风格跟着STL走,便于后期学习STL
② 模块化思想:SeqList.h 头文件存放 结构体、函数的声明; SeqList.c 源文件存放 函数的定义;Test.c 源文件 用于测试。

💬主要的接口函数有:
接口函数采用址传递方式

📁SeqList.h
#pragmaonce#include<stdio.h>#include<stdlib.h>#include<assert.h>//动态顺序表typedefint SLDataType;typedefstructSeqList{
    SLDataType* array;//指向动态开辟的数组size_t size;//有效数据的个数size_t capacity;//容量空间的大小} SL;/*接口函数*///顺序表 初始化voidSeqListInit(SL* psl);//顺序表 尾插voidSeqListPushBack(SL* psl, SLDateType x);//顺序表 打印voidSeqListPrint(SL* psl);//顺序表 销毁voidSeqListDestory(SL* psl);//顺序表 尾删voidSeqListPopBack(SL* psl);//顺序表 头插voidSeqListPushFront(SL* psl,SLDateType x);//顺序表 判断扩容voidSeqListCheckCapacity(SL* psl);//顺序表 头删voidSeqListPopFront(SL* psl);//顺序表 查找intSeqListFind(SL* psl,SLDateType x);//顺序表 任意pos位置插入intSeqListInsert(SL* psl,int pos, SLDateType x);//顺序表 任意pos位置删除voidSeqListEarse(SL* psl,int pos);

🔑解读:

#pragma once

为了避免同一个头文件被多次包含。

顺序表 初始化

📁SeqList.c
//顺序表 初始化voidSeqListInit(SL* psl){
    psl->array =NULL;
    psl->size = psl->capacity =0;}

🔑解读:

顺序表 尾插

💡尾插要考虑三种情况:

  1. 整个顺序表没有空间
  2. 空间已满,则扩容
  3. 空间是足够的,直接插入数据即可

🔱总结: 尾插需要考虑是否需要扩容

📁SeqList.c
//顺序表 尾插voidSeqListPushBack(SL* psl, SLDataType x){//判断是否需要扩容if(psl->size == psl->capacity){int newcapacity = psl->capacity ==0?4: psl->capacity*2;//realloc:若原空间为空,就等于malloc。调整为newcapacity个SLDataType大小的空间
        SLDataType* tmp =(SLDataType*)realloc(psl->array, new_capacity *sizeof(SLDataType));//防止realloc翻车,也可以用assert(tmp);断言if(tmp ==NULL){printf("realloc failed!\n");exit(-1);}//更新
        psl->array = tmp;
        psl->capacity = newcapacity;}//插入
    psl->array[psl->size]= x;
    psl->size++;}

🔑解读:
① 扩容时,扩大为原来capacity的2倍(2倍是比较合理、折中的倍数,你也可以选择其他的倍数)
② 为了避免realloc失败返回NULL导致后续会访问空,我们创建了临时指针tmp接收,并对其判断。

顺序表 打印

📁SeqList.c
//顺序表 打印voidSeqListPrint(SL* psl){int i =0;for(i =0; i < psl->size; i++){printf("%d ", psl->array[i]);}printf("\n");}

🔑解读: for循环遍历顺序表,并逐一打印。
📌温馨提示:编写代码要养成写一段测试一段的习惯。切勿等写完再来测试
测试代码:

📁Test.c
#include"SeqList.h"voidTestSeqList1(){
    SL sl;SeqListInit(&sl);SeqListPushBack(&sl,1);SeqListPushBack(&sl,2);SeqListPushBack(&sl,3);SeqListPushBack(&sl,4);SeqListPushBack(&sl,5);SeqListPrint(&SL);}intmain(){TestSeqList1();return0;}

在这里插入图片描述
🔑解读:

TestSeqList1();

模块化测试思想
具体测试方法:【实用调试技巧】,在此不再叙述。一定要多尝试自己测试调试代码。(下文就不再带大家测试了)

顺序表 销毁

📌注意: 数组是动态开辟的,对于动态开辟的内存空间,我们要养成手动free掉的习惯,否则会有内存泄露的风险。

📁SeqList.c
//顺序表 销毁voidSeqListDestory(SL* psl){free(psl->array);
    psl->array =NULL;
    psl->capacity = psl->size =0;}

🔑解读: free掉

psl->array

指向的内存空间后,要

psl->array

置空,以防止其变成野指针。

顺序表 尾删

① 尾删就是让

psl->size--;

(让有效数据减1即可,因为psl->size作为数组下标时是下一个插入数据的位置)
在这里插入图片描述

② 尾删要考虑顺序表是否为空的情况,当顺序表都空了还删除会有越界非法访问的安全问题。即尾删要考虑判空。

📁SeqList.c
//顺序表 尾删voidSeqListPopBack(SL* psl){//1.温柔手法/*    if (psl->size > 0)
    {
        psl->size--;
    }*///2.暴力手法assert(psl->size >0);
    psl->size--;}

🔑解读: 尾插要考虑判空,判空建议用断言方式。

顺序表 头插 和 顺序表 扩容

📁SeqList.c
//顺序表 扩容voidSeqListCheckCapacity(SL* psl){//判断是否需要扩容if(psl->size == psl->capacity){int newcapacity = psl->capacity ==0?4: psl->capacity*2;//realloc:若原空间为空,就等于malloc。调整为newcapacity个SLDataType大小的空间
        SLDataType* tmp =(SLDataType*)realloc(psl->array, new_capacity *sizeof(SLDataType));//防止realloc翻车,也可以用assert(tmp);断言if(tmp ==NULL){printf("realloc failed!\n");exit(-1);}//更新
        psl->array = tmp;
        psl->capacity = newcapacity;}}//顺序表 头插voidSeqListPushFront(SL* psl, SLDateType x){SeqListCheckCapacity(psl);//复用int end = psl->size -1;//psl->size作为数组下标时是下一个插入数据的位置,所以要减1while(end >=0){
        psl->array[end +1]= psl->array[end];
        end--;}//此时,array[0]被挪到后一位
    psl->array[0]= x;
    psl->size++;}

🔑解读:

① 插入要考虑是否需要扩容,头插和尾插都需要扩容,我们可以将扩容操作封装为一个SeqListCheckCapacity函数,再在头插和尾插中复用。
② 顺序表的数据是从头开始连续存储的。所以顺序表实现头插,需要将所有元素往后挪动1位。

int end = psl->size - 1;

按psl->array[end]、psl->array[end - 1]…的顺序依次往后挪1位

⚡优化顺序表 尾删

在尾插中复用扩容

📁SeqList.c
//顺序表 尾插voidSeqListPushBack(SL* psl, SLDateType x){SeqListCheckCapacity(psl);//复用扩容函数
    psl->array[psl->size]= x;
    psl->size++;}

优化后可读性提高!

顺序表 头删

📁SeqList.c
//顺序表 头删voidSeqListPopFront(SL* psl){assert(psl->size >0);int begin =1;while(begin < psl->size){
        psl->array[begin -1]= psl->array[begin];
        begin++;}
    psl->size--;}

🔑解读:
① 删除需要考虑判空

int begin = 1;

按psl->array[1]、psl->array[2]…的顺序依次往前挪1位,最后

psl->size--;

顺序表 查找

📁SeqList.c
//顺序表 查找intSeqListFind(SL* psl,SLDateType x){//数组下标从0开始int i =0;for(i =0; i < psl->size; i++){if(psl->array[i]== x){return i;}}//没找到return-1;}

🔑解读:
比较简单,for循环依次遍历顺序表 若

psl->array[i] == x

,则返回下标

i

;没找到就返回

-1


时间复杂度为

O(N)

。可以尝试写一个二分查找算法的接口函数实现,但前提要排序。

顺序表 任意pos位置插入

前面学习了顺序表的头插和尾插,我们能不能在任意的pos位置插入呢?这得考虑考虑!

在这里插入图片描述
因此我们要对pos进行条件限定。另外,插入就得考虑是否需要扩容。

📁SeqList.c
//顺序表 任意pos位置插入intSeqListInsert(SL* psl,int pos, SLDateType x){//插入的pos位置有区域约束//1.温柔方法/*    if (pos < 0 && pos > psl->size)
    {
        printf("pos invalidd(pos非法!)\n");
        return;
    }*///2.暴力方法assert(pos >=0&& pos <= psl->size);//凡是插入,考虑是否需要扩容SeqListCheckCapacity(psl);//挪数据int end = psl->size -1;while(end >= pos){
        psl->array[end +1]= psl->array[end];
        end--;}//正式插入
    psl->array[pos]= x;
    psl->size++;}

🔑解读:
思路类似头插,只是while循环的终止条件发生变化。

🔱对头插函数和尾插函数的优化

SeqListInsert函数可以在任意pos位置插入数据,当然包括了头部和尾部。因此我们可以在头插和尾插函数中复用SeqListInsert函数。真是妙极了!

头插函数复用SeqListInsert函数

//顺序表 头插(复用SeqListInsert函数)voidSeqListPushFront(SL* psl, SLDataType x){SeqListInsert(psl,0, x);}

尾插函数复用SeqListInsert函数

//顺序表 尾插(复用SeqListInsert函数voidSeqListPushBack(SL* psl, SLDataType x){SeqListInsert(psl, psl->size, x);}

顺序表 任意pos位置删除

① 类似任意pos位置插入,我们也可以在pos位置删除。同样pos位置是有条件限定的,特别的是注意psl->size无有效数据,不可删除。

② 删除就得考虑判空

//顺序表 任意pos位置删除voidSeqListEarse(SL* psl,int pos){//删除和插入一样:有区域约束,不可任意访问删除//1.温柔方法/*    if (pos < 0 && pos >= 0)
    {
        printf("pos invalidd!\n");
        return;
    }*///2.暴力处理assert(pos >=0&& pos < psl->size);int begin = pos;while(begin < psl->size){
        psl->array[begin]= psl->array[begin +1];
        begin++;}
    psl->size--;}

🔑解读:
思路类似头删。

🔱对头删函数和尾删函数的优化

同样的道理,头删和尾删也可以复用SeqListEarse函数
头删函数复用SeqListEarse函数

//顺序表 头删(复用SeqListEarse函数)voidSeqListPopFront(SL* psl){SeqListEarse(psl,0);}

尾删函数复用SeqListEarse函数

//顺序表 尾删(复用SeqListEarse函数)voidSeqListPopBack(SL* psl){SeqListEarse(psl, psl->size -1);}

到此,想必速通万家们已经知道如何快速实现顺序表了!
我们把SeqListInsert函数和SeqListEarse函数写了,就相当于把头插尾插、头删尾删写了(复用即可)


四、完整代码

📁SeqList.h

#pragmaonce#include<stdio.h>#include<stdlib.h>#include<assert.h>创建静态顺序表//#define N 10//typedef int SLDateType;////typedef struct SeqList//{//    SLDateType array[N];//定长数组//    SLDateType size;//有效个数//}SL;//创建动态顺序表typedefint SLDateType;typedefstructSeqList{
    SLDateType* array;//指向动态开辟的数组
    SLDateType size;//有效个数
    SLDateType capacity;//容量空间大小}SL;/*接口函数*///顺序表 初始化voidSeqListInit(SL* psl);//顺序表 尾插voidSeqListPushBack(SL* psl, SLDateType x);//顺序表 打印voidSeqListPrint(SL* psl);//顺序表 销毁voidSeqListDestory(SL* psl);//顺序表 尾删voidSeqListPopBack(SL* psl);//顺序表 头插voidSeqListPushFront(SL* psl,SLDateType x);//顺序表 判断扩容voidSeqListCheckCapacity(SL* psl);//顺序表 头删voidSeqListPopFront(SL* psl);//顺序表 查找intSeqListFind(SL* psl,SLDateType x);//顺序表 任意pos位置插入intSeqListInsert(SL* psl,int pos, SLDateType x);//顺序表 任意pos位置删除voidSeqListEarse(SL* psl,int pos);

📁SeqList.c

#include"SeqList.h"//顺序表 初始化voidSeqListInit(SL* psl){
    psl->array =NULL;
    psl->size = psl->capacity =0;}//顺序表 尾插voidSeqListPushBack(SL* psl, SLDateType x){//插入SeqListCheckCapacity(psl);
    psl->array[psl->size]= x;
    psl->size++;}//顺序表 打印voidSeqListPrint(SL* psl){int i =0;for(i =0; i < psl->size; i++){printf("%d ", psl->array[i]);}printf("\n");}//顺序表 销毁voidSeqListDestory(SL* psl){free(psl->array);
    psl->array =NULL;
    psl->capacity = psl->size =0;}//顺序表 尾删voidSeqListPopBack(SL* psl){1.温柔手法//if (psl->size > 0)//{//    psl->size--;//}//没有考虑到"删过头"的情况,会非法访问psl->array[psl->size - 1] = 0;//psl->size--;//2.暴力手法//assert(psl->size > 0);//psl->size--;//复用SeqListEarseSeqListEarse(psl, psl->size -1);}//顺序表 头插voidSeqListPushFront(SL* psl, SLDateType x){SeqListCheckCapacity(psl);int end = psl->size -1;while(end >=0){
        psl->array[end +1]= psl->array[end];
        end--;}//此时,array[0]被挪到后一位
    psl->array[0]= x;
    psl->size++;}//顺序表 判断扩容voidSeqListCheckCapacity(SL* psl){if(psl->size == psl->capacity)//判断空间是否足够{//扩容
        SLDateType newcapacity = psl->capacity ==0?4: psl->capacity *2;
        SLDateType* tmp =(SLDateType*)realloc(psl->array, newcapacity *sizeof(SLDateType));;if(tmp ==NULL){printf("扩容失败!\n");exit(-1);}//扩容后需要更新的
        psl->array = tmp;
        psl->capacity = newcapacity;}}//顺序表 头删voidSeqListPopFront(SL* psl){//assert(psl->size > 0);//int begin = 1;//while (begin<psl->size)//{//    psl->array[begin - 1] = psl->array[begin];//    begin++;//}//psl->size--;//复用SeqListEarseSeqListEarse(psl,0);}//顺序表 查找intSeqListFind(SL* psl,SLDateType x){//数组下标从0开始int i =0;for(i =0; i < psl->size; i++){if(psl->array[i]== x){return i;}}//没找到return-1;}//顺序表 任意pos位置插入intSeqListInsert(SL* psl,int pos, SLDateType x){//插入有区域约束//温柔处理:/*    if (pos < 0 && pos > psl->size)
    {
        printf("pos invalidd(pos非法!)\n");
        return;
    }*///暴力解决assert(pos >=0&& pos <= psl->size);//凡是插入,考虑是否需要扩容SeqListCheckCapacity(psl);//挪数据int end = psl->size -1;while(end>=pos){
        psl->array[end +1]= psl->array[end];
        end--;}//正式插入
    psl->array[pos]= x;
    psl->size++;}//顺序表 任意pos位置删除voidSeqListEarse(SL* psl,int pos){//删除和插入一样:有区域约束,不可任意访问删除//温柔处理/*    if (pos < 0 && pos >= 0)
    {
        printf("pos invalidd(pos非法!)\n");
        return;
    }*///暴力处理assert(pos >=0&& pos < psl->size);int begin = pos;while(begin < psl->size){
        psl->array[begin]= psl->array[begin +1];
        begin++;}

    psl->size--;}

📁Test.c

#include"SeqList.h"voidTestSeqList1(){
    SL sl;SeqListInit(&sl);SeqListPushBack(&sl,1);SeqListPushBack(&sl,2);SeqListPushBack(&sl,3);SeqListPushBack(&sl,4);SeqListPushBack(&sl,5);SeqListPrint(&sl);//SeqListPopBack(&sl);//SeqListPopBack(&sl);//SeqListPopBack(&sl);//SeqListPopBack(&sl);//SeqListPopBack(&sl);//SeqListPopBack(&sl);//SeqListPrint(&sl);//SeqListPushBack(&sl, 10);//SeqListPushBack(&sl, 20);//SeqListPrint(&sl);//SeqListPushFront(&sl, 1);//SeqListPushFront(&sl, 2);//SeqListPushFront(&sl, 3);//SeqListPushFront(&sl, 4);//SeqListPrint(&sl);//SeqListDestory(&sl);}voidTestSeqList2(){
    SL sl;SeqListInit(&sl);SeqListPushBack(&sl,1);SeqListPushBack(&sl,2);SeqListPushBack(&sl,3);SeqListPushBack(&sl,4);SeqListPrint(&sl);SeqListPushFront(&sl,1);SeqListPushFront(&sl,2);SeqListPushFront(&sl,3);SeqListPushFront(&sl,4);SeqListPrint(&sl);//SeqListDestory(&sl);}voidTestSeqList3(){
    SL sl;SeqListInit(&sl);SeqListPushBack(&sl,1);SeqListPushBack(&sl,2);SeqListPushBack(&sl,3);SeqListPushBack(&sl,4);SeqListPrint(&sl);//int ret = SeqListFind(&sl, 3);//if (ret == -1)//    printf("找不到!\n");//else//    printf("找到了,下标为%d", ret);SeqListInsert(&sl,5,5);SeqListPrint(&sl);SeqListDestory(&sl);}voidTestSeqList4(){
    SL sl;SeqListInit(&sl);SeqListPushBack(&sl,1);SeqListPushBack(&sl,2);SeqListPushBack(&sl,3);SeqListPushBack(&sl,4);SeqListPushFront(&sl,1);SeqListPushFront(&sl,2);SeqListPushFront(&sl,3);SeqListPushFront(&sl,4);SeqListPrint(&sl);SeqListPopFront(&sl);SeqListPopBack(&sl);SeqListPrint(&sl);SeqListDestory(&sl);}intmain(){//TestSeqList1();//TestSeqList2();//TestSeqList3();TestSeqList4();}

在这里插入图片描述
简单的三连是对作者的最大支持!
请添加图片描述

✒️ 笔者:陈汉新

📅 更新: 2022.4.13

❌ 勘误: 暂无

📣 声明:由于作者水平有限,本文错误之处在所难免,敬请读者指正!


本文转载自: https://blog.csdn.net/weixin_50614301/article/details/124139200
版权归原作者 大猩猩! 所有, 如有侵权,请联系我们删除。

“【极简数据结构】快速了解并实现顺序表,速通玩家的最爱”的评论:

还没有评论