【数据结构】顺序表(c语言实现)(附源码)

简介: 本文介绍了线性表和顺序表的基本概念及其实现。线性表是一种有限序列,常见的线性表有顺序表、链表、栈、队列等。顺序表是一种基于连续内存地址存储数据的数据结构,其底层逻辑是数组。文章详细讲解了静态顺序表和动态顺序表的区别,并重点介绍了动态顺序表的实现,包括初始化、销毁、打印、增删查改等操作。最后,文章总结了顺序表的时间复杂度和局限性,并预告了后续关于链表的内容。

前言

       在我们学习顺序表之前,先引入一个概念:线性表。那么线性表是什么呢?


线性表,是n个具有相同特性的数据元素的有限序列。线性表在数据结构当中广泛使用。常见的线性表有:顺序表、链表、栈、队列、字符串......线性表在逻辑上是线性结构,也就是说数据元素就像一条线一样串联在一起,但是它的每一个数据元素的地址并不一定是连续的


了解到顺序表是线性表的一种,接下来我们进入正题,开始正式学习顺序表。


1.顺序表的概念与结构

顺序表的概念:顺序表是一段按照连续的内存地址将数据元素依次存储的数据结构。一般情况下,它的底层逻辑是数组。也就是说,顺序表的每个元素的内存地址是连续的



顺序表和数组的区别:虽然顺序表的底层结构是数组,但是我们在实现顺序表的过程中,对数组进行了封装,在数组的基础上增加了对它的一些方法,例如增删查改等操作


2.顺序表的分类

       顺序表可以分为静态顺序表动态顺序表。顾名思义,静态顺序表的大小是固定不变的。它的结构定义如下:

#define N 10
 
typedef int SLDataType;
 
//静态顺序表
typedef struct SeqList
{
    SLDataType arr[N];//固定大小的数组
    int size;//有效数据的个数
}SL;

显然,这种结构是有缺陷的。当我们需要存放的数据很多时,它的内存大小是不够的。当存放的数据过少时,又会造成空间的浪费。所以,就有了动态顺序表。动态顺序表的内存大小可以根据数据的数量发生改变。它的结构定义如下:

typedef int SLDataType;
 
//动态顺序表
typedef struct SeqList
{
    SLDataType* arr;//定义起始指针,后续动态开辟内存空间
    int size;//有效数据的个数
    int capacity;//数组的空间大小
}SL;

由于动态顺序表强大的灵活性和实用性,我们平时所谈到的顺序表一般都指的是动态顺序表。接下来我们在以上结构的基础上,一一实现动态顺序表的基本功能。

3.顺序表的实现

3.1 结构定义及方法的声明

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
 
typedef int SLDataType;
 
//动态顺序表
typedef struct SeqList
{
    SLDataType* arr;//定义起始指针,后续动态开辟内存空间
    int size;//有效数据的个数
    int capacity;//数组的空间大小
}SL;
 
//初始化
void SLInit(SL* ps);
 
//销毁
void SLDestroy(SL* ps);
 
//打印顺序表
void SLPrint(SL* ps);
 
//检查空间大小,不够则增容
void SLCheckCapacity(SL* ps);
 
//尾插
void SLPushBack(SL* ps, SLDataType n);
 
//头插
void SLPushFront(SL* ps, SLDataType n);
 
//尾删
void SLPopBack(SL* ps);
 
//头删
void SLPopFront(SL* ps);
 
//指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType n);
 
//指定位置删除数据
void SLErase(SL* ps, int pos);
 
//查找
void SLFind(SL* ps, SLDataType n);

以上就是关于顺序表的定义和一些方法的的声明。接下来,我们尝试一一实现这些方法。


3.2 方法的实现

3.2.1 初始化

       初始化时,我们将结构体赋一个初值就可以。代码如下:

//初始化
void SLInit(SL* ps)
{
    assert(ps);//断言一下,确保传入的不是空指针
    ps->arr = NULL;
    ps->capacity = ps->size = 0;
}

初始情况下,arr是一个空指针,结构的空间大小和数据个数都为0。


3.2.2 销毁

       销毁顺序表时,我们将arr的内存释放掉,然后将空间大小和数据个数调整为0就好了。代码如下:

//销毁
void SLDestroy(SL* ps)
{
    assert(ps);//防止传空指针
    if (ps->arr != NULL)//防止多次释放
    {
        free(ps->arr);
        ps->arr = NULL;
    }
    ps->capacity = ps->size = 0;
}

3.2.3 打印顺序表

