一篇文章快速搞懂顺序表的插入函数和删除函数

1.了解顺序表
1.1 首先,我们先要了解一下顺序表是什么?
顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表中的各个元素、使得线性表中在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系,采用顺序存储结构的线性表通常称为顺序表。顺序表是将表中的结点依次存放在计算机内存中一组地址连续的存储单元中。
各位小伙伴是不是看到这一段话仿佛被当头一棍,感觉整个人都不好了。
一篇文章快速搞懂顺序表的插入函数和删除函数_第1张图片
简单来说的话,就是将数组中的各个元素按顺序依次存放在顺序表的结点中。然后顺序表会将这些结点存放在内存中的一块地址连续的空间中。

就像下面图片表示的那样
一篇文章快速搞懂顺序表的插入函数和删除函数_第2张图片

1.2 其次我们清楚顺序表的存储结构是什么?
顺序表的存储结构指的是一段地址连续的存储单元依次存储顺序表的数据元素

描述顺序表存储结构需要三个元素:

  1. 存储空间的起始位置:数组datas的存储位置
  2. 顺序表的最大存储容量:数组长度,MAX_SIZE
  3. 顺序表的当前长度

顺序表的存储结构:

//1.需要定义顺序表的最大存储空间
#define MAX_SIZE
//2.需要有统一类型的元素集合
typedef int ElemType;
typedef struct{
     
    int id,
    char * name;
}ElemenType;    //为元素集合起一个Element的别名
//3.定义结构
typedef struct{
     
    ElementType datas[MAX_SIZE];   //datas的最大容量为MAX_SIZE
    int length;    //顺序表的当前长度
}SeqList;    //结构的别名SeqList

1.3 最后我们再来看看计算机中的地址是如何计算的?
在计算机中:

  • 第n 个元素的内存地址 = 数组的内存地址 + 第n 个元素的下标
  • 这里还要解释一些两个名词position 和index
  1. position:位置,从1开始(就是下面图中的顺序存储数据元素);
  2. index:下标,从0开始(图中的下标)。
    一篇文章快速搞懂顺序表的插入函数和删除函数_第3张图片

2.在顺序表中实现插入和删除
在了解了顺序表的一些基础知识之后,我们开始学习两个算法(插入算法和删除算法)

2.1 插入算法:
2.1.1 还是老样子,我们先来了解插入算法的原理是什么?

  • 将数据元素a 插入到顺序表(a_1, a_2, ····, a_n)因为打不出来下标,所以用下划线代替!下标为i 的位置
  • 下标为i 及下标为i 以后的所有数据元素后移;
  • 下标i 的位置放入数据元素a。

2.1.2 注意:

  • 插入元素后的顺序表长度要为n+1;
  • 插入元素后,最后一个元素的下标变为n;
  • C 语言数组实现时,顺序表长度不能超过它的最大长度;
  • 除非i = n + 1,否则必须移动数据元素的位置来适应逻辑结构的改变。
    一篇文章快速搞懂顺序表的插入函数和删除函数_第4张图片

2.1.3 代码实现
首行注释为文件名,需要创建4个文件,默认的main.c、DataElement.h(用来定义数据元素)、SequenceList.h(定义函数)、SequenceList.c(实现函数)

//main.c
#include 
#include 
#include "DataElement.h"

//测试数据
ElementType dataArray[] = {
     
    {
     1, "巴菲特"},
    {
     2, "比尔·盖茨"},
    {
     3, "乔布斯"}
};

//测试函数
void TestSequenceList();

int main()
{
     
    TestSequenceList();     //调用测试函数
    return 0;
}

void TestSequenceList()
{
     
    SeqList seqList;    //要操作的顺序表
    InitList(&seqList, dataArray, sizeof(dataArray) / sizeof(dataArray[0]));    //初始化顺序表
    //sizeof(dataArray) / sizeof(dataArray[0]) 用sizeof 来求出dataArray 数组的整个长度,再除以单个元素的长度,就得出了数组元素的个数。
    PrintfList(&seqList);   //打印顺序表
}

注意:
因为PrintList()方法的声明的第一个参数是SeqList * 类型,所以在调用PrintList()方法时,第一个参数应该为SeqList * 结构体指针类型,因此传入参数为&seqList。

//DataElement.h
#ifndef DATAELEMENT_H_INCLUDED
#define DATAELEMENT_H_INCLUDED

 //1.定义顺序表的最大空间
 #define MAX_SIZE 255

 //2.定义数据类型
 //typedef int ElementType;
 /*
  * datas = {
     {1, ""}, {2, ""}};
  */
 typedef struct{
     
    int id;
    char * name;
 }ElementType;

 //3.定义顺序表的结构
 typedef struct{
     
    ElementType datas[MAX_SIZE];    //表明顺序表的最大空间
    int length;                     //记录顺序表的长度
 }SeqList;
