数算部分第二节——顺序表(C语言实现+思路分析+源码分析+运行)

简介: 初始化函数,简而言之,我们想要它能够将我的这个顺序表初始化。

目录


本节我们将介绍:


顺序表的有关概念


顺序表的特点


顺序表的代码实现:


编译环境:gcc;编辑器:vscode


(1)创建3个文件:SeqList.h  SeqList.c  mock.c  


(2)创建节点


(3)具体实现:


1、初始化列表


void SeqListInit(SeqList* pq);  


//接口1:初始化列表(函数)


2、销毁列表


void SeqListDestory(SeqList* pq);


//接口2: 用于销毁列表


3、打印列表


void SeqListPrint(SeqList* pq);  


//接口3:用于打印列表


4、计算容量函数


void SeqCheckCapacity(SeqList* pq);


//接口4:用于计算列表的容量


5、尾插


void SeqListPushBack(SeqList* pq, SeqDataType x);


//接口5:用于在顺序表的尾部增加数据


6、头插


void SeqListPushFront(SeqList* pq, SeqDataType x);  


//接口6:用于在顺序表的前面增加数据


7、尾删


void SeqListPopBack(SeqList* pq);  


//接口7:用于尾删


8、头删


void SeqListPopFront(SeqList* pq);    


//接口8:用于头删


9、查找


int SeqListFind(SeqList* pq, SeqDataType x);        


//接口9:查找某个元素


10、插入(任意位置)


void SeqListInsert(SeqList* pq, int pos, SeqDataType x);


//接口10:在pos的后面插入一个数据


11、删除


void SeqListErase(SeqList* pq, int pos);  


//接口11:删除pos位置的数据


12、修改


void SeqListModify(SeqList* pq, int pos, SeqDataType x);


//接口12:将在pos位置的数据修改为x


顺序表的有关概念

简而言之,就是用一段地址连续的存储单元依次存储数据的线性结构。


如图:

微信图片_20221208155119.png



简而言之一句话,类比数组就行,和数组的存储方式几乎一模一样。


顺序表的特点:

1、空间连续;



2、缺点:在中间或前面部分的插入删除时间复杂度为O(n),增容的代价比较大;


3、优点:支持随机访问;更适合频繁访问第n个元素的场景。


顺序表的代码实现:

好,废话不多说,我们来开始模拟实现顺序表(C语言版)


我们将会以项目工程和接口的形式来完成顺序表的实现。


编译环境:gcc;编辑器:vscode

很多童鞋说VS太大,用的不多,那好,我今天就用vscode来为大家展示(当然vscode的优势不在这里)


前提是大家要会vscode的多文件编译。


(1)创建3个文件:SeqList.h  SeqList.c  mock.c  

     


我们接下来的操作,会在这三个文件当中去切换。


当然,我会为大家标注出切换到何处了。


好,我们正式开始。


(2)创建节点

实际上就是创建一个结构体。


首先,在SeqList.h中:


//SeqList.h
#pragma once//防止头文件被重复引用
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SeqDataType;//将int 重命名一下,这样我们以后如果存储的不是int类型,
                          就可以直接在这里改一下就可以了
typedef struct SeqList  //创建一个结构体,作为顺序表的一个节点
{
    SeqDataType* a;     //一个指针(或者叫数组),用来存放数据
    size_t capacity;    //顺序表的总容量
    size_t size;        //顺序表的总大小
}SeqList;              //同样的道理,我们重命名一下,这样以后的书写中,就不用带上struct了


视图效果:


那么接下来,我们将依次实现下面这些接口:


这些接口分别是:


增、删、查、改、打印、初始化、销毁等等


概览一下:



具体分析如下:

//SeqList.h
#pragma once
typedef int SeqDataType;
typedef struct SeaqList
{
    int* a;
    int capacity;
    int size;
}SeaqList;
void SeqListInit(SeqList* pq);     //接口1:用于初始化列表
void SeqListDestory(SeqList* pq);  //接口2:用于销毁列表
void SeqListPrint(SeqList* pq);    //接口3:用于打印列表
void SeqCheckCapacity(SeqList* pq);//接口4:用于计算列表的容量,判断是否需要增容
void SeqListPushBack(SeqList* pq, SeqDataType x); //接口5:用于在顺序表的尾部增加数据
void SeqListPushFront(SeqList* pq, SeqDataType x);  //接口6:用于在顺序表的前面增加数据
void SeqListPopBack(SeqList* pq);                   //接口7:用于尾删
void SeqListPopFront(SeqList* pq);                  //接口8:用于头删
int SeqListFind(SeqList* pq, SeqDataType x);        //接口9:查找某个元素
void SeqListInsert(SeqList* pq, int pos, SeqDataType x);  //接口10:在pos的后面插入一个数据
void SeqListErase(SeqList* pq, int pos);   //接口11:删除在pos位置的一个数据
void SeqListModify(SeqList* pq, int pos, SeqDataType x);  //接口12:将在pos位置的数据修改为x



别着急,接下来,我们将一个一个实现。


(3)具体实现:

1、初始化列表

void SeqListInit(SeqList* pq);  

//接口1:初始化列表(函数)

初始化函数,简而言之,我们想要它能够将我的这个顺序表初始化(就什么数据也不给,最最开始的时候)


具体的作用见下:

//SeqList.c
#include "SeqList.h"
void SeqListInit(SeqList* pq)
{
    assert(pq);          //断言一下,防止pq本身是个空指针
    pq->a = NULL;        //将a置为空
    pq->capacity = pq->size = 0;   //将容量和大小都置为0
}


接下来,


2、销毁列表

void SeqListDestory(SeqList* pq);

//接口2: 用于销毁列表

因为我们的内存空间是动态开辟的,所以用完以后,我们需要手动销毁


我们想用它将整个顺序表销毁,它是这样的:



具体分析见下:

//SeqList.c
void SeqListDestory(SeqList* pq)
{
    assert(pq);              //同样的道理,断延一下,防止pq为空
    free(pq);                //将pq  free掉。
                               (后面在我们创建时会知道,pq实际上是动态开辟出来的)
    pq->a = NULL;           //将a弄为空
    pq->capacity = pq->size = 0;   //容量和大小都变成0
}


3、打印列表

void SeqListPrint(SeqList* pq);  

//接口3:用于打印列表

很简单,就是将列表中的数据打印出来。(假设都是整形)


这个还是比较容易理解的,就是一个for循环搞定的事情:

//SeqList.c
void SeqListPrint(SeqList* pq)
{
    assert(pq);
    for(int i =0;i < pq->size;i++)
    {
        printf("%d ",pq->a[i]);
    }
    printf("\n");           //为了使下一次打印的时候能够换行
}



来整体感受一下代码之美:

微信图片_20221208155954.png


我们继续


4、计算容量函数

void SeqCheckCapacity(SeqList* pq);

//接口4:用于计算列表的容量

检测容量,如果不够要扩容(假如我们以二倍扩容)



解析如下:

//SeqList.c
void SeqCheckCapacity(SeqList* pq)
{
    assert(pq);                   //断延一下
    if(pq->size == pq->capacity)  //判断,如果二者相等,问你就要进行扩容
    {
        int NewCapacity = pq->capacity == 0 ? 4 : pq->capacity*2;   
        //如果原来的容量为0,那么就规定增至4,否则,扩容至原容量的二倍
        SeqDataType* NewA = (SeqDataType*)realloc(pq->a, sizeof(SeqList)*NewCapacity);
        //动态开辟一块这么大的空间
        if(NewA == NULL)
        {
            printf("realloc fail\n");
            exit(-1);
        }//对是否开辟成功进行检测
        pq->a = NewA;       //将新的地址赋给a
        pq->capacity = NewCapacity;  //新的容量赋给capacity
    }
}


那么如果我们有了上面的接口,实现插入就是比较简单的了。


来看尾插:


5、尾插

void SeqListPushBack(SeqList* pq, SeqDataType x);

//接口5:用于在顺序表的尾部增加数据

//SeqList.c
void SeqListPushBack(SeqList* pq, SeqDataType x)
{
    assert(pq);             //断延
    SeqCheckCapacity(pq);   //检测容量是否够我插入的,不够则要自动扩容 
    pq->a[pq->size++] = x;  //在a[pq->size]的位置插入x,插入完之后size++
}



