顺序表-c语言实现

简介: 1. 顺序表概念顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。这里重点是数组,有两种顺序表:1.静态顺序表,2.动态顺序表。这里我只对动态的进行讲解。

1. 顺序表概念

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

这里重点是数组,有两种顺序表:1.静态顺序表,2.动态顺序表。这里我只对动态的进行讲解。

2. 静态顺序表

2.1 框架

静态顺序表实现目的:用来存储信息;实现结构:首先静态的我们想到的连续的存储空间就是数组,所以其结构中必须有个数组,其次光有数组是不行的,我们顺序表实现肯定有增删查改的功能,假设我们实现一个增加的功能,那么我们要知道有多少元素了,然后对数组大小进行增加一个,那么这里就要知道数组的大小,也就是数组元素个数。

2.2 结构

#define MAXSIZE 100
#typedef int SequenceDataType;
struct SequenceList{
    int arr[MAXSIZE];
    int size;
};

2.3 缺点

  1. 空间满了需要手动设置
  2. 在实现删除增加功能时需要依次移动位置进行覆盖,效率低
  3. 程序跑完并不会留下数据(并不使用动态内存管理)

这里静态顺序表使用场景太少,并不适用,所以不对其进行说明,相信你读完后续的动态顺序表介绍对于你来说静态的就是小儿科

3. 动态顺序表

3.1 框架

首先需要动态开辟内存空间,可以保留数据,那么就要使用指针(也就相当于数组名),存储空间是连续的,还需要元素个数,使用动态内存开辟我们要考虑一个问题那就是是否扩容,所以要有结构容量。

3.2 结构

typedef int SLDataType; //对类型进行重命名,后续可以直接更改类型,来存储不同类型
typedef struct SequenceList{
    SLDataType* a;
    int size;
    int capacity;
}SL;//重命名结构体,方便后续书写

3.3 功能实现

3.3.1 前提




0de9f8508e619926e5e8226a47fa9b8b.png


3.3.2 初始化动态顺序表

对结构内部进行改变,所以要拿到结构体的地址;指针置空,元素个数为0,容量为0

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

3.3.3 尾插数据

数组尾插数据,首先要考虑扩容问题,再对其尾部直接赋值,并且元素个数增加。(对结构体内部进行改变所以拿到结构体的地址)

void SeqListPushBack(SL* ps, SLDataType x) {
  assert(ps);
    //检查扩容问题
  if (ps->size == ps->capacity) {
    int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//最开始状态size=capacity=0,所以先对容量开4个空间,后面直接倍数开辟
    SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));//考虑到扩容失败就用tmp暂时接收(realloc语法中如果此时ps->a=NULL,则此函数就相当于malloc)
    if (tmp == NULL) {
      perror("SeqListPushBack:(realloc):");
      exit(-1);
    }
    ps->a = tmp;//ps->a指向开辟空间
    ps->capacity = newcapacity;//结构体中容量调整
  }
  ps->a[ps->size] = data;//给尾部数据
  ps->size++;//元素个数增加
}

为什么拿tmp暂时接收?

原因:扩容有可能失败,就拿tmp暂时接收,为NULL就不给ps->a,直接结束程序,这样不易出现漏洞。

扩容有什么问题吗?

扩容会有两种扩容:原地扩容和异地扩容。也就是当我们对ps->a这个地址向后开辟空间时,如果后面空间小于我们需要开辟的空间,系统就会再找另外一块有充分空间的内存进行开辟,最后返回一个新的地址,并不是在ps->a原本位置进行开辟的。异地扩容会把之前开辟的空间释放,找新一块空间。并不影响什么但是要说明一下(不理解可以看看相关realloc知识,你也可以测试)

3.3.4 尾删数据

直接删除最后一个数据,也就是元素个数减少一个(对结构体内部进行改变所以拿到结构体的地址)

void SeqListPopBack(SL* ps) {
  assert(ps);
  //检查没有数据还Pop情况
  assert(ps->size > 0);
  //再此使用时直接覆盖了,不需要另外创建变量来存储再置为0
  ps->size--;
}

3.3.5 头插数据

首先检查是否需要扩容,再需要对数组的每个元素依次向后移动一位,再在第一个位置上插入一个数据。(对结构体内部进行改变所以拿到结构体的地址)