#endif // DATAELEMENT_H_INCLUDED
//SequenceList.h
#ifndef SEQUENCELIST_H_INCLUDED
#define SEQUENCELIST_H_INCLUDED

//要引用的头文件
#include 
#include 
#include "DataElement.h"

/**
 *  初始化顺序表
 *  @param seqList      要初始化的顺序表
 *  @param elemArray    初始化时添加的元素内容数组
 *  @param length       初始化时添加的元素个数
 */

void InitList(SeqList * seqList, ElementType * elemArray, int length);

/**
 *  向顺序表中的index下标处插入某个元素
 *  @param seqList  要初始化的顺序表
 *  @param index    要插入的下标位置
 *  @param element  要插入的元素
 */
void InserElement(SeqList * seqList, int index, ElementType element);

/**
 *  打印seqList
 */
void PrintfList(SeqList * seqList);
#endif // SEQUENCELIST_H_INCLUDED
//SequenceList.c
#ifndef SEQUENCELIST_C_INCLUDED
#define SEQUENCELIST_C_INCLUDED

//引入头文件
#include "SequenceList.h"

/**
 *  初始化顺序表
 *  @param seqList      要初始化的顺序表
 *  @param elemArray    初始化时添加的元素内容数组
 *  @param length       初始化时添加的元素个数
 */

void InitList(SeqList * seqList, ElementType * elemArray, int length)
{
     
    if(length > MAX_SIZE)
    {
     
        printf("超出数组的最大容量,初始化失败!\n");
        return;
    }

    seqList -> length = 0;  //记得在初始化顺序表的时候,将顺序表的长度置零

    for(int i = 0; i < length; i++)
    {
     
        //每次循环在下标为i 的位置插入一个元素
        InserElement(seqList, i, elemArray[i]);
    }
}

/**
 *  向顺序表中的index下标处插入某个元素
 *  @param seqList  要初始化的顺序表
 *  @param index    要插入的下标位置
 *  @param element  要插入的元素
 */
void InserElement(SeqList * seqList, int index, ElementType element)
{
     
    //1.验证插入后的空间元素是否超过MAX_SIZE;
    //2.index 的值是否合法;
    //3.插入的index 应该在length 之内;
    //4.从第length - 1 个下标开始,前面一个元素赋值给后面一个元素
    if(seqList -> length + 1 >= MAX_SIZE)   //插入后的顺序表长度length 不能超过总长度MAX_SIZE,所以要把原来的length + 1 进行判断。
    {
     
        printf("要插入的数组空间已满!无法插入!\n");
        return;     //在输入不合法的时候,一定要记得用return 来退出程序。
    }

    if(index < 0 || index > MAX_SIZE - 1)   //下标数最小为0,最大为MAX_SIZE - 1。因为下标比长度要-1。
    {
     
        printf("只能在允许的下标范围内插入元素[0, %d]\n", MAX_SIZE - 1);
        return;
    }

    if(index > seqList -> length)   //下标不应该大于顺序表的元素个数
    {
     
        printf("插入的下标超过了数组的最大长度1,插入失败!\n");
        return;
    }

    //在c89 标准中,直接在for 循环中定义变量是不允许的;
    //在c99 标准以后就允许。
    for(int i = seqList -> length - 1; i >= index; i--)
    {
     
        seqList -> datas[i + 1] = seqList -> datas[i];
    }
    //5.将要插入的值赋值给第index 个元素。
    seqList -> datas[index] = element;
    //6.顺序表长度length + 1!!!- 一定要注意不要遗漏这条语句!
    seqList -> length++;
}

/**
 *  打印seqList
 */
void PrintfList(SeqList * seqList)
{
     
    for(int i = 0; i < seqList -> length; i++)
    {
     
        printf("%d\t%s\n", seqList -> datas[i], seqList -> datas[i].name);
    }
}
#endif // SEQUENCELIST_C_INCLUDED

2.2 既然有插入,那么自然也有删除算法,礼尚往来嘛~
一篇文章快速搞懂顺序表的插入函数和删除函数_第5张图片
2.2.1 删除算法的原理

  • 将第i 个数据元素从顺序表(a_1,a_2,···,a_n)中删除;
  • 找到第i 个位置的数据元素;
  • 将第i 个数据元素以后的数据元素依次覆盖前一个位置;
  • 顺序表当前长度减1。
    一篇文章快速搞懂顺序表的插入函数和删除函数_第6张图片
    2.2.2 代码实例
    首行注释为文件名,需要创建4个文件,默认的main.c、DataElement.h(用来定义数据元素)、SequenceList.h(定义函数)、SequenceList.c(实现函数)