//打印顺序表
void SLPrint(SL* ps)
{
    assert(ps);//防止传空指针
    for (int i = 0; i < ps->size; i++)//遍历打印
    {
        printf("%d ", ps->arr[i]);
    }
    printf("\n");
}

3.2.4 检查空间大小,不够则增容

      在我们插入数据的时候,数据的总数有可能会超出顺序表的空间大小,此时我们就需要检查空间大小,如果不够就需要增容。我们将增容封装为一个函数来实现


//检查空间大小,不够则增容
void SLCheckCapacity(SL* ps)
{
    assert(ps);
    if (ps->capacity == ps->size)//空间大小与数据个数相等则说明空间已满
    {
        int NewCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;//设定一个新空间大小,第一次增容时大小为4,之后每次以2倍的形式增容
        SLDataType* tmp = (SLDataType*)realloc(ps->arr, NewCapacity * sizeof(SLDataType));//防止内存丢失,创建局部变量暂时接收起始地址
        if (tmp == NULL)//内存开辟失败退出程序
        {
            perror("realloc");
            exit(1);
        }
        ps->arr = tmp;//将调整好的内存赋值给arr
        ps->capacity = NewCapacity;
    }
}

3.2.5 尾插

//尾插
void SLPushBack(SL* ps, SLDataType n)
{
    assert(ps);
    SLCheckCapacity(ps);//检查空间大小
    ps->arr[ps->size++] = n;//在下标为size的位置插入元素,然后size自增
}

3.2.6 头插

       在头插的过程中,我们需要先将所有的数据全部后移一位,然后在第一个位置插入数据。



代码如下:

//头插
void SLPushFront(SL* ps, SLDataType n)
{
    assert(ps);
    SLCheckCapacity(ps);//检查空间大小
    for (int i = ps->size; i > 0; i--)
    {
        ps->arr[i] = ps->arr[i - 1];//所有元素后移一位
    }
    ps->arr[0] = n;//第一个位置插入数据
    ps->size++;//元素个数加1
}

3.2.7 尾删

//尾删
void SLPopBack(SL* ps)
{
    assert(ps);
    assert(ps->size);//若数据为空,则不能删除
    ps->size--;//size自减,则最后一个元素无法被访问到,相当于删除了最后一个元素
}

这里只需要将size自减,使得最后一个元素无法被访问,相当于完成了删除操作。


3.2.8 头删

       头删时,我们将第一个元素之后的所有元素向前移动一位即可。代码如下:

//头删
void SLPopFront(SL* ps)
{
    assert(ps && ps->size);//合并两个断言语句
    for (int i = 0; i < ps->size - 1; i++)
    {
        ps->arr[i] = ps->arr[i + 1];//整体向前移动一位,覆盖第一个元素
    }
    ps->size--;//元素个数减1
}

3.2.9 指定位置之前插入

       在我们实现指定位置插入时,需要将该位置及之后的所有元素整体向后移动一位,然后再插入元素即可。代码如下:

//指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType n)//这里的参数pos是下标
{
    assert(ps && pos >= 0 && pos <= ps->size);//确保pos在合理范围内
    SLCheckCapacity(ps);
    for (int i = ps->size; i > pos; i--)
    {
        ps->arr[i] = ps->arr[i - 1];//将pos位置后的元素整体向后移动一位
    }
    ps->arr[pos] = n;//插入
    ps->size++;//元素个数加1
}

3.2.10 指定位置删除

       指定位置删除时,将该位置之后的元素整体向前移动一位,覆盖该元素即可。代码如下:

//指定位置删除数据
void SLErase(SL* ps, int pos)
{
    assert(ps && ps->size && pos >= 0 && pos < ps->size);//确保pos在合理范围内
    for (int i = pos; i < ps->size - 1; i++)
    {
        ps->arr[i] = ps->arr[i + 1];//pos之后的元素整体向前移动一位,覆盖pos位置元素
    }
    ps->size--;//元素个数减1
}

3.2.11 查找

       查找元素时,我们只需要遍历顺序表,找到符合的元素即可。

//查找
void SLFind(SL* ps, SLDataType n)
{
    assert(ps);
    for (int i = 0; i < ps->size; i++)//遍历顺序表
    {
        if (ps->arr[i] == n)
        {
            return i;//匹配成功则返回对应下标
        }
    }
    return -1;//找不到返回-1
}

4.程序全部代码

       程序全部代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
 
typedef int SLDataType;
 
