[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;
}

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

相关文章
|
16天前
|
存储 Java 程序员
【数据结构】初识集合&深入剖析顺序表(Arraylist)
Java集合框架主要由接口、实现类及迭代器组成,包括Collection和Map两大类。Collection涵盖List(有序、可重复)、Set(无序、不可重复),Map则由键值对构成。集合通过接口定义基本操作,具体实现由各类如ArrayList、HashSet等提供。迭代器允许遍历集合而不暴露其实现细节。List系列集合元素有序且可重复,Set系列元素无序且不可重复。集合遍历可通过迭代器、增强for循环、普通for循环及Lambda表达式实现,各有适用场景。其中ArrayList实现了动态数组功能,可根据需求自动调整大小。
28 11
|
22天前
|
存储 人工智能 C语言
数据结构基础详解(C语言): 栈的括号匹配(实战)与栈的表达式求值&&特殊矩阵的压缩存储
本文首先介绍了栈的应用之一——括号匹配,利用栈的特性实现左右括号的匹配检测。接着详细描述了南京理工大学的一道编程题,要求判断输入字符串中的括号是否正确匹配,并给出了完整的代码示例。此外,还探讨了栈在表达式求值中的应用,包括中缀、后缀和前缀表达式的转换与计算方法。最后,文章介绍了矩阵的压缩存储技术,涵盖对称矩阵、三角矩阵及稀疏矩阵的不同压缩存储策略,提高存储效率。
|
22天前
|
C语言
数据结构基础详解(C语言):图的基本概念_无向图_有向图_子图_生成树_生成森林_完全图
本文介绍了图的基本概念,包括图的定义、无向图与有向图、简单图与多重图等,并解释了顶点度、路径、连通性等相关术语。此外还讨论了子图、生成树、带权图及几种特殊形态的图,如完全图和树等。通过这些概念,读者可以更好地理解图论的基础知识。
|
24天前
|
存储 算法 C语言
数据结构基础详解(C语言): 二叉树的遍历_线索二叉树_树的存储结构_树与森林详解
本文从二叉树遍历入手,详细介绍了先序、中序和后序遍历方法,并探讨了如何构建二叉树及线索二叉树的概念。接着,文章讲解了树和森林的存储结构,特别是如何将树与森林转换为二叉树形式,以便利用二叉树的遍历方法。最后,讨论了树和森林的遍历算法,包括先根、后根和层次遍历。通过这些内容,读者可以全面了解二叉树及其相关概念。
|
24天前
|
存储 机器学习/深度学习 C语言
数据结构基础详解(C语言): 树与二叉树的基本类型与存储结构详解
本文介绍了树和二叉树的基本概念及性质。树是由节点组成的层次结构,其中节点的度为其分支数量,树的度为树中最大节点度数。二叉树是一种特殊的树,其节点最多有两个子节点,具有多种性质,如叶子节点数与度为2的节点数之间的关系。此外,还介绍了二叉树的不同形态,包括满二叉树、完全二叉树、二叉排序树和平衡二叉树,并探讨了二叉树的顺序存储和链式存储结构。
|
24天前
|
存储 C语言
数据结构基础详解(C语言): 树与二叉树的应用_哈夫曼树与哈夫曼曼编码_并查集_二叉排序树_平衡二叉树
本文详细介绍了树与二叉树的应用,涵盖哈夫曼树与哈夫曼编码、并查集以及二叉排序树等内容。首先讲解了哈夫曼树的构造方法及其在数据压缩中的应用;接着介绍了并查集的基本概念、存储结构及优化方法;随后探讨了二叉排序树的定义、查找、插入和删除操作;最后阐述了平衡二叉树的概念及其在保证树平衡状态下的插入和删除操作。通过本文,读者可以全面了解树与二叉树在实际问题中的应用技巧和优化策略。
|
24天前
|
存储 算法 C语言
C语言手撕数据结构代码_顺序表_静态存储_动态存储
本文介绍了基于静态和动态存储的顺序表操作实现,涵盖创建、删除、插入、合并、求交集与差集、逆置及循环移动等常见操作。通过详细的C语言代码示例,展示了如何高效地处理顺序表数据结构的各种问题。
|
3天前
|
算法 安全 测试技术
golang 栈数据结构的实现和应用
本文详细介绍了“栈”这一数据结构的特点,并用Golang实现栈。栈是一种FILO(First In Last Out,即先进后出或后进先出)的数据结构。文章展示了如何用slice和链表来实现栈,并通过golang benchmark测试了二者的性能差异。此外,还提供了几个使用栈结构解决的实际算法问题示例,如有效的括号匹配等。
golang 栈数据结构的实现和应用
01_设计一个有getMin功能的栈
01_设计一个有getMin功能的栈
|
3天前
|
前端开发
07_用队列实现栈
07_用队列实现栈
下一篇
无影云桌面