【数据结构初阶】第二篇——顺序表

简介: 【数据结构初阶】第二篇——顺序表

顺序表的概念及其结构


基本概念

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

image.png

  • 存储空间连续,既允许元素的顺序访问,又可以随机访问
  • 要访问指定元素,可以使用索引(下标)来访问,时间复杂度为O(1)
  • 要在其中增加或者删除一个元素,都要涉及后面所有元素的向前或向后移动,时间复杂度为O(n);
  • 可以方便的存储表中的任一结点,存储速度快
  • 长度固定,分配内存之前必须知道数组的长度;
  • 无需为表示结点间的逻辑关系而增加额外的存储空间,存储利用率提高

存储结构:

顺序表一般可以分为:

1.静态顺序表:使用定长数组存储;

2.动态顺序表:使用动态开辟的数组存储;

静态分配和动态分配有什么不同?其实就是数组的不同。在静态分配时,我们在编写的时候,就已经确定了数组的大小。而动态分配时,没有确定它的大小,是根据动态分配语句在运行时才将它的大小进行分配。这样有一点好处就是,在静态分配时,当我想要存放顺序表的数据元素超过100时候则会产生错误溢出,而动态分配时,如果一旦超过了分配的空间大小,可以再重新分配一块内存空间,把旧的空间和所增加的数据元素转移到新申请的空间上,这样就不会产生溢出的问题了。这是动态分配的一个优点。

代码如下:

//顺序表的静态存储
#define N 8
typedef int SLDataType;
typedef struct SeqList
{
  SLDataType array[N];//定长数组
  size_t size;//有效数组的个数
}SeqList;
//顺序表的动态存储
typedef int SLDataType;
typedef struct SeqList
{ 
   SLDataType* array;//指向动态开辟的数组
   size_t size;//有效数据的个数
   size_t capacity;//空间容量的大小
}SeqList;
/*注解:我们发现这里用的是指针,指针是存放一个存储单元地址的.顺序表根据第一个数据元素的地址和数据元素的大小,就可以算出任意数据元素的位置.即只定义第一个元素的指针即可,描述整个顺序表。但它仅仅是个地址,没有确切的空间,因此我们使用是要开辟空间;
SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity*sizeof(SLDataType));
详细代码下面讲解;*/

静态顺序表的定长数组导致N定长,空间开少了不够用,开多了浪费,所以现实中都是使用动态顺序表,使用倍增-复制的办法来支持动态扩容,将顺序表变成了"可变长度"的,下面我将实现动态顺序表;

初始化顺序表


对顺序表进行初始化

void SeqListInit(SeqList* ps)
{
  assert(ps);
  ps->array = NULL;//顺序表指针NULL
  ps->size = 0;//起始元素个数为0
  ps->capacity = 0;//容量为0
}

销毁顺序表


因为顺序表所用的内存空间是动态开辟在堆区的,所以我们在使用完后需要及时对其进行释放,避免造成内存泄漏

//销毁顺序表
void SeqListDestory(SeqList* ps)
{
  assert(ps);
  free(ps->a);//释放顺序表指针指向的空间
  ps->a = NULL;//及时置空
  ps->size = 0;//元素个数置0
  ps->capacity = 0;//容量置0
}

若需要对数据进行保存,可以使用文件操作函数将数据保存到一个文件中,下次使用顺序表的时候先读取文件数据即可

打印顺序表


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

增加数据


头插


每一次在顺序表最前方插入,其他数据后移

image.png

void SeqListPushFront(SL* ps, SLDataType x)
{
  //如果没有空间,或者空间不足,我们可以增容
  if (ps->size == ps->capacity)
  {
    int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//对数组进行二倍增容,使用三目运算符是为了防止0这种情况
    SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * newcapacity);
    //判断是否增容成功
    if(tmp == NULL)
    {
      printf("realloc fail\n");
      exit(-1);
    }
    ps->a = tmp;
    ps->capacity = newcapacity;
  }
    //从最后一个元素开始向前遍历到第一个元素,分别将它们向后移动一个位置
  for (int i = ps->size - 1; i >= 0; i--)
  {
    ps->a[i + 1] = ps->a[i];
  }
  ps->a[0] = x;//将要插入的数据插入到头部
  ps->size++;//表长加1
}

尾插


顺序表的末尾增加数据,其他元素不用改变

image.png

void SeqListPushBack(SL* ps, SLDataType x)
{
  if (ps->size == ps->capacity)
  {
    int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
    SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));
    if (tmp == NULL)
    {
      printf("realloc fail");
      exit(-1);
    }
    ps->a = tmp;
    ps->capacity = newcapacity;
  }
  ps->a[ps->size] = x;
  ps->size++;
}

指定下标位置插入


从最后一个元素开始向前遍历到第指定位置,分别将它们向后移动一个位置

image.png

void SeqListInsert(SL* ps, int pos, SLDataType x)
{
  //因为顺序表连续存储,所以首先要判断插入位置是否合法;
  assert(pos >= 0 && pos <= ps->size);
  //增容
  if (ps->size == ps->capacity)
  {
    int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
    SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * newcapacity);
    if (tmp == NULL)
    {
      printf("realloc fail\n");
      exit(-1);
    }
    ps->a = tmp;
    ps->capacity = newcapacity;
  }
  //从最后一个元素开始向前遍历到第指定位置,分别将它们向后移动一个位置
  for (int i = ps->size - 1; i >= pos; i--)
  {
    ps->a[i + 1] = ps->a[i];
  }
    //将x插入到指定位置
  ps->a[pos] = x;
    //表长加1
  ps->size++;
}

删除数据


头删


从删除位置遍历到最后一个元素的位置,将他们依次向前移一个位置;

image.png

void SeqListPopFront(SeqList* ps)
{
  assert(ps->size > 0);
  for (int i = 1; i < ps->size; i++)
  {
    ps->array[i-1] = ps->array[i];
  }
  ps->size--;
}

尾删


将表长减去1即可;

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

删除指定位置


首先判断指定位置是否合法然后从指定位置的下一个元素依次向前移动一步

image.png

void SeqListErase(SL* ps, int pos)
{
  assert(pos >= 0 && pos < ps->size);
  for (int i = pos + 1; i < ps->size; i++)
  {
    ps->a[i - 1] = ps->a[i];
  }
  ps->size--;
}

查找数据


顺序表的一端开始,依次将每个元素的关键字同给定值 K 进行比较,直到相等或比较完毕还未找到.

int SeqListFind(SL* ps, SLDataType x)
{
  for (int i = 0; i < ps->size; i++)
  {
    if (ps->a[i] == x)
    {
      return i;
    }
  }
  return -1;
}

修改数据


//修改指定下标位置元素
void SeqListModify(SeqList* ps, int pos, SLDataType x)
{
  assert(ps);
  assert(pos >= 0 && pos < ps->size);//检查输入下标的合法性
  ps->a[pos] = x;//修改数据
}



相关文章
|
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月前
|
存储
数据结构(顺序表)
数据结构(顺序表)
29 0
|
2月前
|
存储 算法
【数据结构】新篇章 -- 顺序表
【数据结构】新篇章 -- 顺序表
17 0
|
2月前
|
存储 测试技术
探索数据结构:顺序表的实现与应用
探索数据结构:顺序表的实现与应用

热门文章

最新文章