数据结构之动态顺序表(含游戏菜单)

简介: 我们可以知道,静态顺序表的缺点是: 因为使用的是定长数组,所以空间给少了不够用,给多了,就会造成空间的浪费. 由此,我们引入一个新的顺序表----动态顺序表.

    一.什么是动态顺序表?

动态顺序表:使用动态开辟的数组存储,使用指针指向动态开辟的数组,可以进行扩容。可以队数组内容进行增删查改等操作。


20210709111035449 (1).png


       这里顺便提一下什么是逻辑结构和物理结构

逻辑结构: 人为想象出来的,实际并不存在.

物理结构: 实际存在,可以被观察到

二.动态顺序表的优缺点

优点:空间连续,支持随机访问。


缺点:1.中间或前面部分的插入时间复杂度是O(N),


           2.增容的代价比较大    

三.创建项目


工程文件 存放的内容
SeqList.h 函数声明,宏定义
SeqList.c 实现顺序表函数接口的定义
test.c 测试函数接口是否能实现功能

 四.接口实现

       1.定义顺序表

       为了后续我们想更改数据类型时方便,我们一般对类型进行typedef操作。


typedef int SeqlistType;
//创建结构体
typedef struct Seqlist
{
  SeqlistType* arr; //指向动态开辟的数组
  SeqlistType size; //有效个数
  SeqlistType capacity;//容量
}SL;

20210709111035449.png

    2.结构体初始化

传值:因为形参是实参的一份临时拷贝,对形参的改变并不会改变实参。所以我们应该把结构体变量的地址传过去,用结构体指针接收!为了防止传过来的是空指针,我们可以用assert进行断言!


//

void SeqlistInit(SL* ps)
{
  assert(ps);
  ps->arr = NULL;
  ps->size = 0;
  ps->capacity = 0;
}

3.打印数据

我们只需要遍历顺序表就能完成打印

void SeqlistPrint(SL* ps)
{
  assert(ps);
  int i = 0;
  for (i = 0; i < ps->size; i++)
  {
    printf("%d ", ps->arr[i]);
  }
  printf("\n");
}


/

4.顺序表增容

       我们插入数据时,应该要先检查顺序表的容量是否满了(ps->size == ps->capacity)


因为一开始我们时没有给容量的(capacity = 0),所以我们可以先给4个空间,后续不够空间了再两倍的扩容!

//检查容量
void SeqlistCheckCapacity(SL* ps)
{
  assert(ps);
  //当size == capacity时要扩容了
  int newcapacity = 0;
  if (ps->size == ps->capacity)
  {
    newcapacity = (ps->capacity == 0 ? 4 : ps->capacity * 2);
    //如果一开始容量为0,就给4个空间
    //不为0就扩为原来的2倍
    //扩容还要重新开辟空间->realloc
    SeqlistType* tmp = 
      (SeqlistType*)realloc(ps->arr, sizeof(SeqlistType) * newcapacity);
    //注意:要判断是否开辟成功
    if (tmp == NULL)
    {
      printf("扩容失败\n");
      //exit(-1);
      return;
    }
    else
    {
      ps->arr = tmp;
      ps->capacity = newcapacity;
    }
  }
}


/

5.头插数据

       头插数据:1.要先检查顺序表容量,不够则扩容


                          2.头插即为把数据放到第一个位置,那我们把数组原来的数据向后移动即可!要注意从最右边的开始移动(i = ps->size-1),否则前面的数据会把后面的数据覆盖!


                          3.插入数据后,标记有效数字的size+1;

//头插
void SeqlistPushFront(SL* ps, SeqlistType x)
{
  assert(ps);
  //注意先检查容量!!!!
  SeqlistCheckCapacity(ps);
  int i = 0;
  for (i = ps->size-1; i >=0; i--)
  {
    ps->arr[i+1] = ps->arr[i];
  }
  ps->arr[0] = x;
  ps->size++;
}

   


/

    6.尾插数据

       尾插数据:1.尾插数据也要先检查容量


                         2.在数组的位置上插入数据即:ps->size位置


                          3.插入数据,szie++;


//尾插
void SeqlistPushBack(SL* ps, SeqlistType x)
{
  assert(ps);
  //注意先检查容量!!!!
  SeqlistCheckCapacity(ps);
  ps->arr[ps->size] = x;
  ps->size++;
}


      7.头删数据

 头删数据:1.把后面的数据往前面覆盖,要注意从左边开始(i=0),否则会造成数据丢失!


                    2.删除了数据,size--


void SeqlistPopFront(SL* ps)
{
  assert(ps);
  int i = 0;
  for (i = 0; i < ps->size-1; i++)
  {
    ps->arr[i] = ps->arr[i+1];
  }
  ps->size--;
}


      8.尾删数据

      只需让有效数字-1即可,数据虽然还在数组里,但是我们以及访问不到了!

void SeqlistPopBack(SL* ps)
{
  assert(ps);
  ps->size--;
}


9.查找数据、

1查找元素是否在顺序表内,如果元素在数组中的下标.


2.如果不在,返回-1  (-1为无效坐标)


3.返回类型为int  若返回类型写成我们之前typedef的类型要注意typedef的类型是什么!

int SeqlistFind(SL* ps, SeqlistType x)
{
  assert(ps);
  //遍历查找
  for (int i = 0; i < ps->size; i++)
  {
    if (ps->arr[i] == x)
      return i;
  }
  return -1;  //-1为无效坐标
}


