• 数据结构——顺序表


    目录

    一.简介

    线性表

    顺序表

    二.结构体与初始化

    1.创建

    2.初始化

    三.功能实现

    1.打印

    2.销毁

    3.扩容

    4.尾插

    5.尾删

     6.头插

    7.头删

    8.查找元素

    9.下标位置的插入与某一数据前的插入

    10.下标位置的删除与某一数据的删除

    11.头插、头删、尾插、尾删的常态化


    一.简介

    线性表

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

    线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

    而本篇文章讲的便是顺序表

    顺序表

    顺序表本质上就是一个数组,但与数组不同的是,顺序表在逻辑上是连续的。



    二.结构体与初始化

    1.创建

    在创建好数组之后,我们为了明确在数组中究竟存储了多少个元素,我们还需要有一个变量存储数据个数。如此顺序表内便有了多个值,我们便可以构建结构体。

    1. #define N 5
    2. struct SeqList
    3. {
    4. int a[N];
    5. int size;
    6. }

    可以看到,我们在这里使用的是静态的数组,而我们静态的数组不能随着顺序表的元素个数来合理分配空间,所以我们可以改用动态数组

    同时,在数组中,存储的往往不只是整形,我们需要根据数据类型决定数组的类型,而数组往往会被频繁的使用,因此,为了避免大规模的改动,我们可以使用typedef来重新定义类型名,如此,只需要改动typedef修改的类型即可。同样,我们还可以使用typedef来简化结构体类型

    1. typedef int SeqListDateType;
    2. typedef struct SeqList
    3. {
    4. SeqListDateType* a;
    5. int size;
    6. int capacity;
    7. }SL;

    可以看到,在我们更改为动态数组时,由于不再存在,我们w增加了一个变量capacity表示数组的大小,而size依旧表示动态数组中所存储的数据个数。


    2.初始化

    在创建好结构体后,我们需要对结构体进行初始化。

    由于动态数组中我们使用的是指针,我们需要将其初始化为空指针,而其他的变量初始化为0

    1. void SeqListInit(SL* ps)
    2. {
    3. ps->a = NULL;
    4. ps->size = 0;
    5. ps->capacity = 0;
    6. }


    三.功能实现

    1.打印

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

    1. void SepListPrint(SL* ps)
    2. {
    3. int i;
    4. for (i = 0; i < ps->size; i++)
    5. {
    6. printf("%d ", ps->a[i]);
    7. }
    8. printf("\n");
    9. }

    2.销毁

    在顺序表不再使用时,我们需要将动态数组销毁

    1. void SeqListDestory(SL* ps)
    2. {
    3. free(ps->a);
    4. ps->a = NULL;
    5. }

    3.扩容

    在我们实现插入的功能时,顺序表中存储的数据不断增多,而当数据的个数与数组大小相等时(即size等于capacity)动态数组需要进行扩容。

    在动态数组的扩容中,我们需要使用realloc函数。

    为了避免太多次扩容影响效率,同时为了避免扩容空间过大导致空间浪费,我们选择进行指数型扩容,我们在这里就选择将其扩增到原本的2倍。

    而由于我们一开始将数组的空间初始化为0,所以在第一次扩容时显然不能将其扩增到2倍,我们在这里选择将其扩增为4。

    1. void SepListExpansion(SL* ps)
    2. {
    3. int newcapacity = ps->capacity == 0 ? 4:ps->capacity*2;
    4. SeqListDateType* tmp = (SeqListDateType*)realloc(ps->a, newcapacity*sizeof(SeqListDateType));
    5. if (tmp == NULL)
    6. {
    7. exit(-1);
    8. }
    9. else
    10. {
    11. ps->a = tmp;
    12. ps->capacity = newcapacity;
    13. }
    14. }

    4.尾插

    尾插,即在顺序表中数据的最后添加一个数据,实现这项功能,我们只需要注意扩容即可

    1. void SeqListPushBack(SL* ps, SeqListDateType x)
    2. {
    3. if (ps->size == ps->capacity)
    4. {
    5. SepListExpansion(ps);
    6. }
    7. ps->a[ps->size] = x;
    8. ps->size++;
    9. SepListPrint(ps);
    10. }

    5.尾删

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

    而循序表中数据的个数是有限的,当其中不存在数据时,我们不能进行删除操作

    1. void SeqListPopBack1(SL* ps)
    2. {
    3. assert(ps->size > 0);//报错
    4. ps->size--;
    5. SepListPrint(ps);
    6. }
    1. void SeqListPopBack2(SL* ps)
    2. {
    3. if (ps->size > 0)//条件判断
    4. {
    5. ps->a[ps->size - 1] = 0;
    6. ps->size--;
    7. }
    8. }

    我们在这里写出了两种解决方式,可以根据自己的习惯来选择,在后面的功能实现中,我们统一使用第一种方法。


     6.头插

    头插头删与尾插尾删相比,会比较麻烦,因为它们需要对后面的数据进行进行挪动

     例如这样一个顺序表,想要头插一个数据x

    我们需要自右向左进行挪动,最后在头部放入x

    同样的,也需要注意扩容

    1. void SeqListPushFront(SL* ps, SeqListDateType x)
    2. {
    3. if (ps->size == ps->capacity)
    4. {
    5. SepListExpansion(ps);
    6. }
    7. for (int end = ps->size; end >0 ; end--)
    8. {
    9. ps->a[end] = ps->a[end - 1];
    10. }
    11. ps->a[0] = x;
    12. ps->size++;
    13. SepListPrint(ps);
    14. }

    7.头删

    1. void SeqListPopFront(SL* ps)
    2. {
    3. assert(ps->size > 0);
    4. for (int i = 0; i < ps->size-1; i++)
    5. {
    6. ps->a[i] = ps->a[i + 1];
    7. }
    8. ps->a[ps->size - 1] = 0;
    9. ps->size--;
    10. SepListPrint(ps);
    11. }

    8.查找元素

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

    1. int SeqListFind(SL* ps, SeqListDateType x, int begin)
    2. {
    3. int i = 0;
    4. for (i = begin; i < ps->size; i++)
    5. {
    6. //查找到返回下标i
    7. if (ps->a[i] == x)
    8. {
    9. return i;
    10. }
    11. }
    12. //查找不到返回-1
    13. return -1;
    14. }

    9.下标位置的插入与某一数据前的插入

    下标位置的插入,与头插类似,只是需要将数据挪动的起始位置从下标0改为参数中传入的下标。

    同时还需要注意传入的下标是否合法。

    1. void SeqListInsertPos(SL* ps, int pos, SeqListDateType x)
    2. {
    3. assert(pos >= 0 && pos <= ps->size);
    4. if (ps->size == ps->capacity)
    5. {
    6. SepListExpansion(ps);
    7. }
    8. for (int end = ps->size; end > pos; end--)
    9. {
    10. ps->a[end] = ps->a[end - 1];
    11. }
    12. ps->a[pos] = x;
    13. ps->size++;
    14. SepListPrint(ps);
    15. }

    将下标位置的插入与元素的查找结合,我们便可以实现在某一元素前的插入

    1. void SeqListInsertData(SL* ps, SeqListDateType x, SeqListDateType data)
    2. {
    3. int pos = -2;
    4. while (1)
    5. {
    6. pos = SeqListFind(ps, data, pos+2);
    7. if (pos!=-1)
    8. {
    9. SeqListInsertPos(ps, pos, x);
    10. }
    11. else
    12. {
    13. break;
    14. }
    15. }
    16. }

    例如这样一个顺序表,我们想要在2的前面插入5

    首先我们要查找2的位置,函数SeqListFind的参数begin应该为0,此时的返回值应该为1。

    之后在返回的pos下标位置插入5

     之后,由于我们不确定2的个数,我们需要在前一个2的后面再次查找,由图可知,begin=pos+2

    结合这个表达式,我们可以将上面begin=0写作pos=-2;  begin=pos+2;

     

     

    以此类推 

    当pos=-1时结束。 


    10.下标位置的删除与某一数据的删除

    由于与上面的插入类似,这里就不多做赘述

    1. void SeqListErasePos(SL* ps, int pos)
    2. {
    3. assert(pos >= 0 && pos <= ps->size);
    4. assert(ps->size > 0);
    5. int i = 0;
    6. for (i = pos; i < ps->size-1; i++)
    7. {
    8. ps->a[i] = ps->a[i + 1];
    9. }
    10. ps->size--;
    11. SepListPrint(ps);
    12. }
    1. void SeqListEraseData(SL* ps, SeqListDateType data)
    2. {
    3. int pos = -2;
    4. while (1)
    5. {
    6. pos = SeqListFind(ps, data, pos + 2);
    7. if (pos !=-1)
    8. {
    9. SeqListErasePos(ps,pos);
    10. }
    11. else
    12. {
    13. break;
    14. }
    15. }
    16. }

    11.头插、头删、尾插、尾删的常态化

    我们可以想到,头插、尾插是下标位置插入的特殊化,头删、尾删是下标位置删除的特殊化。

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

    1. //尾插
    2. void SeqListPushBack(SL* ps, SLDataType x)
    3. {
    4. SeqListInsertPos(ps, ps->size, x);
    5. }
    6. //尾删
    7. void SeqListPopBack(SL* ps)
    8. {
    9. SeqListErasePos(ps, ps->size-1);
    10. }
    11. //头插
    12. void SeqListPushFront(SL* ps, SLDataType x)
    13. {
    14. SeqListInsertPos(ps, 0, x);
    15. }
    16. //头删
    17. void SeqListPopFront(SL* ps)
    18. {
    19. SeqListErasePos(ps, 0);
    20. }

    end

  • 相关阅读:
    图卷积神经网络GCN的一些理解以及DGL代码实例的一些讲解
    基于springboot+vue的戒毒所人员管理系统毕业设计源码251514
    AIE分子四(4,4′,4″,4′″-烯丙氧基)四苯基乙烯/萘酰亚胺及吡啶偶联三苯胺聚集诱导发光型分子探针的研究
    QT事件系统_lineEdit输入信息触发事件方法
    旭日图更好地呈现数据的层次结构,细致划分各项数据
    【AI落地应用实战】如何高效检索与阅读论文——302.AI学术论文工具评测
    【Leetcode】python回溯算法结果出现[[], [], []](列表深拷贝 浅拷贝问题)
    java 实现原型模式
    3种模型之间的关系,对象模型定义了做事情的实体。
    浅谈前端性能优化手段
  • 原文地址:https://blog.csdn.net/finish_speech/article/details/127941772