Day1 —— 顺序表的基本操作(C语言实现)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_41221623/article/details/81631963

Day1 —— 顺序表的概念复习及顺序表的基本操作实现

  • 顺序表

    • 也就是顺序存储结构的线性表,也就是用一段地址连续的存储单元来依次存储表中的数据元素。
  • 描述顺序表需要三个属性:
  1. 存储空间的起始位置:也就是用来存储数据的数组的存储位置,这里使用动态数组的方式来存储数据元素
  2. 顺序表的初始最大存储容量:也就是数组的初始最大长度,这里用LIST_INIT_SIZE表示
  3. 顺序表的当前长度:这里用length表示
  • 综上,归纳为以下代码:
#define LIST_INIT_SIZE 1000     //顺序表初始分配量
#define LISTINCREMENT  100      //存储空间分配增量

/* 顺序表需要有统一类型的元素集合 */
typedef struct{
    //属于该类型元素的数据项
    //这里以角色id与名称为例
    int id;
    char * name;
}ElementType;

// 或 typedef int ElemType;

//顺序表结构
typedef struct{
    ElementType *datas;
    //或 ElemType *datas;

    int length;             //当前长度
    int listsize;           //当前分配的存储容量(以sizeof(ElementType)为单位)
}SeqList;

一般在实现这些操作之前,都会定义一些函数结果状态代码和一些需要的函数类型,如以下代码:

/* 函数结果状态代码 */
#define OK          1
#define ERROR       0
#define TRUE        1
#define FALSE       0
#define OVERFLOW    -2

/* 函数类型,返回值为函数结果状态代码 */
typedef int Bool;
typedef int Status;

定义好了结构,就可以定义它的基本操作并实现了,我们知道,顺序表的一般基本操作有:

  1. 初始化一个空的顺序表
  2. 顺序表的插入与删除
  3. 查找元素
  4. 判断顺序表是否为空
  5. 得到顺序表长度
  6. 顺序表的遍历
  7. 清空顺序表
  8. 销毁顺序表
  • 对于顺序表的初始化,可以用以下代码实现:

/** 初始化顺序表 */
Status InitList(SeqList * seqList)
{
    seqList->datas = (ElementType *)malloc(LIST_INIT_SIZE * sizeof(ElementType));
    if(!seqList->datas)
    {
        //存储分配失败
        exit(OVERFLOW);
    }

    seqList->length = 0;                    //初始化顺序表当前长度为0
    seqList->listsize = LIST_INIT_SIZE;     //初始存储容量(1000)

    return OK;
}
  • 接着到顺序表的插入与删除

  • 先来看插入与删除的原理:
  • 插入元素到顺序表下标为i的位置的步骤(以操作下标来插入,从0开始):
  1. 第一步,下标为i以及下标为i以后的所有数据元素后移一个单位
  2. 除非插入的位置是最后一个元素之后或者空表时插入的第一个位置,否则必须移动数据元素的位置来适应逻辑结构的改变
  3. 第二步、下标i的位置放入要插入的数据元素
  4. 插入元素后的顺序表长度变为n+1
  5. 插入元素后,最后一个元素的下标变为n
  • 代码实现如下:
/**
 * 顺序表插入
 * @param seqList   要插入数据的顺序表
 * @param index     要进行插入元素的下标位置
 * @param element   要插入的元素
 */