10.某一位置插入数据

    函数接口实现内容:给一个下标,在该下标前位置插入数据.

1.要保证位置在数组有效范围内(ps->size-1范围内)

      2.把要插入位置的数据及后面的数据向后移动,注意要从最后的数据开始移动(i=ps->size-1),否则会造成前面的数据覆盖后面的数据。

       3.最后把数据插入该位置,size++

//插入
void SeqlistInsert (SL* ps, SeqlistType pos, SeqlistType x)
{
  assert(ps);
  //要保证在有效区域内插入
  assert(pos < ps->size);
  //把pos位置及其后面的数据后移  
  // 要从最后面开始移动
  //要保证容量
  SeqlistCheckCapacity(ps);
  for (int i = ps->size-1; i >=pos;i--)
  {
    ps->arr[i+1] = ps->arr[i];
  }
  ps->arr[pos] = x;
  ps->size++;
}


11.删除某一位置

    函数接口实现内容:给一个下标,删除该位置。

    1.要保证要删除的位置的数组在数据范围内((ps->size-1范围内))

     2.把该位置后面的数据向前移动,把要删除位置的数据覆盖即可。

     3.删除掉元素->size--

//删除
void SeqlistErase(SL* ps, SeqlistType pos)
{
  assert(ps);
    assert(pos<ps->size);
  //把pos后面的东西前移动
  for (int i = pos + 1; i < ps->size; i++)
  {
    ps->arr[i-1] = ps->arr[i];
  }
  ps->size--;
}


      12.修改某一位置数据

      函数接口实现内容:给一个下标,修改该下标对应的值

              要保证该下标在数组的数据范围内(ps->size-1范围)

//更改数据
void SeqlistModify(SL* ps, SeqlistType pos, SeqlistType x)
{
  //要保证在有效数据范围内更改
  assert(pos < ps->size);
  ps->arr[pos] = x;
}


13.顺序表的元素个数

              size标识的就是顺序表的有效数字个数

 
         


/

14.销毁顺序表

释放掉动态开辟的数组即可,free和指针置空要同时使用!不然就可能造成野指针!

 
         


  精华总结:

              1. 有些童鞋理解不了size和数组下标的关系!下面请看图


20210813170637613.png


size是有效数字的个数,即数组有多少个元素。数组的下标是从0开始的,所以ps->arr[ps->size]是数组最后一个元素后面的位置。数组最后一个元素下标为ps->arr[ps->size-1]


       其他注意事项:


              !搞清楚传值还是传址问题!


传值:形参是实参的一份临时拷贝,对形参的改变并不会改变实参

       1.插入数据时要检查容量!

       2.在某个下标,修改/删/插入数据,要保证该下标在数组下标的范围内(ps->size-1)

      3.插入/删除数据后,size也要记得++ /-- !

       4.头插头删以及在某个下标位置前插入,要搞清楚从哪边元素开始移动!

当我们将全部接口实现时,我们就可以设计成一个游戏菜单了!


效果展示:

20210813171945237.png


具体游戏代码以及本文章中的所有代码都在附录!


    如果感觉本篇文章对你有所帮助,请给笔者一个小小的点赞,评论和关注吧!你们的支持就是我的动力!


相关文章
|
2月前
|
存储 编译器 C语言
数据结构-顺序表详解(看这篇就足够了,哈哈哈)
数据结构-顺序表详解(看这篇就足够了,哈哈哈)
59 2
|
1月前
|
存储 算法 安全
2024重生之回溯数据结构与算法系列学习之顺序表【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
顺序表的定义和基本操作之插入;删除;按值查找;按位查找等具体详解步骤以及举例说明
|
1月前
|
存储 C语言
【数据结构】顺序表(c语言实现)(附源码)
本文介绍了线性表和顺序表的基本概念及其实现。线性表是一种有限序列,常见的线性表有顺序表、链表、栈、队列等。顺序表是一种基于连续内存地址存储数据的数据结构,其底层逻辑是数组。文章详细讲解了静态顺序表和动态顺序表的区别,并重点介绍了动态顺序表的实现,包括初始化、销毁、打印、增删查改等操作。最后,文章总结了顺序表的时间复杂度和局限性,并预告了后续关于链表的内容。
74 3
|
1月前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之顺序表习题精讲【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
顺序表的定义和基本操作之插入;删除;按值查找;按位查找习题精讲等具体详解步骤以及举例说明
|
2月前
|
存储 Java
数据结构第二篇【关于java线性表(顺序表)的基本操作】
数据结构第二篇【关于java线性表(顺序表)的基本操作】
40 6
|
2月前
|
存储 安全 Java
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
27 3
|
2月前
|
存储 C语言
探索C语言数据结构:利用顺序表完成通讯录的实现
本文介绍了如何使用C语言中的顺序表数据结构实现一个简单的通讯录,包括初始化、添加、删除、查找和保存联系人信息的操作,以及自定义结构体用于存储联系人详细信息。
33 2
|
2月前
|
存储
【数据结构】线性表和顺序表
【数据结构】线性表和顺序表
25 1
|
2月前
|
存储 算法 索引
【数据结构】——顺序表
【数据结构】——顺序表
|
2月前
|
存储
数据结构1——顺序表
数据结构1——顺序表
20 1