//main.c
#include 
#include 
#include "DataElement.h"

//测试数据
ElementType dataArray[] = {
     
    {
     1, "巴菲特"},
    {
     2, "比尔·盖茨"},
    {
     3, "乔布斯"}
};

//测试函数
void TestSequenceList();

int main()
{
     
    TestSequenceList();     //调用测试函数
    return 0;
}

void TestSequenceList()
{
     
    SeqList seqList;    //要操作的顺序表
    ElementType *delElement;    //保存删除的元素
    InitList(&seqList, dataArray, sizeof(dataArray) / sizeof(dataArray[0]));    //初始化顺序表
    //sizeof(dataArray) / sizeof(dataArray[0]) 用sizeof 来求出dataArray 数组的整个长度,再除以单个元素的长度,就得出了数组元素的个数。
    printf("初始化后\n");
    PrintfList(&seqList);   //打印顺序表

    delElement = DeleteElement(&seqList,2);
    printf("删除后\n");
    PrintfList(&seqList);
    printf("被删除的元素:\n");
    printf("%d\t%s\n", delElement -> id, delElement -> name);
    free(delElement);   //释放分配的内存。
}
//DataElement.h
#ifndef DATAELEMENT_H_INCLUDED
#define DATAELEMENT_H_INCLUDED

 //1.定义顺序表的最大空间
 #define MAX_SIZE 255

 //2.定义数据类型
 //typedef int ElementType;
 /*
  * datas = {
     {1, ""}, {2, ""}};
  */
 typedef struct{
     
    int id;
    char * name;
 }ElementType;

 //3.定义顺序表的结构
 typedef struct{
     
    ElementType datas[MAX_SIZE];    //表明顺序表的最大空间
    int length;                     //记录顺序表的长度
 }SeqList;
#endif // DATAELEMENT_H_INCLUDED
//SequenceList.h
#ifndef SEQUENCELIST_H_INCLUDED
#define SEQUENCELIST_H_INCLUDED

//要引用的头文件
#include 
#include 
#include "DataElement.h"
//定义TURE 和FALSE 的常量
#define TRUE 1
#define FALSE 0

/**
 *  初始化顺序表
 *  @param seqList      要初始化的顺序表
 *  @param elemArray    初始化时添加的元素内容数组
 *  @param length       初始化时添加的元素个数
 */

void InitList(SeqList * seqList, ElementType * elemArray, int length);

/**
 *  向顺序表中的index下标处插入某个元素
 *  @param seqList  要初始化的顺序表
 *  @param index    要插入的下标位置
 *  @param element  要插入的元素
 */
void InserElement(SeqList * seqList, int index, ElementType element);

/**
 *  删除顺序表中指定下标的元素
 *  @param seqList  要操作的顺序表
 *  @param index    要删除的下标
 *  @return         返回删除的元素,如果删除失败,则返回NULL
 */
ElementType * DeleteElement(SeqList * seqList, int index);

/**
 *  返回顺序表中指定下标的元素
 *  @param seqList  要操作的顺序表
 *  @param index    要返回元素的下标
 *  @return         返回指定下标的元素,如果查找失败,则返回NULL
 */
ElementType * GetElement(SeqList * seqList, int index);

/**
 *  打印seqList
 */
void PrintfList(SeqList * seqList);
#endif // SEQUENCELIST_H_INCLUDED
//SequenceList.c
#ifndef SEQUENCELIST_C_INCLUDED
#define SEQUENCELIST_C_INCLUDED

//引入头文件
#include "SequenceList.h"

/**
 *  初始化顺序表
 *  @param seqList      要初始化的顺序表
 *  @param elemArray    初始化时添加的元素内容数组
 *  @param length       初始化时添加的元素个数
 */

void InitList(SeqList * seqList, ElementType * elemArray, int length)
{
     
    if(length > MAX_SIZE)
    {
     
        printf("超出数组的最大容量,初始化失败!\n");
        return;
    }

    seqList -> length = 0;  //记得在初始化顺序表的时候,将顺序表的长度置零

    for(int i = 0; i < length; i++)
    {
     
        //每次循环在下标为i 的位置插入一个元素
        InserElement(seqList, i, elemArray[i]);
    }
}

/**
 *  向顺序表中的index下标处插入某个元素
 *  @param seqList  要初始化的顺序表
 *  @param index    要插入的下标位置
 *  @param element  要插入的元素
 */
