1.了解顺序表
1.1 首先,我们先要了解一下顺序表是什么?
顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表中的各个元素、使得线性表中在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系,采用顺序存储结构的线性表通常称为顺序表。顺序表是将表中的结点依次存放在计算机内存中一组地址连续的存储单元中。
各位小伙伴是不是看到这一段话仿佛被当头一棍,感觉整个人都不好了。
简单来说的话,就是将数组中的各个元素按顺序依次存放在顺序表的结点中。然后顺序表会将这些结点存放在内存中的一块地址连续的空间中。
1.2 其次我们清楚顺序表的存储结构是什么?
顺序表的存储结构指的是一段地址连续的存储单元依次存储顺序表的数据元素
描述顺序表存储结构需要三个元素:
顺序表的存储结构:
//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 最后我们再来看看计算机中的地址是如何计算的?
在计算机中:
2.在顺序表中实现插入和删除
在了解了顺序表的一些基础知识之后,我们开始学习两个算法(插入算法和删除算法)
2.1 插入算法:
2.1.1 还是老样子,我们先来了解插入算法的原理是什么?
2.1.2 注意:
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 既然有插入,那么自然也有删除算法,礼尚往来嘛~
2.2.1 删除算法的原理
//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
名词解释:
3.总结顺序表的优缺点
3.1 优点
3.2缺点
好了,看到这里的时候。我们就要开始我们的老规矩准备说再见了,希望大家可以多多练习这两个算法。毕竟成功是没有捷径的。
下一期我会为大家带来线性表家族的另一个成员“链表”。
如果觉得本篇文章对你有帮助的话,麻烦各位兄弟萌点个赞帮忙转发一下~