[C语言数据结构]顺序表

简介: [C语言数据结构]顺序表

💊1.1顺序的定义

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。(要求数据在物理上是连续存储的)

💊1.2顺序表的分类

💊1.2.1静态顺序表

静态顺序表使用定长数组存储数据

💊1.2.2静态顺序表的定义

首先我们要实现的是静态顺序表,所以我们一定要采用数组,其次作为一个静态顺序表它还需要一个变量来存储它内部所存储的数据的数量;

所以我们可以构建一个结构体,其中包含两个变量:
一个定长的数组(可以采用宏定义的方法来控制数组大小,方便后期随时修改);

一个整型变量(用来存储当前静态顺序表中所存储的数据的个数);

上代码:

//创建静态的顺序表
#define N 100
struct SeqList
{
  int a[N];
  int size;
};

💊1.2.3动态顺序表

对于静态顺序表来说我们使用的比较少,而且实现起来也比较简单,所以本文主要讲解关于动态顺序表的实现

💊1.2.4动态顺序表的定义

首先动态顺序表是由动态开辟的数组存储数据,和静态顺序表相比他的存储空间可以根据需要来进行扩充。所以动态顺序表利用的就是一段动态开辟的数组存储;

💊1.2.5动态顺序表的实现

对于动态顺序表,所以我们一定要采用指针(用于指向动态开辟的数组,方便后期的扩容),作为一个动态顺序表它还需要一个变量来存储它内部所存储的数据的数量,由于动态顺序表当空间不足时需要对数组的空间进行扩容,所以相比较静态顺序表,动态顺序表还要多一个变量来记录当前顺序表的大小;

所以我们可以构建一个结构体,其中包含三个变量:

一个指针(类型可以用 typedef 来声明一下,方便后期修改数据类型);

一个整型变量(用来存储当前动态顺序表中所存储的数据的个数);

一个整型变量(用来存储当前动态顺序表的容量);

上代码:

typedef int SeqType;
typedef struct SeqList
{
  SeqType* a;
  int size;   //有效数据的个数
  int capacity;   //容量大小
}SeqList;

对于动态顺序表来说我们主要实现以下几个函数功能:

//顺序表的初始化
void SeqListInit(SeqList* pSeq);
//顺序表的销毁
void SeqListDestroy(SeqList* pSeq);
//打印顺序表中的数据
void SeqListPrint(SeqList* pSeq);
//检查顺序表的大小是否够用
void CheckSeqList(SeqList* pSeq);
//顺序表的尾插
void SeqListPushBack(SeqList* pSeq, SeqType x);
//顺序表的头插
void SeqListPushFront(SeqList* pSeq, SeqType x);
//顺序表的尾删
void SeqListPopBack(SeqList* pSeq);
//顺序表的头删
void SeqListPopFront(SeqList* pSeq);
//在顺序表寻找元素
int SeqListFind(SeqList* pSeq, SeqType x);
//在顺序表任意位置插入
void SeqListInsert(SeqList* pSeq,int pos ,SeqType x);
//在顺序表任意位置删除
void SeqListErase(SeqList* pSeq, int pos);
//给顺序表修改
void SeqListModify(SeqList* pSeq, int pos, SeqType x);

所以接下来将细讲,以上函数功能该如何实现,并且实现的过程中存在那些细节和要注意的一些点;

①首先对于第一个函数功能对顺序表进行初始化;
void SeqListInit(SeqList* pSeq);

对于初始化,我们要实现对于动态顺序表的数组指针置空,并且size和capacity全部置零;

所以该函数的实现非常简单,我们直接上代码:

//顺序表的初始化
void SeqListInit(SeqList* pSeq)
{
  //断言
  //如果pSeq为空指针则终止程序,并且显示程序错误的位置
  //assert(pSeq != NULL);
  assert(pSeq);
  pSeq->a = NULL;
  pSeq->size = pSeq->capacity = 0;
}

注意:我们观察函数声明发现函数的参数类型选用了指针类型,这是为什么呢,因为我们在C语言的函数中,函数形参只是实参的一份临时拷贝,如果我们不用指针来修改顺序表的参数的话,那么我们是不能达到修改的效果的(在函数内部修改,并不会影响函数外部的情况);


②顺序表销毁函数:

void SeqListDestroy(SeqList* pSeq);

对于顺序表的销毁,我们要实现功能非常简单,只需要释放掉数组指针的动态内存空间,然后再将顺序表的size和capacity全部置零;

上代码:

//顺序表的销毁
void SeqListDestroy(SeqList* pSeq)
{
  assert(pSeq);
  free(pSeq->a);
  pSeq->a = NULL;
  pSeq->size = pSeq->capacity = 0;
}

注意:这个函数功能非常简单所以需要注意的点就是对于动态内存空间的释放,释放结束后一定要记得对数组指针进行置空,防止野指针问题;


③打印顺序表中的数据:

void SeqListPrint(SeqList* pSeq);

该函数的功能就是实现将顺序表中的数据从头到尾依次打印出来即可;

代码:

//打印顺序表中的数据
void SeqListPrint(SeqList* pSeq)
{
  assert(pSeq);
  for (int i = 0; i < pSeq->size; i++)
  {
    printf("%d ", pSeq->a[i]);
  }
  printf("\n");
}

注意:这个函数有一个点需要注意,更换了SeqType之后,也就是更换了顺序表的数据类型的时候,这块printf函数内部的参数需要进行手动修改。


④检查顺序表的容量是否需要扩充

void CheckSeqList(SeqList* pSeq);

该函数的功能就是在每次需要向顺序表中插入数据的时候,检查顺序表的容量是否足够,如果不足对其进行扩容;

上代码:

//检查顺序表的大小是否够用
void CheckSeqList(SeqList* pSeq)
{
  //如果空间不够则需要增容
  if (pSeq->size == pSeq->capacity)
  {
    int newcapacity = pSeq->capacity == 0 ? 4 : pSeq->capacity * 2;
    SeqList* newA = (SeqList*)realloc(pSeq->a, sizeof(SeqList) * newcapacity);
    if (newA == NULL)
    {
      printf("realloc is fail\n SeqListPushBack");
      exit(-1);
    }
    pSeq->a = newA;
    pSeq->capacity = newcapacity;
  }
}

注意:这个函数内部使用的是realloc函数申请动态内存空间,因为realloc函数的功能可以在扩充内存的同时,将原本空间中的数据也拷贝到新申请的更大的空间中;


⑤在顺序表任意位置插入数据
void SeqListInsert(SeqList* pSeq,int pos ,SeqType x);

该函数的功能就是实现在顺序表的任意位置插入数据,所以函数总共由三个参数,第一个顺序表数组指针,第二个是要插入的位置,第三个是要插入的数据;因为插入数据存在顺序表的容量是否足够的问题,所以我们在该函数内部调用了

void CheckSeqList(SeqList* pSeq);以应对容量不足的情况;

上代码:

//在顺序表任意位置插入
void SeqListInsert(SeqList* pSeq, int pos, SeqType x)
{
  assert(pSeq);
  assert(pos >= 0 && pos <= pSeq->size);
  CheckSeqList(pSeq);
  for (int i =  pSeq->size; i > pos; i--)
  {
    pSeq->a[i] = pSeq->a[i - 1];
  }
  pSeq->a[pos] = x;
  pSeq->size++;
}

注意:在这个函数内部牵扯到移动顺序表中数据的情况,所以我们要先进行容量大小的判断。然后在进行数据的插入;

对于挪动数据的时候我们要采取从后往前的挪的方法,因为这样的话就可以保证原本顺序表中的数据不会被覆盖而丢失;


⑥在顺序表任意位置删除数据
void SeqListErase(SeqList* pSeq, int pos);

这个函数的功能就是实现在顺序表的任意位置插入数据,参数与上一个函数类似,就不再介绍了;

直接上代码:

//在顺序表任意位置删除
void SeqListErase(SeqList* pSeq, int pos)
{
  assert(pSeq);
  assert(pos >= 0 && pos <= pSeq->size);
  for (int i = pos; i < pSeq->size - 1; i++)
  {
    pSeq->a[i] = pSeq->a[i + 1];
  }
  pSeq->size--;
}

注意:这个函数是直接从要插入的位置从前往后开始移动数据,直接将要删除的数据进行覆盖,任意位置插入函数的操作正好相反


⑦顺序表的头插头删:

void SeqListPushFront(SeqList* pSeq, SeqType x);
void SeqListPopFront(SeqList* pSeq);

首先来说头删函数,对于这个函数其实就是执行了上面的在顺序表的任意位置插入数据函数,只是将其中的pos参数修改为0即可。同理头删函数,也可以理解为是在顺序表的任意位置删除数据函数,将其中的pos参数修改为0即可;

所以上代码:

//头部插入
void SeqListPushFront(SeqList* pSeq, SeqType x)
{
  SeqListInsert(pSeq, 0, x);
}
//头部删除
void SeqListPopFront(SeqList* pSeq)
{
  SeqListErase(pSeq, 0);
}

⑧顺序表的尾插尾删:

void SeqListPushBack(SeqList* pSeq, SeqType x);
void SeqListPopBack(SeqList* pSeq);

这两个函数的实现可以说和上面的两个函数非常相似,只是将任意位置插入函数和任意位置删除函数的参数pos换成ps->size;
所以上代码:

//尾插
void SeqListPushBack(SeqList* pSeq, SeqType x)
{
  SeqListInsert(pSeq, pSeq->size, x);
}
//尾部删除
void SeqListPopBack(SeqList* pSeq)
{
  SeqListErase(pSeq, pSeq->size);
}

⑨在顺序表中寻找数据:

int SeqListFind(SeqList* pSeq, SeqType x);
该函的功能是实现在顺序表中寻找数据,就是简单的遍历整个顺序表,当找到数据时就返回下标;

所以直接上代码:

//在顺序表寻找元素
int SeqListFind(SeqList* pSeq, SeqType x)
{
  for (int i = 0; i < pSeq->size; i++)
  {
    if (x == pSeq->a[i])
    {
      return i;
    }
  }
  return -1;
}

注意:但是对于上面的函数有一个缺点,若是寻找的数据在顺序表中存在多个,那么这个函数只会返回第一个匹配数据的下标

而不能返回剩下元素的下标;所以我们对该函数进行改造,再此基础上引入一个新的参数:int begin,目的就是让函数从begin的位置开始寻找数据,可以跳过之前所匹配的函数从而找出所有匹配的数据;、

上代码:

//在顺序表寻找元素
int SeqListFind(SeqList* pSeq, SeqType x,int begin)
{
  for (int i = begin; i < pSeq->size; i++)
  {
    if (x == pSeq->a[i])
    {
      return i;
    }
  }
  return -1;
}

⑩修改顺序表中指定位置的数据:

void SeqListModify(SeqList* pSeq, int pos, SeqType x);
该函数实现的是修改顺序表中指定位置的数据,所以函数的参数包括,顺序表的数组指针,要修改的位置pos,和要修改成的数据x;函数的功能实现非常简单,所以我们直接上代码:

//给顺序表修改
void SeqListModify(SeqList* pSeq, int pos, SeqType x)
{
  assert(pos >= 0 && pos < pSeq->size);
  pSeq->a[pos] = x;
}

注意:该函数内部需要对size 的范围进行判断,以防止产生越界访问


对于以上函数有一个要注意的细节就是我们在删除顺序表中的数据的时候,我们不能无线的删除,当size==0的时候我们就不能进行删除操作了,对于这个问题,我的代码中采取的是断言的方法,比较粗暴的停止程序,但是当程序停止的时候我们可以迅速知道是哪里出了问题;