void SeqListPushFront(SL* ps, SLDataType x)
{
  assert(ps);
  //检查扩容问题
  if (ps->size == ps->capacity) {
    int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//最开始状态size=capacity=0,所以先对容量开4个空间,后面直接倍数开辟
    SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));//考虑到扩容失败就用tmp暂时接收(realloc语法中如果此时ps->a=NULL,则此函数就相当于malloc)
    if (tmp == NULL) {
      perror("SeqListPushBack:(realloc):");
      exit(-1);
    }
    ps->a = tmp;//ps->a指向开辟空间
    ps->capacity = newcapacity;//结构体中容量调整
  }
    //依次移动位置进行覆盖
  int end = ps->size - 1;
  while (end >= 0)
  {
    ps->a[end + 1] = ps->a[end];
    end--;
  }
    //第一个位置上插入数据
  ps->a[0] = x;
  ps->size++;//元素个数增加
}

移动顺序是后到前:这样数据就不会丢失或者被覆盖

cc890fe0f0c9e7262c7adb3239dc997d.png

时间复杂度:O(N)


3.3.6 头删数据

直接依次向前移动数据即可(对结构体内部进行改变所以拿到结构体的地址)

void SeqListPopFront(SL* ps)
{
  assert(ps);
    //检查没有元素还Pop情况
  assert(ps->size > 0);
    //依次向前移动位置覆盖
  int begin = 1;
  while (begin < ps->size)
  {
    ps->a[begin - 1] = ps->a[begin];
    begin++;
  }
  ps->size--;//元素个数减少一个
}

==移动顺序是后到前:这样数据就不会丢失或者被覆盖

0040e967429c360659032e6a67bf3942.png

时间复杂度:O(N)

3.3.7 在任意位置插入数据

首先检查扩容,再对该位置之后的数据依次移动位置覆盖,再插入数据(对结构体内部进行改变所以拿到结构体的地址)

void SeqListInsert(SL* ps, int pos, SLDataType x) 
{
  assert(ps);
  //位置判断
  assert(pos >= 0);
  //可在尾部插入
  assert(pos <= ps->size);
  //检查扩容问题
  if (ps->size == ps->capacity) {
    int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//最开始状态size=capacity=0,所以先对容量开4个空间,后面直接倍数开辟
    SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));//考虑到扩容失败就用tmp暂时接收(realloc语法中如果此时ps->a=NULL,则此函数就相当于malloc)
    if (tmp == NULL) {
      perror("SeqListPushBack:(realloc):");
      exit(-1);
    }
    ps->a = tmp;//ps->a指向开辟空间
    ps->capacity = newcapacity;//结构体中容量调整
  }
  //对pos后数据依次向后移动一位
  int end = ps->size - 1;
  while (end >= pos)
  {
    ps->a[end + 1] = ps->a[end];
    end--;
  }
  //插入x数据
  ps->a[pos] = x;
  ps->size++;
}

时间复杂度:O(N)

3.3.8 在任意位置删除数据

对该位置之后的数据依次移动位置覆盖即可(对结构体内部进行改变所以拿到结构体的地址)

void SeqListErase(SL* ps, int pos)
{
  assert(ps);
    //删除不能越界
  assert(pos >= 0);
  assert(pos < ps->size);
  // 挪动数据覆盖
  int begin = pos + 1;
  while (begin < ps->size)
  {
    ps->a[begin - 1] = ps->a[begin];
    begin++;
  }
  ps->size--;
}

如果删除只有一个元素的数组可以吗?

可以的,你会发现只是执行了ps->size–操作,这样就不会找到这个数据了。

对最后一个数据进行删除可以吗?

可以的,同样只是执行了ps->size–操作。

3.3.9 从任意位置查找数据

就是从该位置向后遍历数据,找到返回下标,未找到返回-1.

int SeqListFind(SL* ps, SLDataType x, int begin)
{
  assert(ps);
    //限制begin起点
    assert(begin <= ps->size-1);
  assert(begin >= 0);
  //从begin开始,找到返回下标,没找到返回-1
  for (int i = begin; i < ps->size; ++i)
  {
    if (x == ps->a[i])
    {
      return i;
    }
  }
  return -1;
}

为什么未找到返回-1呢?

这里可以随意返回,这里的-1也不是最合适的选择,假设数据中有-1的话,那么就有冲突,这里只是个样例,实际上并可以搞个提示信息,并不返回。

3.3.10 打印数据

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

3.3.11 销毁顺序表

只有不为空的情况下销毁,否则不会销毁。(对结构体内部进行改变所以拿到结构体的地址)