Status InsertElement(SeqList * seqList, int index, ElementType element)
{
    //1、验证插入后的元素空间是否超过初始分配空间listsize,若超过则增加存储空间
    //2、index的值是否合法[0,LIST_INIT_SIZE - 1]
    //3、插入的index应该在length之内
    //4、从第length - 1个下标开始,前面一个元素赋值给后面一个元素,直到index为止

    if(index < 0 || index > LIST_INIT_SIZE - 1)
    {
        printf("插入位置非法!\n");
        return ERROR;
    }
    if(index > seqList->length)
    {
        printf("插入的下标超过了能插入元素的范围!\n");
        return ERROR;
    }

    if(seqList->length >= seqList->listsize)     //当前存储空间已满,增加分配
    {
        ElementType * newbase;
        newbase = (ElementType *)realloc(seqList->datas, (seqList->listsize + LISTINCREMENT) * sizeof(ElementType));
        if(!newbase)
        {
            //存储分配失败
            exit(OVERFLOW);
        }

        seqList->datas = newbase;               //新基址
        seqList->listsize += LISTINCREMENT;     //增加当前存储容量

    }

    //元素后移
    for(int i = seqList->length - 1; i >= index; i--)
    {
        seqList->datas[i + 1] = seqList->datas[i];
    }

    //将要插入的值赋给第index个元素
    seqList->datas[index] = element;

    //顺序表总长度+1
    seqList->length++;

    return OK;
}
  • 删除顺序表中的第i个元素的步骤:

  1. 第一步,找到第i个位置的数据元素
  2. 第二步、将第i个数据元素以后的数据元素依次覆盖前一个位置
  3. 第三步、顺序表当前长度减1
  • 代码实现如下:
/**
 * 删除顺序表中指定下标的元素
 * @param seqList   要操作的顺序表
 * @param index     要删除的下标
 * @return          返回删除的元素,如果删除失败,返回NULL
 */
ElementType * DeleteElement(SeqList * seqList, int index)
{
    if(index < 0 || index > seqList->length - 1)
    {
        printf("下标越界,无法删除指定下标的元素!\n");
        return NULL;
    }

    //1、找到要删除的元素,并保存起来以便返回(保存的是已删除元素的副本)
    ElementType *e = (ElementType *)malloc(sizeof(ElementType));
    
    //通过查找元素的函数来复制一份要删除元素的副本给e
    *e = *GetElement(seqList, index);

    //2、从指定位置删除,后面一个元素赋值给前面一个元素
    for(int i = index; i < seqList->length - 1; i++)
    {
        seqList->datas[i] = seqList->datas[i + 1];
    }

    //3、顺序表的总长度-1
    seqList->length--;

    //返回被删除的元素
    return e;
}
  • 对于查找顺序表中的元素,只需先判断顺序表是否已被销毁以及是否为空和查找的位置合不合法,然后再使用下标查找到要查找的元素并返回即可。

  • 代码实现如下:
/**
 * 返回顺序表中指定下标的元素
 * @param seqList   要操作的顺序表
 * @param index     要返回元素的下标
 * @return          返回指定下标的元素,如果查找失败,返回NULL
 */
ElementType * GetElement(SeqList * seqList, int index)
{
    if(seqList->datas == NULL)
    {
        printf("顺序表已被销毁,无法查找!\n");
        return NULL;
    }
    if(IsEmpty(seqList))
    {
        printf("顺序表此时为空表,查找失败!\n");
        return NULL;
    }
    if(index < 0  || index > seqList->length - 1)
    {
        printf("下标越界,无法找到指定下标的元素!\n");
        return NULL;
    }

    ElementType *element;
    element = &seqList->datas[index];    //通过下标查找元素并将查找到的元素赋给element并返回

    //返回查找到的元素
    return element;
}
  • 得到顺序表长度,只需返回当前长度即可

  • 代码实现如下:
/** 返回顺序表的长度 */
int GetLength(SeqList * seqList)
{
    if(seqList->datas == NULL)
    {
        printf("顺序表已被销毁,无法获得当前长度!\n");
        return ERROR;
    }

    return seqList->length;
}
  • 判断顺序表是否为空,只要判断顺序表当前长度即可

  • 代码实现如下:
/** 返回顺序表是否为空 */
Bool IsEmpty(SeqList * seqList)
{
    if(seqList->datas == NULL)
    {
		exit(OVERFLOW);
    }

    return GetLength(seqList) == 0 ? TRUE : FALSE;
}
  • 做完以上操作后,我们可以遍历一下顺序表,代码实现如下:

/** 打印顺序表 */
Status PrintList(SeqList * seqList)
{

    if(seqList->datas == NULL)
    {
        printf("顺序表已被销毁,无法遍历!\n");
        return ERROR;
    }
    if(IsEmpty(seqList))
    {
        printf("Sorry,当前顺序表为空,无法遍历!\n");
        return ERROR;
    }

    int i;
    for(i = 0; i < seqList->length; i++)
    {
        printf("%-8d%-16s\n", seqList->datas[i].id, seqList->datas[i].name);
    }

    return OK;
}
  • 对于清空顺序表,只需令顺序表当前长度为0即可。

  • 代码实现如下:
/** 清空顺序表 */
Status ClearList(SeqList * seqList)
{
    if(seqList->datas == NULL)
    {
        return ERROR;
    }

    seqList->length = 0;
    printf("清空成功!\n");

    return OK;
}
  • 最后便是销毁顺序表了,只要把数组的内存free掉即可

  • 代码实现如下:
/** 销毁顺序表 */
Status DestroyList(SeqList * seqList)
{
    if(!seqList->datas)
    {
        return ERROR;
    }

    free(seqList->datas);     //释放存储空间
    seqList->datas = NULL;    //初始化指针

    printf("销毁成功!\n");

    return OK;
}
  • 使用代码实现以上操作之后,可以用一组测试数据来测试一下:

#include <stdio.h>
#include <stdlib.h>
#include "List.h"

/** 测试数据 */
ElementType datas[] = {
    {1, "关羽"},
    {2, "张飞"},
    {3, "刘备"},
    {4, "赵云"},
};

/** 测试函数 */
void testFunction();

int main()
{
    testFunction();
    return 0;
}

void testFunction()
{
    SeqList seqList;            //要操作的顺序表
    ElementType *delElement;    //用于保存被删除的元素
    ElementType *getElement;    //用于保存被查找的元素

    InitList(&seqList);         //顺序表初始化

    /** 插入测试数据进入空表 */
    for(int i = 0; i < 4; i++)
    {
        InsertElement(&seqList, i, datas[i]);
    }

    printf("初始化后\n");
    //打印顺序表
    PrintList(&seqList);

    /** 删除元素测试,这里删除下标为2的元素,即刘备 */
    delElement = DeleteElement(&seqList, 2);
    printf("\n删除后\n");
    PrintList(&seqList);
    printf("\n被删除的元素:\n");
    printf("%-8d%-16s\n", delElement->id, delElement->name);

    //使用完后释放空间
    free(delElement);

    /** 查找元素测试,这里查找删除元素后的顺序表中的下标为2的元素,即赵云 */
    getElement = GetElement(&seqList, 2);
    printf("\n被查找的元素:\n");
    printf("%-8d%-16s\n", getElement->id, getElement->name);

    /** 获取顺序表长度测试,这里删除元素后表长为3 */
    printf("\n顺序表当前长度为:%d\n", GetLength(&seqList));

    /** 顺序表清空测试 */
    printf("\n");

    printf("清空前:\n");
    PrintList(&seqList);

    //清空顺序表
    printf("\n");
    ClearList(&seqList);
    printf("\n");

    //清空后再次调用一下打印函数
    printf("清空后:\n");
    PrintList(&seqList);

    /** 顺序表销毁测试 */
    //销毁前先打印下标为0的元素,即关羽
    printf("\n销毁前:\n%-8d%-16s\n", seqList.datas[0].id, seqList.datas[0].name);
    printf("\n");

    //销毁顺序表
    DestroyList(&seqList);
    printf("\n");

    printf("销毁后:\n");

    //销毁后再次调用一下打印函数
    PrintList(&seqList);

    //销毁后再次打印下标为0的元素,即关羽
    printf("%-8d%-16s\n", seqList.datas[0].id, seqList.datas[0].name);
}
  • 测试结果:

  • 在实现这些操作的过程中,可以得出以下小结:

  • 顺序表的优点:
  1. 无须为表示表中元素之间的逻辑关系而增加额外的存储空间
  2. 可以快速地存取表中任意位置的元素
  • 顺序表的缺点:
  1. 插入和删除操作需要移动大量的元素
  2. 当线性表长度变化较大时,难以确定存储空间的容量
  3. 容易造成空间的“碎片”

猜你喜欢

转载自blog.csdn.net/qq_41221623/article/details/81631963
今日推荐