完整代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
创建静态的顺序表
// //#define N 100
//struct SeqList
//{
//  int a[N];
//  int size;
//};
typedef int SeqType;
typedef struct SeqList
{
  SeqType* a;
  int size;   //有效数据的个数
  int capacity;   //容量大小
}SeqList;
//顺序表的初始化
void SeqListInit(SeqList* pSeq);
//顺序表的销毁
void SeqListDestroy(SeqList* pSeq);
//打印顺序表中的数据
void SeqListPrint(SeqList* pSeq);
//检查顺序表的大小是否够用
void CheckSeqList(SeqList* pSeq);
//顺序表的增删查改的接口
//
void SeqListPushBack(SeqList* pSeq, SeqType x);
void SeqListPushFront(SeqList* pSeq, SeqType x);
void SeqListPopBack(SeqList* pSeq);
void SeqListPopFront(SeqList* pSeq);
//在顺序表寻找元素
int SeqListFind(SeqList* pSeq, SeqType x);
//在顺序表任意位置插入
void SeqListInsert(SeqList* pSeq,int pos ,SeqType x);
//在顺序表任意位置删除
void SeqListErase(SeqList* pSeq, int pos);
//给顺序表修改
void SeqListModify(SeqList* pSeq, int pos, SeqType x);
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
//顺序表的初始化
void SeqListInit(SeqList* pSeq)
{
  //断言
  //如果pSeq为空指针则终止程序,并且显示程序错误的位置
  //assert(pSeq != NULL);
  assert(pSeq);
  pSeq->a = NULL;
  pSeq->size = pSeq->capacity = 0;
}
//顺序表的销毁
void SeqListDestroy(SeqList* pSeq)
{
  assert(pSeq);
  free(pSeq->a);
  pSeq->a = NULL;
  pSeq->size = pSeq->capacity = 0;
}
//打印顺序表中的数据
void SeqListPrint(SeqList* pSeq)
{
  assert(pSeq);
  for (int i = 0; i < pSeq->size; i++)
  {
    printf("%d ", pSeq->a[i]);
  }
  printf("\n");
}
//检查顺序表的大小是否够用
void CheckSeqList(SeqList* pSeq)
{
  //如果空间不够则需要增容
  if (pSeq->size == pSeq->capacity)
  {
    int newcapacity = pSeq->capacity == 0 ? 4 : pSeq->capacity * 2;
    SeqList* newA = (SeqList*)realloc(pSeq->a, sizeof(SeqList) * newcapacity);
    if (newA == NULL)
    {
      printf("realloc is fail\n SeqListPushBack");
      exit(-1);
    }
    pSeq->a = newA;
    pSeq->capacity = newcapacity;
  }
}
//尾插
void SeqListPushBack(SeqList* pSeq, SeqType x)
{
  SeqListInsert(pSeq, pSeq->size, x);
  //assert(pSeq);
  如果空间不够则需要增容
  //CheckSeqList(pSeq);
  //pSeq->a[pSeq->size] = x;
  //pSeq->size++;
}
//头插
void SeqListPushFront(SeqList* pSeq, SeqType x)
{
  SeqListInsert(pSeq, 0, x);
  //assert(pSeq);
  //CheckSeqList(pSeq);
  //int end = pSeq->size - 1;
  //while (end >= 0)
  //{
  //  pSeq->a[end + 1] = pSeq->a[end];
  //  end--;
  //}
  //pSeq->a[0] = x;
  //pSeq->size++;
}
//尾部删除
void SeqListPopBack(SeqList* pSeq)
{
  SeqListErase(pSeq, pSeq->size);
  //assert(pSeq);
  //assert(pSeq->size > 0);//判断size是否大于零
  //pSeq->size--;
}
//头部删除
void SeqListPopFront(SeqList* pSeq)
{
  SeqListErase(pSeq, 0);
  //assert(pSeq);
  //assert(pSeq->size > 0);//判断size是否大于零
  //for (int i = 1; i < pSeq->size; i++)
  //{
  //  pSeq->a[i - 1] = pSeq->a[i];
  //}
  //pSeq->size--;
}
//在顺序表寻找元素
int SeqListFind(SeqList* pSeq, SeqType x)
{
  for (int i = 0; i < pSeq->size; i++)
  {
    if (x == pSeq->a[i])
    {
      return i;
    }
  }
  return -1;
}
//在顺序表任意位置插入
void SeqListInsert(SeqList* pSeq, int pos, SeqType x)
{
  assert(pSeq);
  assert(pos >= 0 && pos <= pSeq->size);
  CheckSeqList(pSeq);
  for (int i =  pSeq->size; i > pos; i--)
  {
    pSeq->a[i] = pSeq->a[i - 1];
  }
  pSeq->a[pos] = x;
  pSeq->size++;
}
//在顺序表任意位置删除
void SeqListErase(SeqList* pSeq, int pos)
{
  assert(pSeq);
  assert(pos >= 0 && pos <= pSeq->size);
  for (int i = pos; i < pSeq->size - 1; i++)
  {
    pSeq->a[i] = pSeq->a[i + 1];
  }
  pSeq->size--;
}
//给顺序表修改
void SeqListModify(SeqList* pSeq, int pos, SeqType x)
{
  assert(pos >= 0 && pos < pSeq->size);
  pSeq->a[pos] = x;
}