void SeqListDestroy(SL* ps) {
  assert(ps);
  //不为空的情况下销毁
  if (ps->a) {
    free(ps->a);
    ps->a = NULL;
    ps->size = 0;
    ps->capacity = 0;
  }
    else
    {
        perror("Sequence is NULL,can not destory!");
        exit(-1);
    }
}

3.4 优化改进

3.4.1 尾插数据优化

void SeqListPushBack(SL* ps, SLDataType x) {
  assert(ps);
  //对ps->size下标出尾插
  SeqListInsert(ps, ps->size, x);
}

3.4.2 尾删数据优化

void SeqListPopBack(SL* ps) {
  assert(ps);
  //检查没有数据还Pop情况
  assert(ps->size > 0);
  //对ps->size-1下标数据删除
  SeqListErase(ps, ps->size - 1);
}

3.4.3 头插数据优化

void SeqListPushFront(SL* ps, SLDataType x)
{
  assert(ps);
  //下标为0出插入数据
  SeqListInsert(ps, 0, x);
}

3.4.4 头删数据优化

void SeqListPopFront(SL* ps)
{
  assert(ps);
  assert(ps->size > 0);
  //对下标为0出数据删除
  SeqListErase(ps, 0);
}

感谢大家观看,有很多不足可以私信讨论,嘻嘻!




相关文章
|
22天前
|
存储 C语言
【数据结构】顺序表(c语言实现)(附源码)
本文介绍了线性表和顺序表的基本概念及其实现。线性表是一种有限序列,常见的线性表有顺序表、链表、栈、队列等。顺序表是一种基于连续内存地址存储数据的数据结构,其底层逻辑是数组。文章详细讲解了静态顺序表和动态顺序表的区别,并重点介绍了动态顺序表的实现,包括初始化、销毁、打印、增删查改等操作。最后,文章总结了顺序表的时间复杂度和局限性,并预告了后续关于链表的内容。
56 3
|
1月前
|
存储 C语言
探索C语言数据结构:利用顺序表完成通讯录的实现
本文介绍了如何使用C语言中的顺序表数据结构实现一个简单的通讯录,包括初始化、添加、删除、查找和保存联系人信息的操作,以及自定义结构体用于存储联系人详细信息。
20 2
|
1月前
|
C语言
链式顺序表实现(C语言描述)
本文介绍了如何在C语言中实现链式顺序表,包括数据结构的定义、节点的创建、数据的插入和删除以及链表的打印和销毁。
39 2
|
1月前
|
C语言
顺序表数组法构建(C语言描述)
如何使用C语言通过数组方法构建有序顺序表,包括顺序表的创建、插入、删除和打印等。
20 2
|
2月前
|
存储 C语言 C++
数据结构基础详解(C语言) 顺序表:顺序表静态分配和动态分配增删改查基本操作的基本介绍及c语言代码实现
本文介绍了顺序表的定义及其在C/C++中的实现方法。顺序表通过连续存储空间实现线性表,使逻辑上相邻的元素在物理位置上也相邻。文章详细描述了静态分配与动态分配两种方式下的顺序表定义、初始化、插入、删除、查找等基本操作,并提供了具体代码示例。静态分配方式下顺序表的长度固定,而动态分配则可根据需求调整大小。此外,还总结了顺序表的优点,如随机访问效率高、存储密度大,以及缺点,如扩展不便和插入删除操作成本高等特点。
200 5
|
2月前
|
存储 算法 C语言
C语言手撕数据结构代码_顺序表_静态存储_动态存储
本文介绍了基于静态和动态存储的顺序表操作实现,涵盖创建、删除、插入、合并、求交集与差集、逆置及循环移动等常见操作。通过详细的C语言代码示例,展示了如何高效地处理顺序表数据结构的各种问题。
|
5月前
|
存储 C语言
数据结构——顺序表(C语言版)
数据结构——顺序表(C语言版)
64 5
|
6月前
|
存储 C语言
C语言实验-动态顺序表实现简易通讯录(二)
在这个C语言实验中,你将实现一个简单的通讯录,它使用动态顺序表来存储联系人信息。
51 2
|
6月前
|
存储 C语言
数据结构——顺序表(C语言)
数据结构——顺序表(C语言)
|
5月前
|
存储 C语言
数据结构之顺序表(C语言版)
数据结构之顺序表(C语言版)
28 0