//动态顺序表
typedef struct SeqList
{
    SLDataType* arr;//定义起始指针,后续动态开辟内存空间
    int size;//有效数据的个数
    int capacity;//数组的空间大小
}SL;
 
//初始化
void SLInit(SL* ps);
 
//销毁
void SLDestroy(SL* ps);
 
//打印顺序表
void SLPrint(SL* ps);
 
//检查空间大小,不够则增容
void SLCheckCapacity(SL* ps);
 
//尾插
void SLPushBack(SL* ps, SLDataType n);
 
//头插
void SLPushFront(SL* ps, SLDataType n);
 
//尾删
void SLPopBack(SL* ps);
 
//头删
void SLPopFront(SL* ps);
 
//指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType n);
 
//指定位置删除数据
void SLErase(SL* ps, int pos);
 
//查找
void SLFind(SL* ps, SLDataType n);
 
//初始化
void SLInit(SL* ps)
{
    assert(ps);//断言一下,确保传入的不是空指针
    ps->arr = NULL;
    ps->capacity = ps->size = 0;
}
 
//销毁
void SLDestroy(SL* ps)
{
    assert(ps);//防止传空指针
    if (ps->arr != NULL)//防止多次释放
    {
        free(ps->arr);
        ps->arr = NULL;
    }
    ps->capacity = ps->size = 0;
}
 
//打印顺序表
void SLPrint(SL* ps)
{
    assert(ps);
    for (int i = 0; i < ps->size; i++)
    {
        printf("%d ", ps->arr[i]);
    }
    printf("\n");
}
 
//检查空间大小,不够则增容
void SLCheckCapacity(SL* ps)
{
    assert(ps);
    if (ps->capacity == ps->size)//空间大小与数据个数相等则说明空间已满
    {
        int NewCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;//设定一个新空间大小,第一次增容时大小为4,之后每次以2倍的形式增容
        SLDataType* tmp = (SLDataType*)realloc(ps->arr, NewCapacity * sizeof(SLDataType));//防止内存丢失,创建局部变量暂时接收起始地址
        if (tmp == NULL)//内存开辟失败退出程序
        {
            perror("realloc");
            exit(1);
        }
        ps->arr = tmp;//将调整好的内存赋值给arr
        ps->capacity = NewCapacity;
    }
}
 
//尾插
void SLPushBack(SL* ps, SLDataType n)
{
    assert(ps);
    SLCheckCapacity(ps);//检查空间大小
    ps->arr[ps->size++] = n;//在下标为size的位置插入元素,然后size自增
}
 
//头插
void SLPushFront(SL* ps, SLDataType n)
{
    assert(ps);
    SLCheckCapacity(ps);//检查空间大小
    for (int i = ps->size; i > 0; i--)
    {
        ps->arr[i] = ps->arr[i - 1];//所有元素后移一位
    }
    ps->arr[0] = n;//第一个位置插入数据
    ps->size++;//元素个数加1
}
 
//尾删
void SLPopBack(SL* ps)
{
    assert(ps);
    assert(ps->size);//若数据为空,则不能删除
    ps->size--;//size自减,则最后一个元素无法被访问到,相当于删除了最后一个元素
}
 
//头删
void SLPopFront(SL* ps)
{
    assert(ps && ps->size);//合并两个断言语句
    for (int i = 0; i < ps->size - 1; i++)
    {
        ps->arr[i] = ps->arr[i + 1];//整体向前移动一位,覆盖第一个元素
    }
    ps->size--;//元素个数减1
}
 
//指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType n)//这里的参数pos是下标
{
    assert(ps && pos >= 0 && pos <= ps->size);//确保pos在合理范围内
    SLCheckCapacity(ps);
    for (int i = ps->size; i > pos; i--)
    {
        ps->arr[i] = ps->arr[i - 1];//将pos位置后的元素整体向后移动一位
    }
    ps->arr[pos] = n;//插入
    ps->size++;//元素个数加1
}
 
//指定位置删除数据
void SLErase(SL* ps, int pos)
{
    assert(ps && ps->size && pos >= 0 && pos < ps->size);//确保pos在合理范围内
    for (int i = pos; i < ps->size - 1; i++)
    {
        ps->arr[i] = ps->arr[i + 1];//pos之后的元素整体向前移动一位,覆盖pos位置元素
    }
    ps->size--;//元素个数减1
}
 
//查找
void SLFind(SL* ps, SLDataType n)
{
    assert(ps);
    for (int i = 0; i < ps->size; i++)//遍历顺序表
    {
        if (ps->arr[i] == n)
        {
            return i;//匹配成功则返回对应下标
        }
    }
    return -1;//找不到返回-1
}

