文章目录
1. 概念
顺序表也就是线性表,是零个或多个相同数据元素的有限序列。
1.1 顺序表示
- 空表:当 n=0 时,线性表为空表,记为 φ。
- 非空表:当 n>0 时,线性表为非空表。
1.2 二元组表示
- 有序性:线性表中的元素有明确的顺序,每个元素都有唯一的前驱和后继(除第一个和最后一个元素外)。
- 有限性:线性表的长度是有限的,可以包含零个或多个元素。
- 相同数据类型:线性表中的所有元素属于相同的数据类型。
2. 顺序表的实现
2.1 管理结构体
设计一个管理结构体
该结构体用于说明数据的入口、总数据量和当前数据量。其具体定义如下:
typedef struct data {
Data_Type *Data_Enter; // 堆空间的入口地址
int Size; // 数组总量(可以存放的数据量),即数组最大的下标
int Last; // 当前的使用量,即数组当前使用的下标
} Data, *P_Data;
Data_Type *Data_Enter
:指向存储数据的堆空间的入口地址。这是一个指针,指向动态分配的数组。int Size
:数组总量,表示可以存放的数据总量,即数组的最大下标。int Last
:当前使用量,表示数组当前使用的元素数量,即数组当前的下标。
- 数据段:用于存储实际的数据元素,如1, 3, 5, 9。
- 代码段:包含顺序表的管理信息,包括数组总量(Size)和当前使用量(Last)。
- 堆:动态分配的内存空间,存放顺序表的实际数据。
图中,顺序表当前存储了4个元素(1, 3, 5, 9),其中总容量为8(可以存放8个元素),当前使用了4个元素。
3. 初始化顺序表
- 建立堆空间,来初始化
Data_Type * Data_Enter
。 - 确定堆空间的大小,初始化
int Size
。 - 初始化当前使用量为0,即
int Last
。
Data* init_data()
{
// 在堆中申请一个管理结构体的内存空间
P_Data ptr = calloc(1, sizeof(Data));
// 申请堆空间来作为顺序表的存储空间
ptr->Data_Enter = calloc(DATA_SIZE, sizeof(Data_Type));
ptr->Size = DATA_SIZE; // 设置顺序表的大小为 10
ptr->Last = 0; // 设置当前使用量为 0
return ptr; // 返回管理结构体指针
}
申请管理结构体内存空间
- 作用:在堆中申请一个
Data
结构体的内存空间,并将指针ptr
指向这块内存。 - 函数:
calloc
用于动态分配内存,参数1
表示分配1个Data
结构体的大小,sizeof(Data)
表示每个结构体的字节大小。
申请顺序表存储空间
- 作用:在堆中申请一块连续的内存空间,用于存储顺序表的实际数据。
- 参数:
DATA_SIZE
表示顺序表的最大容量,sizeof(Data_Type)
表示每个数据元素的大小。
初始化顺序表的大小和当前使用量
- 设置顺序表的大小:
ptr->Size
被初始化为DATA_SIZE
,表示顺序表可以存放的最大元素数量。 - 设置当前使用量:
ptr->Last
被初始化为0,表示当前顺序表中没有存储任何元素。
返回管理结构体指针
- 作用:返回指向已初始化管理结构体的指针,供后续操作使用。
4. 插入数据
在顺序表中插入数据时,需要传递以下参数:
- 管理结构体的地址
- 新的数据内容
无论是普通插入还是顺序插入,都需要先判断顺序表是否已满,然后根据不同的需求将数据插入到相应的位置,最后更新当前使用量。
4.1 普通插入(无序)
普通插入是指将新数据插入到顺序表的末尾位置。以下是具体实现:
int ins_data(Data* Ctrl, Data_Type new_data)
{
// 判断管理结构体指针是否为空
// 判断当前管理结构体中现有数据是否已满
if (Ctrl == NULL || Ctrl->Last == Ctrl->Size)
{
printf("当前内存已满!!!\n");
return Ctrl->Last; // 返回下标为 Size
}
// 把数据存放到顺序表的内存中
Ctrl->Data_Enter[Ctrl->Last] = new_data;
// 让顺序表的当前下标往后移动
Ctrl->Last++;
return Ctrl->Last; // 返回具体的下标值
}
-
检查是否满表:
if (Ctrl == NULL || Ctrl->Last == Ctrl->Size)
这里检查是否管理结构体指针为空或者当前使用量是否已达到最大容量。如果是,则打印内存已满的提示并返回当前使用量。
-
插入数据:
Ctrl->Data_Enter[Ctrl->Last] = new_data;
将新数据插入到当前使用量(Last)所指向的位置。
-
更新当前使用量:
Ctrl->Last++;
插入数据后,更新当前使用量。
4.2 顺序插入
顺序插入是指将新数据插入到顺序表中的适当位置,保持顺序表中的数据有序。以下是具体实现:
int ins_data(Data* Ctrl, Data_Type new_data)
{
// 判断管理结构体指针是否为空
// 判断当前管理结构体中现有数据是否已满
if (Ctrl == NULL || Ctrl->Last == Ctrl->Size)
{
printf("当前内存已满!!!\n");
return 0;
}
int tmp = 0;
if (!(Ctrl->Last == 0)) // 判断是否为空表
{
for (int i = 0; i < Ctrl->Last; i++) // 遍历整个表
{
if (new_data <= Ctrl->Data_Enter[i]) // 比较数据是否与新数据相同或大于
{
tmp = i;
for (int j = Ctrl->Last; j > i; j--)
{
Ctrl->Data_Enter[j] = Ctrl->Data_Enter[j - 1];
}
// 把数据存放到顺序表的内存中
Ctrl->Data_Enter[tmp] = new_data;
// 让顺序表的当前下标往后移动
Ctrl->Last++;
return Ctrl->Last;
}
}
}
// 把数据存放到顺序表的内存中
Ctrl->Data_Enter[Ctrl->Last] = new_data;
// 让顺序表的当前下标往后移动
Ctrl->Last++;
return Ctrl->Last; // 返回具体的下标值
}
-
检查是否满表:
if (Ctrl == NULL || Ctrl->Last == Ctrl->Size)
检查管理结构体指针是否为空或当前使用量是否已达到最大容量。如果是,则打印内存已满的提示并返回0。
-
确定插入位置:
if (!(Ctrl->Last == 0))
如果顺序表不为空,遍历顺序表找到适当的插入位置。
-
移动元素:
for (int j = Ctrl->Last; j > i; j--) { Ctrl->Data_Enter[j] = Ctrl->Data_Enter[j - 1]; }
如果找到合适的位置,需要将插入位置及之后的元素向后移动,为新元素腾出空间。
-
插入新数据:
Ctrl->Data_Enter[tmp] = new_data; Ctrl->Last++;
将新数据插入到找到的位置,并更新当前使用量。
-
插入到末尾:
Ctrl->Data_Enter[Ctrl->Last] = new_data; Ctrl->Last++;
如果新数据是最大的,直接插入到末尾。
5. 显示数据
在顺序表中显示数据时,需要注意判断表是否为空,然后遍历并显示每个数据元素。。如果顺序表为空,则需要提示用户并返回当前使用量。
int display_data(Data* Ctrl)
{
// 判断管理结构体指针是否为空
// 判断当前管理结构体中是否有数据
if (Ctrl == NULL || Ctrl->Last == 0)
{
printf("顺序表为空!!!\n");
return Ctrl->Last; // 返回下标为 0
}
// 遍历顺序表中的所有数据并显示
for (int i = 0; i < Ctrl->Last; i++)
{
printf("data: %d\n", *(Ctrl->Data_Enter + i));
}
return Ctrl->Last; // 返回当前数据量
}
检查是否为空表:
- 检查管理结构体指针是否为空或当前使用量是否为0。如果是,则打印顺序表为空的提示并返回当前使用量。
遍历并显示数据:
- 遍历顺序表中的所有数据,并通过指针访问每个元素并显示。
6. 删除数据
在顺序表中删除数据时,需要先找到要删除的数据,然后将该数据后面的所有数据往前移动一位(覆盖)。
int del_data(Data* Ctrl, int del)
{
// 判断管理结构体指针是否为空
// 判断当前管理结构体中是否为空表
if (Ctrl == NULL || Ctrl->Last == 0)
{
printf("顺序表为空!!!\n");
return 0; // 返回0表示删除失败
}
// 遍历整个表,查找要删除的数据
for (int i = 0; i < Ctrl->Last; i++)
{
if (del == Ctrl->Data_Enter[i]) // 比较数据是否相同
{
// 找到要删除的数据后,将其后面的数据往前移动
for (size_t j = i; j < Ctrl->Last - 1; j++)
{
Ctrl->Data_Enter[j] = Ctrl->Data_Enter[j + 1];
}
Ctrl->Last--; // 更新当前使用量
return 1; // 返回1表示删除成功
}
}
return 0; // 返回0表示未找到要删除的数据
}
检查是否为空表
- 检查管理结构体指针是否为空或当前使用量是否为0。如果是,则打印顺序表为空的提示并返回0,表示删除失败。
遍历查找要删除的数据
- 遍历顺序表,查找要删除的数据。如果找到,则进入删除操作。
移动数据
- 找到要删除的数据后,将其后面的所有数据往前移动一位,覆盖掉被删除的数据。然后更新当前使用量。
返回删除结果
- 删除成功后返回1。如果遍历完顺序表未找到要删除的数据,则返回0,表示未找到要删除的数据。
7. 总结
初始化部分
-
main 函数
- 调用
init_data()
函数初始化顺序表,并返回指向管理结构体的指针Ctrl
。
- 调用
-
init_data() 函数
- 在堆中申请一个管理结构体的内存空间。
- 初始化管理结构体字段。
- 返回指向管理结构体的指针。
内存布局
-
Ctrl 指针
- 指向管理结构体。
-
ptr 指针
- 指向堆中分配的管理结构体。
-
Data_Enter
- 指向数据存储区域。
-
数据存储区域
- 实际存储的数据内容。
结构体 Data
- 包含两个字段:size 和 last。
- size = 10,表示顺序表的最大容量。
- last = 4,表示当前使用量。
流程
- 从
main
函数调用init_data()
函数,返回管理结构体指针Ctrl
。 - 在
init_data()
函数中,使用calloc
申请管理结构体和数据存储区域的内存。 - 将数据存储在
Data_Enter
指向的区域。
顺序表的优缺点
优点
-
数据存储的物理位置能体现数据与数据的逻辑关系:
- 顺序表中的数据按顺序存储,物理位置能够反映逻辑顺序。
-
存储的形式是按顺序存储的,因此存储的密度非常高,存储在一片连续的内存中:
- 顺序表的数据存储在连续的内存区域,内存利用率高。
-
可以单独访问任何一个数据:
- 可以通过下标直接访问任意位置的数据,访问速度快。
缺点
-
在实现插入/删除操作的时候需要保持数据之间的逻辑关系和物理位置的关系需要大量的移动数据,操作起来很费力:
- 插入和删除操作需要移动大量数据,尤其是在表头或表中进行操作时,效率较低。
-
如果数据量较多,则需要一大片连续的内存:
- 顺序表需要一块连续的内存区域,当数据量较大时,分配连续内存可能会有困难。
-
如果数据量较多,对某一个数据进行删除/插入则需要大量的时间来进行移动其他的数据:
- 在数据量较大的顺序表中,插入或删除操作可能会导致大量数据的移动,耗时较长。
8. 代码示例
顺序表将实现以下功能:
- 从键盘获取用户输入的整型数据,存储到顺序表中。
- 在存入新数据时实现排序,把数据按从小到大的顺序存储到表中。
- 当用户输入负数时,则把该数据所对应的绝对值进行删除。
- 每次增加/删除数据操作成功后把顺序表打印到屏幕中。
设计思路
- 结构体设计:通过定义结构体来管理顺序表,包括数据存储指针、容量和当前使用量。
- 初始化函数:分配内存并初始化结构体字段。
- 插入和排序:设计插入函数,在插入新数据时保持顺序。
- 删除操作:设计删除函数,通过左移数据实现删除操作。
- 显示函数:设计显示函数,打印顺序表的当前状态。
- 用户交互:通过主函数实现与用户的交互,根据输入值执行相应操作并打印结果。
#include <stdio.h>
#include <stdlib.h>
#define DATA_SIZE 100 // 定义顺序表的最大容量
typedef int Data_Type;
// 定义管理结构体
typedef struct data {
Data_Type* Data_Enter; // 堆空间的入口地址
int Size; // 数组总量(可以存放的数据量)
int Last; // 当前的使用量
} Data, *P_Data;
// 初始化顺序表
P_Data init_data() {
P_Data ptr = (P_Data)calloc(1, sizeof(Data));
ptr->Data_Enter = (Data_Type*)calloc(DATA_SIZE, sizeof(Data_Type));
ptr->Size = DATA_SIZE;
ptr->Last = 0;
return ptr;
}
// 顺序插入数据并保持排序
int ins_data(P_Data Ctrl, Data_Type new_data) {
if (Ctrl == NULL || Ctrl->Last == Ctrl->Size) {
printf("当前内存已满!!!\n");
return 0;
}
int i = 0;
// 找到新数据应插入的位置
while (i < Ctrl->Last && Ctrl->Data_Enter[i] < new_data) {
i++;
}
// 将插入位置后的数据右移
for (int j = Ctrl->Last; j > i; j--) {
Ctrl->Data_Enter[j] = Ctrl->Data_Enter[j - 1];
}
// 插入新数据
Ctrl->Data_Enter[i] = new_data;
Ctrl->Last++;
return 1;
}
// 删除数据
int del_data(P_Data Ctrl, int del) {
if (Ctrl == NULL || Ctrl->Last == 0) {
printf("顺序表为空!!!\n");
return 0;
}
int i = 0;
// 找到要删除的数据位置
while (i < Ctrl->Last && Ctrl->Data_Enter[i] != del) {
i++;
}
if (i == Ctrl->Last) {
printf("未找到要删除的数据!!!\n");
return 0;
}
// 将删除位置后的数据左移
for (int j = i; j < Ctrl->Last - 1; j++) {
Ctrl->Data_Enter[j] = Ctrl->Data_Enter[j + 1];
}
Ctrl->Last--;
return 1;
}
// 打印顺序表
void display_data(P_Data Ctrl) {
if (Ctrl == NULL || Ctrl->Last == 0) {
printf("顺序表为空!!!\n");
return;
}
printf("顺序表:");
for (int i = 0; i < Ctrl->Last; i++) {
printf("%d ", Ctrl->Data_Enter[i]);
}
printf("\n");
}
int main() {
P_Data Ctrl = init_data();
int value;
while (1) {
printf("请输入整型数据(输入负数表示删除该数据的绝对值,输入0退出):");
scanf("%d", &value);
if (value == 0) {
break;
} else if (value > 0) {
if (ins_data(Ctrl, value)) {
printf("插入数据 %d 成功!\n", value);
}
} else {
if (del_data(Ctrl, -value)) {
printf("删除数据 %d 成功!\n", -value);
}
}
display_data(Ctrl);
}
// 释放内存
free(Ctrl->Data_Enter);
free(Ctrl);
return 0;
}