void InserElement(SeqList * seqList, int index, ElementType element)
{
     
    //1.验证插入后的空间元素是否超过MAX_SIZE;
    //2.index 的值是否合法;
    //3.插入的index 应该在length 之内;
    //4.从第length - 1 个下标开始,前面一个元素赋值给后面一个元素
    if(seqList -> length + 1 >= MAX_SIZE)   //插入后的顺序表长度length 不能超过总长度MAX_SIZE,所以要把原来的length + 1 进行判断。
    {
     
        printf("要插入的数组空间已满!无法插入!\n");
        return;     //在输入不合法的时候,一定要记得用return 来退出程序。
    }

    if(index < 0 || index > MAX_SIZE - 1)   //下标数最小为0,最大为MAX_SIZE - 1。因为下标比长度要-1。
    {
     
        printf("只能在允许的下标范围内插入元素[0, %d]\n", MAX_SIZE - 1);
        return;
    }

    if(index > seqList -> length)   //下标不应该大于顺序表的元素个数
    {
     
        printf("插入的下标超过了数组的最大长度1,插入失败!\n");
        return;
    }

    //在c89 标准中,直接在for 循环中定义变量是不允许的;
    //在c99 标准以后就允许。
    for(int i = seqList -> length - 1; i >= index; i--)
    {
     
        seqList -> datas[i + 1] = seqList -> datas[i];
    }
    //5.将要插入的值赋值给第index 个元素。
    seqList -> datas[index] = element;
    //6.顺序表长度length + 1!!!- 一定要注意不要遗漏这条语句!
    seqList -> length++;
}

/**
 *  删除顺序表中指定下标的元素
 *  @param seqList  要操作的顺序表
 *  @param index    要删除的下标
 *  @return         返回删除的元素,如果删除失败,则返回NULL(建议使用完毕后进行free,否则会造成内存泄露)
 */
ElementType * DeleteElement(SeqList * seqList, int index)
{
     
    //1.判断下标是否合法
    if(index < 0 || index > MAX_SIZE - 1)
    {
     
        printf("下标越界,无法删除指定下标的元素!\n");
        return NULL;
    }

    //2.找到要删除的元素并保存起来,以便返回(保存的是已删除的副本);
    ElementType * delElement = (ElementType*)malloc(sizeof(ElementType));
    //单独定义并调用查找函数,返回要删除元素的指针
    *delElement = *GetElement(seqList, index);

    //3.从指定位置删除,后面一个元素赋值给前面一个元素;
    for(int i = index; i > seqList -> length - 1; i++)
    {
     
        seqList -> datas[i] = seqList -> datas[i + 1];
    }
    //4.顺序表的总长度-1。
    seqList -> length--;

    return delElement;  //建议使用完毕后进行free,否则会造成内存泄露
}

/**
 *  返回顺序表中指定下标的元素
 *  @param seqList  要操作的顺序表
 *  @param index    要返回元素的下标
 *  @return         返回指定下标的元素,如果查找失败,则返回NULL
 */
ElementType * GetElement(SeqList * seqList, int index)
{
     
    //判断下标是否合法
    if(index < 0 || index > MAX_SIZE - 1)
    {
     
        printf("下标越界,无法查找指定下标的元素!\n");
        return NULL;
    }

    ElementType * element;  //要查找的元素
    element = &seqList -> datas[index];
}

/**
 *  打印seqList
 */
void PrintfList(SeqList * seqList)
{
     
    for(int i = 0; i < seqList -> length; i++)
    {
     
        printf("%d\t%s\n", seqList -> datas[i], seqList -> datas[i].name);
    }
}
#endif // SEQUENCELIST_C_INCLUDED

名词解释:

  • malloc函数:动态内存分配。
  • 内存泄露:指定的内存用完后没有释放,导致这块内存无法使用。

3.总结顺序表的优缺点
3.1 优点

  • 无需为表示表中元素之间的逻辑关系而增加额外的存储空间
  • 可以快速地存取表中任意位置的元素

3.2缺点

  • 插入和删除操作需要移动大量的元素
  • 当线性表长度变化较大时,难以确定存储空间的容量
  • 造成空间的“碎片”

好了,看到这里的时候。我们就要开始我们的老规矩准备说再见了,希望大家可以多多练习这两个算法。毕竟成功是没有捷径的。
下一期我会为大家带来线性表家族的另一个成员“链表”。
如果觉得本篇文章对你有帮助的话,麻烦各位兄弟萌点个赞帮忙转发一下~
一篇文章快速搞懂顺序表的插入函数和删除函数_第7张图片

你可能感兴趣的:(数据结构,算法,数据结构)