继续:


6、头插

void SeqListPushFront(SeqList* pq, SeqDataType x);  

//接口6:用于在顺序表的前面增加数据

//SeqList.c
void SeqListPushFront(SeqList* pq, SeqDataType x)
{
    assert(pq);                    //断延
    SeqCheckCapacity(pq);          //容量检测,容量不够要扩容
    int end = pq->size;            //标记size位置
    while(end)                     //将顺序表中所有元素循环右移一位
    {
        pq->a[end] = pq->a[end-1];
        end--;
    }
    pq->a[0] = x;                 //把x的值给第一位
    pq->size++;                   //值加一
}



那么接下来,我们可以来玩玩试试看了。


我们在mock.c中来去创建一个这样一个顺序表:


(就像这样)

微信图片_20221208160240.png



那么我们让程序运行,得到如下的结果:


可以看到,我们成功了。它按照我们的方式,打印了我们所需要的数据。


好,我们继续:


7、尾删

void SeqListPopBack(SeqList* pq);  

//接口7:用于尾删

//SeqList.c
void SeqListPopBack(SeqList* pq)
{
    assert(pq);                    //断延
    assert(pq->size>0);            //断延    
    pq->size--;                    //size减一
}


这个其实比较简单。


下一个:


8、头删

void SeqListPopFront(SeqList* pq);    

//接口8:用于头删

//SeqList.c
void SeqListPopFront(SeqList* pq)    //头删
{ 
    assert(pq);                      //断延
    assert(pq->size > 0);            //断延
    for(int i =0;i<pq->size -1;i++)  //将所有后面的元素依次前移
    {
        pq->a[i] = pq->a[i+1];
    }
    pq->size--;                     //size减1
}


继续,


9、查找

下面的是查找一个元素:

 
         

int SeqListFind(SeqList* pq, SeqDataType x);        

//接口9:查找某个元素

如果找到我们想要的元素,那么我们就返回这个元素所在的下标。否则,返回-1。

//SeqList.c
int SeqListFind(SeqList* pq, SeqDataType x)
{
    assert(pq);                          //断延
    int end = 0;                         //标记一个位置
    while(end<pq->size)                  
    {   
        if(pq->a[end]==x)return end;     //如果找到,则返回下标
        else end++;
    }
    return -1;                         //如果没找到,则返回-1
}


10、插入(任意位置)

在pos元素后面插入一个元素x:


void SeqListInsert(SeqList* pq, int pos, SeqDataType x);

//接口10:在pos的后面插入一个数据

void SeqListInsert(SeqList* pq, int pos, SeqDataType x)
{
    assert(pq);
    assert(pos < pq->size);              //断延
    SeqCheckCapacity(pq);                //检测容量大小,是否需要扩容
    int end = pq->size - 1;              //找到这个列表中最后一个元素的下标
    while(end>=pos)                     
    {                                    
        pq->a[end+1] = pq->a[end];      //将在pos前面的元素依次左移
        end--;                          
    }
    pq->a[pos] = x;                     //将在pos的位置赋值给x
    pq->size++;
}


11、删除

删除下标为pos的元素:


void SeqListErase(SeqList* pq, int pos);  

//接口11:删除pos位置的数据


void SeqListErase(SeqList* pq, int pos)
{
    assert(pq);      
    assert(pos < pq->size && pos > 0);             //两次断延
    int del = pos;                                 //标记pos位置
    while(del < pq->size - 1)                      
    {
        pq->a[del] =pq->a[del+1];                  //将pos后面的元素依次左移
        del++;
    }
    pq->size--;                                   //size减1
}


12、修改

void SeqListModify(SeqList* pq, int pos, SeqDataType x);

//接口12:将在pos位置的数据修改为x


void SeqListModify(SeqList* pq, int pos, SeqDataType x)
{
    assert(pq);
    assert(pq->size>pos);                 //两次断延
    pq->a[pos] = x;                       //直接将下标为pos的元素改为x
}

好,现在我们就将所有的接口全部写完了。


我们再来玩玩看:


在mock.c中打入:


运行结果:


我们可以看到,我们成功了。


目录
相关文章
|
4月前
|
前端开发 C语言
C语言06-HelloWorld执行流程分析
C语言06-HelloWorld执行流程分析
C语言06-HelloWorld执行流程分析
|
3月前
|
存储 C语言
【C语言】基础刷题训练4(含全面分析和代码改进示例)
【C语言】基础刷题训练4(含全面分析和代码改进示例)
|
8天前
|
存储 C语言
【数据结构】顺序表(c语言实现)(附源码)
本文介绍了线性表和顺序表的基本概念及其实现。线性表是一种有限序列,常见的线性表有顺序表、链表、栈、队列等。顺序表是一种基于连续内存地址存储数据的数据结构,其底层逻辑是数组。文章详细讲解了静态顺序表和动态顺序表的区别,并重点介绍了动态顺序表的实现,包括初始化、销毁、打印、增删查改等操作。最后,文章总结了顺序表的时间复杂度和局限性,并预告了后续关于链表的内容。
32 3
|
29天前
|
存储 C语言
探索C语言数据结构:利用顺序表完成通讯录的实现
本文介绍了如何使用C语言中的顺序表数据结构实现一个简单的通讯录,包括初始化、添加、删除、查找和保存联系人信息的操作,以及自定义结构体用于存储联系人详细信息。
18 2
|
29天前
|
C语言
链式顺序表实现(C语言描述)
本文介绍了如何在C语言中实现链式顺序表,包括数据结构的定义、节点的创建、数据的插入和删除以及链表的打印和销毁。
35 2
|
29天前
|
C语言
顺序表数组法构建(C语言描述)
如何使用C语言通过数组方法构建有序顺序表,包括顺序表的创建、插入、删除和打印等。
16 2
|
2月前
|
存储 C语言 C++
数据结构基础详解(C语言) 顺序表:顺序表静态分配和动态分配增删改查基本操作的基本介绍及c语言代码实现
本文介绍了顺序表的定义及其在C/C++中的实现方法。顺序表通过连续存储空间实现线性表,使逻辑上相邻的元素在物理位置上也相邻。文章详细描述了静态分配与动态分配两种方式下的顺序表定义、初始化、插入、删除、查找等基本操作,并提供了具体代码示例。静态分配方式下顺序表的长度固定,而动态分配则可根据需求调整大小。此外,还总结了顺序表的优点,如随机访问效率高、存储密度大,以及缺点,如扩展不便和插入删除操作成本高等特点。
181 5
|
2月前
|
存储 算法 C语言
C语言手撕数据结构代码_顺序表_静态存储_动态存储
本文介绍了基于静态和动态存储的顺序表操作实现,涵盖创建、删除、插入、合并、求交集与差集、逆置及循环移动等常见操作。通过详细的C语言代码示例,展示了如何高效地处理顺序表数据结构的各种问题。
|
2月前
|
存储 监控 Linux
C语言 多路复用 select源码分析
本文详细介绍了阻塞IO与非阻塞IO的概念及其在Linux系统中的实现方式。首先阐述了常见的IO模型,包括阻塞型、非阻塞型及多路复用IO模型。阻塞IO模型会在IO请求未完成时阻塞进程,而非阻塞IO模型则允许在IO未完成时立即返回。非阻塞IO可通过设置`O_NONBLOCK`标志实现。接着介绍了多路复用IO模型,利用`select`、`poll`和`epoll`等系统调用监控多个文件描述符。`select`函数通过内核检测文件描述符是否就绪,并通知调用者。
|
4月前
|
Linux C语言 Windows
C语言文件编程-Linux环境下运行
本文介绍了在Linux环境下使用C语言进行文件编程时的两种主要接口:C标准库函数与Linux系统调用。C标准库提供了`fopen`, `fread`, `fwrite`, 和 `fclose`等函数,适用于普通文件操作;而Linux系统调用如`open`, `read`, `write`, 和 `close`则更适合处理设备文件,同时也可用于普通文件。这两种方法的主要区别在于前者使用文件指针,后者使用文件描述符。文章还给出了两个示例程序:一个使用C标准库函数实现文件复制,另一个则使用Linux系统调用完成相同任务。