总结

       以上就是我们顺序表的概念及功能实现。不难发现,它的许多方法都需要遍历数组,时间复杂度为O(N),运行效率不是很高。之后博主将会介绍链表的相关知识和功能,他会弥补顺序表的一些不足。如果你觉得博主讲的还不错,就请留下一个小小的赞在走哦,感谢大家的支持❤❤❤

相关文章
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
631 1
|
存储 算法 搜索推荐
【趣学C语言和数据结构100例】91-95
本文涵盖多个经典算法问题的C语言实现,包括堆排序、归并排序、从长整型变量中提取偶数位数、工人信息排序及无向图是否为树的判断。通过这些问题,读者可以深入了解排序算法、数据处理方法和图论基础知识,提升编程能力和算法理解。
195 4
|
7月前
|
存储 安全 Java
Java 集合面试题从数据结构到 HashMap 源码剖析详解及长尾考点梳理
本文深入解析Java集合框架,涵盖基础概念、常见集合类型及HashMap的底层数据结构与源码实现。从Collection、Map到Iterator接口,逐一剖析其特性与应用场景。重点解读HashMap在JDK1.7与1.8中的数据结构演变,包括数组+链表+红黑树优化,以及put方法和扩容机制的实现细节。结合订单管理与用户权限管理等实际案例,展示集合框架的应用价值,助你全面掌握相关知识,轻松应对面试与开发需求。
375 3
|
10月前
|
存储 自然语言处理 数据库
【数据结构进阶】AVL树深度剖析 + 实现(附源码)
在深入探讨了AVL树的原理和实现后,我们不难发现,这种数据结构不仅优雅地解决了传统二叉搜索树可能面临的性能退化问题,还通过其独特的平衡机制,确保了在任何情况下都能提供稳定且高效的查找、插入和删除操作。
802 19
|
10月前
|
数据库 C++
【数据结构进阶】红黑树超详解 + 实现(附源码)
本文深入探讨了红黑树的实现原理与特性。红黑树是一种自平衡二叉搜索树,通过节点着色(红/黑)和特定规则,确保树的高度接近平衡,从而实现高效的插入、删除和查找操作。相比AVL树,红黑树允许一定程度的不平衡,减少了旋转调整次数,提升了动态操作性能。文章详细解析了红黑树的性质、插入时的平衡调整(变色与旋转)、查找逻辑以及合法性检查,并提供了完整的C++代码实现。红黑树在操作系统和数据库中广泛应用,其设计兼顾效率与复杂性的平衡。
2205 3
|
11月前
|
定位技术 C语言
c语言及数据结构实现简单贪吃蛇小游戏
c语言及数据结构实现简单贪吃蛇小游戏
|
12月前
|
搜索推荐 C语言
数据结构(C语言)之对归并排序的介绍与理解
归并排序是一种基于分治策略的排序算法,通过递归将数组不断分割为子数组,直到每个子数组仅剩一个元素,再逐步合并这些有序的子数组以得到最终的有序数组。递归版本中,每次分割区间为[left, mid]和[mid+1, right],确保每两个区间内数据有序后进行合并。非递归版本则通过逐步增加gap值(初始为1),先对单个元素排序,再逐步扩大到更大的区间进行合并,直至整个数组有序。归并排序的时间复杂度为O(n*logn),空间复杂度为O(n),且具有稳定性,适用于普通排序及大文件排序场景。
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】顺序表的基本运算(头歌实践教学平台习题)【合集】
本文档介绍了线性表的基本运算任务,涵盖顺序表和链表的初始化、销毁、判定是否为空、求长度、输出、查找元素、插入和删除元素等内容。通过C++代码示例详细展示了每一步骤的具体实现方法,并提供了测试说明和通关代码。 主要内容包括: - **任务描述**:实现顺序表的基本运算。 - **相关知识**:介绍线性表的基本概念及操作,如初始化、销毁、判定是否为空表等。 - **具体操作**:详述顺序表和链表的初始化、求长度、输出、查找、插入和删除元素的方法,并附有代码示例。 - **测试说明**:提供测试输入和预期输出,确保代码正确性。 - **通关代码**:给出完整的C++代码实现,帮助完成任务。 文档
407 5
|
数据库
数据结构中二叉树,哈希表,顺序表,链表的比较补充
二叉搜索树,哈希表,顺序表,链表的特点的比较
数据结构中二叉树,哈希表,顺序表,链表的比较补充
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
415 5