以上就是动态顺序表的实现和各个函数需要注意的细节,如果有错误可以在评论区指正!

相关文章
|
3月前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
107 1
|
3月前
|
存储 算法 搜索推荐
【趣学C语言和数据结构100例】91-95
本文涵盖多个经典算法问题的C语言实现,包括堆排序、归并排序、从长整型变量中提取偶数位数、工人信息排序及无向图是否为树的判断。通过这些问题,读者可以深入了解排序算法、数据处理方法和图论基础知识,提升编程能力和算法理解。
84 4
|
3月前
|
存储 机器学习/深度学习 搜索推荐
【趣学C语言和数据结构100例】86-90
本文介绍并用C语言实现了五种经典排序算法:直接插入排序、折半插入排序、冒泡排序、快速排序和简单选择排序。每种算法都有其特点和适用场景,如直接插入排序适合小规模或基本有序的数据,快速排序则适用于大规模数据集,具有较高的效率。通过学习这些算法,读者可以加深对数据结构和算法设计的理解,提升解决实际问题的能力。
66 4
|
3月前
|
存储 算法 数据处理
【趣学C语言和数据结构100例】81-85
本文介绍了五个经典算法问题及其C语言实现,涵盖图论与树结构的基础知识。包括使用BFS求解单源最短路径、统计有向图中入度或出度为0的点数、统计无向无权图各顶点的度、折半查找及二叉排序树的查找。这些算法不仅理论意义重大,且在实际应用中极为广泛,有助于提升编程能力和数据结构理解。
67 4
|
3月前
|
算法 数据可视化 数据建模
【趣学C语言和数据结构100例】76-80
本文介绍了五种图论算法的C语言实现,涵盖二叉树的层次遍历及广度优先搜索(BFS)和深度优先搜索(DFS)的邻接表与邻接矩阵实现。层次遍历使用队列按层访问二叉树节点;BFS利用队列从源节点逐层遍历图节点,适用于最短路径等问题;DFS通过递归或栈深入图的分支,适合拓扑排序等场景。这些算法是数据结构和算法学习的基础,对提升编程能力和解决实际问题至关重要。
71 4
|
13天前
|
定位技术 C语言
c语言及数据结构实现简单贪吃蛇小游戏
c语言及数据结构实现简单贪吃蛇小游戏
|
1月前
|
搜索推荐 C语言
数据结构(C语言)之对归并排序的介绍与理解
归并排序是一种基于分治策略的排序算法,通过递归将数组不断分割为子数组,直到每个子数组仅剩一个元素,再逐步合并这些有序的子数组以得到最终的有序数组。递归版本中,每次分割区间为[left, mid]和[mid+1, right],确保每两个区间内数据有序后进行合并。非递归版本则通过逐步增加gap值(初始为1),先对单个元素排序,再逐步扩大到更大的区间进行合并,直至整个数组有序。归并排序的时间复杂度为O(n*logn),空间复杂度为O(n),且具有稳定性,适用于普通排序及大文件排序场景。
|
1月前
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】顺序表的基本运算(头歌实践教学平台习题)【合集】
本文档介绍了线性表的基本运算任务,涵盖顺序表和链表的初始化、销毁、判定是否为空、求长度、输出、查找元素、插入和删除元素等内容。通过C++代码示例详细展示了每一步骤的具体实现方法,并提供了测试说明和通关代码。 主要内容包括: - **任务描述**:实现顺序表的基本运算。 - **相关知识**:介绍线性表的基本概念及操作,如初始化、销毁、判定是否为空表等。 - **具体操作**:详述顺序表和链表的初始化、求长度、输出、查找、插入和删除元素的方法,并附有代码示例。 - **测试说明**:提供测试输入和预期输出,确保代码正确性。 - **通关代码**:给出完整的C++代码实现,帮助完成任务。 文档
50 5
|
2月前
|
数据库
数据结构中二叉树,哈希表,顺序表,链表的比较补充
二叉搜索树,哈希表,顺序表,链表的特点的比较
数据结构中二叉树,哈希表,顺序表,链表的比较补充
|
3月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
103 5