【数据结构】详解顺序表

简介: 【数据结构】详解顺序表

引言

经过一段时间的学习,博主也是学到了数据结构和算法这块,那么在接下来的时间里,我也将继续分享我在数据结构这块的学习心得和重点内容。那么第一个我将分享的是动态顺序表的实现,这一块内容将对大家c语言动态内存管理有一定的要求,之前博主也有介绍,如有问题还请前往: 【c语言】详解动态内存管理

一、顺序表的介绍

当谈及顺序表结构式时,我们便会引入线性表的概念,如下:

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

我们今天介绍的顺序表就是以类似数组结构的形式存储的

二、顺序表的两种分类

  1. 静态顺序表
    一般使用定长数组存储数据,如下这般申请静态顺序表:
//静态顺序表
typedef int SLDataType;
#define N 10
typedef struct SeqList
{
    SLDataType arr[N];//定长数组
    int size;//有效数据个数
}SList;

静态顺序表缺陷:空间给少了不够用,给多了造成空间浪费。


  1. 动态顺序表
    为了解决静态顺序表的问题,便有了动态顺序表,一般如下定义:
//动态顺序表--按需申请空间
typedef int SLDataType;
typedef struct SeqList
{
    SLDataType* head;
    int size;//有效数据个数
    int capacity;//空间容量
}SL;

在初次动态申请内存空间时,我们便可用capacity记录空间容量,size记录已存的有效数字个数,用realloc()函数来动态开辟内存,用head来指向动态开辟的空间的起始地址,这样便可通过下表来访问顺序表元素。例如,我们想访问第三个元素:head[2]。

三、动态顺序表的一些常用接口

我们定义如下结构体,表示动态顺序表:

typedef int SLDataType;
// 动态顺序表 -- 按需申请
typedef struct SeqList
{
 SLDataType* a;
 int size; // 有效数据个数
 int capacity; // 空间容量
}SL;

下面各接口函数都是基于此动态顺序表。

3.1空间容量检测

每当我们插入数据时都要检测空间剩余容量,即ps->capacity == ps->size与否,不足便要增容,如果每次写插入数据函数时都写这一段代码那就太麻烦了,所以我们封装一个这样的函数。

在此函数中要先判读ps->capacity大小,如果为0,便要赋值为4;有值时,便翻倍。

还要一点要注意的是,realloc()开辟新空间时,如果原空间后面内存不够,便会释放此空间,在其他地方重新开辟,并拷贝原始数据,所以在这我们重新定义一个指针new_p。

//空间检测
void SLcapacity_check(SL* ps)
{
  assert(ps);
  if (ps->capacity == ps->size)
  {
      //判断新开辟的空间容量mewcapacity
    int newcapacity = (ps->capacity == 0) ? 4 : (ps->capacity) * 2;
    SLDataType* new_p = (SLDataType*)realloc(ps->p, sizeof(SLDataType) * newcapacity);
    //检测开辟成功与否
    if (new_p == NULL)
    {
      printf("realloc fail\n");
      exit(-1);
    }
    //赋值新空间地址,空间容量
    ps->p = new_p;
    ps->capacity = newcapacity;
  }
}

3.2尾插数据

在检测空间后,便可直接插入数据,相对简单就不多介绍了。

//尾插
void SLPushBack(SL* ps, SLDataType x)
{
  assert(ps);
  SLcapacity_check(ps);
  ps->p[ps->size] = x;
  ps->size++;
}

3.3头插数据

在检测剩余空间后,要实现头插,便要将原始数据都后移一位,需要注意的是,要从尾节点开始后移,然后再插到下标为0的位置。

//头插
void SLPushFront(SL* ps, SLDataType x)
{
  assert(ps);
  SLcapacity_check(ps);
  //原数据后移
  for (int i = ps->size; i > 0; i--)
    ps->p[i] = ps->p[i - 1];
  ps->p[0] = x;
  ps->size++;
}

3.4尾删数据

事实上尾删数据后,我们的有效数据个数就会减少一个,那么直接ps->size--,便可在逻辑上实现尾删。

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

3.5头删数据

对于头删数据,其实和头插极其相似。头插是从后向前拷贝数据,头删则是从前向后拷贝数据,即将第i个拷贝到第i-1个的位置,最后再将ps->size--。如果有效数据已经为0了,那么在函数内部不会进行任何操作。

//头删
void SLPopFront(SL* ps)
{
  assert(ps);
  if (ps->size > 0)
  {
    for (int i = 0; i < ps->size - 1; i++)
    {
      ps->p[i] = ps->p[i + 1];
    }
    ps->p[ps->size - 1] = 0;
    ps->size--;
  }
}

3.6寻找某个数

通过遍历顺序表,便可实现此函数,另外只需要注意一点:找到返回下标,未找到返回-1,返回值类型为int

此函数主要为下面要介绍的两个函数服务。

//顺序表查找--返回下标
int SListFind(SL* ps, SLDateType x)
{
  for (int i = 0; i < ps->size; ++i)
  {
    if (ps->a[i] == x)
    {
      return i;
    }
  }
  return -1;
}

3.7指定位置插入数据

在执行插入操作前就要判断,给定的位置是否超过有效数据个数。插入部分代码就是与头插类似的版本,只不过SListPushFront()函数是从下标ps->size-10的数据向后拷贝;而SListInsert()函数是从ps->size-1pos的数据向后拷贝。

// 顺序表在pos位置插入x
void SListInsert(SL* ps, size_t pos, SLDateType x)
{
  assert(ps);
  assert(pos <= ps->size);//指定位置不能超过有效数据个数
  SLcapacity_check(ps);
    //此操作与头插相似,拷贝数据,再插入
  int end = ps->size ;
  while (end > pos)
  {
    ps->a[end] = ps->a[end - 1];
    --end;
  }
  ps->a[pos] = x;
  ps->size++;
}

3.8指定位置删除数据

与头删类似,从下标为pos+1的位置向前拷贝,最后ps->size--,其他就不多解释了。

// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, int pos)
{
  assert(ps && pos < ps->size);
    //与头删相似,从下标为pos+1的位置向前拷贝
  int start = pos+1;
  while (start < ps->size)
  {
    ps->a[start-1] = ps->a[start];
    ++start;
  }
  ps->size--;
}

四、小结

通过上面对动态顺序表的实现的讲解后,我们不难发现其实动态顺序表也是有很多缺点的:

1. 当空间不够时需要增容,而增容是有代价的;
2. 为了避免频繁扩容,我本每次扩2倍,这样可能导致空间的浪费;

3. 顺序表要求从开始位置连续存储,那么我们在头部和中部位置插入/删除数据就需要挪动数据,这样的效率并不高

但除了一些缺点外,顺序表当然也是有优点的:

1. 顺序表支持随机访问(下标);
2. cpu高速缓存命中率更高

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