aaaaa
1. 线性表
1.1 概念
1.2 举例
生活中的线性表例子非常多,比如一个班级中的以学号编排的学生,一座图书馆中的以序号编排的图书、一条正常排队等候的队列、一摞从上到下堆叠的餐盘,这些都是线性表。他们的特点都是:除了首尾两个元素,其余任何一个元素前后都对应相邻的另一个元素。
注意:线性表是一种数据内部的逻辑关系,与存储形式无关线性表既可以采用连续的顺序存储,也可以采用离散的链式存储
2.顺序表
2.1 基本概念
- 顺序表:顺序存储的线性表。
- 链式表:链式存储的线性表,简称链表。
顺序存储就是将数据存储到一片连续的内存中,在C语言环境下,可以是具名的栈数组,或者是匿名的堆数组。
存储方式不仅仅只是提供数据的存储空间,而是必须要能体现数据之间的逻辑关系。当采用顺序存储的方式来存放数据时,唯一能用来表达数据间本身的逻辑关系的就是存储位置。比如队列中的两个人,小明和小花,如果小明在逻辑上排在相邻的小花的前面,那么在存储位置上也必须把小明存放在相邻的小花的前面。
2.2 基本操作
- 顺序表设计(管理结构体)
-
- 顺序表总容量
- 顺序表当前最末元素下标位置
- 顺序表指针
管理顺序表结构体:
// 管理顺序表的结构体
typedef struct
{
int capcity; // 顺序表容量
int last; // 顺序表最末元素下标
data_t *data; // 顺序表
} sequencelist;
顺序表数据类型:
// 顺序表数据类型
typedef struct data
{
int num;
char name[30];
} data_t;
初始化顺序表:
/// @brief 初始化管理顺序表的结构体
/// @param len 顺序表的容量
/// @return 初始化完成的结构体地址
sequencelist *initCntl(int len)
{
// 在堆内存中申请一个管理顺序表结构体的空间
sequencelist *cntl = (sequencelist *)calloc(1, sizeof(sequencelist));
if (cntl == NULL)
{
printf("申请管理顺序表结构体空间失败.\n");
perror("CALLOC ERROR");
return NULL;
}
// 初始化管理结构体各成员
// 顺序表入口地址以及申请存放顺序表数据空间
cntl->data = (data_t *)calloc(len, sizeof(data_t));
if (cntl->data == NULL)
{
printf("申请顺序表数据空间失败.\n");
perror("CALLOC ERROR");
return NULL;
}
// 顺序表总容量
cntl->capcity = len;
// 顺序表最末元素下标
cntl->last = -1;
return cntl;
}
将数据插入到顺序表中:
/// @brief 将数据头插到顺序表中
/// @param cntl 顺序表
/// @param newData 新数据的地址
/// @return 插入成功返回真,失败返回假
bool addCntlHead(sequencelist *cntl, data_t *newData)
{
// 判断顺序表是否已满,注意顺序表下标从零开始,容量从1开始
if (cntl->last == cntl->capcity - 1)
{
printf("顺序表已满\n");
return false;
}
// 添加数据前更新顺序表下标
cntl->last++;
// 将数据拷贝到顺序表中
// *(cntl->data+cntl->last) = *newData;
// cntl->data[cntl->last] = *newData;
memcpy(cntl->data + cntl->last, newData, sizeof(data_t));
return true;
}
/// @brief 将数据按排序顺序插入到顺序表中
/// @param cntl 顺序表
/// @param newData 新数据的地址
/// @return 插入成功返回真,失败返回假
bool addCntlOrder(sequencelist *cntl, data_t *newData)
{
// 判断顺序表是否已满,注意顺序表下标从零开始,容量从1开始
if (cntl->last == cntl->capcity - 1)
{
printf("顺序表已满.\n");
return false;
}
// 寻找合适位置插入
int tmp;
for (tmp = 0; (newData->num > cntl->data[tmp].num) &&
tmp <= cntl->last;
tmp++)
;
// 将tmp之后的数据右移,留出tmp的位置插入
for (int i = cntl->last; i >= tmp; i--)
{
memcpy(cntl->data + i + 1, cntl->data + i, sizeof(data_t));
}
// 拷贝新数据到顺序表中
memcpy(cntl->data + tmp, newData, sizeof(data_t));
// 更新顺序表下标
cntl->last++;
return true;
}
判断顺序表是否为空:
/// @brief 判断顺序表是否为空
/// @param cntl 顺序表
/// @return 为空返回真,不为空返回假
bool isCntlEmpty(sequencelist *cntl)
{
return (cntl->last == -1);
}
修改顺序表的数据:
/// @brief 修改顺序表的数据
/// @param cntl 顺序表
/// @return 成功修改返回true,失败返回false
bool reviseCntl(sequencelist* cntl)
{
int reviseData,reviseResult;
printf("请输入要修改的数据.\n");
scanf("%d",&reviseData);
reviseResult = findCntl(cntl,reviseData);
if (reviseResult==-1)
{
printf("未查找到要修改的数据.\n");
return false;
}
printf("请输入编号名字修改:\n");
//第一个的类型为int类型,所以需要取地址&,第二个的类型为char *类型,所以不用取地址符
scanf("%d%s",&cntl->data[reviseResult].num,cntl->data[reviseResult].name);
printf("修改成功.\n");
return true;
}
快速排序排序顺序表的元素:
/// @brief 快排划分支点左右两边元素
/// @param cntl 顺序表
/// @param low 参与排序元素的最小下标
/// @param high 参与排序元素的最大下标
/// @return 支点的坐标
int pivot(sequencelist *cntl, int low, int high)
{
//临时变量tmp存放选取支点的值
int tmp = cntl->data[low].num;
//high=low一次支点左右划分结束
while (high > low)
{
//循环遍历比支点小的high的元素
while (high > low && cntl->data[high].num >= tmp)
{
high--;
}
//跳出循环时high定位到比支点小的元素,交换元素把比支点小的元素放到low的位置
swapCntl(&cntl->data[low], &cntl->data[high]);
//循环遍历比支点大的low的元素
while (high > low && cntl->data[low].num <= tmp)
{
low++;
}
//跳出循环时low定位到比支点大的元素,交换元素把比支点大的元素放到high的位置
swapCntl(&cntl->data[low], &cntl->data[high]);
}
// 循环结束,此时low等于high也是tmp的位置,把支点放进去
memcpy(&cntl->data[low], &tmp, sizeof(tmp));
return high;
}
/// @brief 快速排序
/// @param cntl 顺序表
/// @param low 参与排序元素最小下标
/// @param high 参与排序元素最大下标
void quickSort(sequencelist *cntl, int low, int high)
{
//high=low递归结束条件,快速排序结束
if (high > low)
{
//调用一次支点划分并返回支点
int piv = pivot(cntl, low, high);
//对支点左边继续划分
quickSort(cntl, low, piv - 1);
//对支点右边继续划分
quickSort(cntl, piv + 1, high);
return;
}
return;
}
遍历打印顺序表的元素:
/// @brief 打印顺序表
/// @param cntl 顺序表
void display(sequencelist *cntl)
{
if (isCntlEmpty(cntl))
{
printf("顺序表为空.\n");
return;
}
for (int i = 0; i <= cntl->last; i++)
{
printf("[%d]:%s\n", cntl->data[i].num, cntl->data[i].name);
}
return;
}
查找顺序表中的元素:
/// @brief 查找数据并返回查找到的数据的下标
/// @param cntl 顺序表
/// @param findData 要查找的数据
/// @return 查找成功返回查找到的数据的下标,失败返回-1
int findCntl(sequencelist *cntl, int findData)
{
if (isCntlEmpty(cntl))
{
printf("顺序表为空.\n");
return -1;
}
for (int i = 0; i <= cntl->last; i++)
{
if (cntl->data[i].num == findData)
{
return i;
}
}
return -1;
}
/// @brief 二分法查找
/// @param cntl 顺序表
/// @param findData 待查找的数据
/// @param left
/// @param right
/// @return 数据查找成功返回其下标,失败返回-1
int twoFindCntl(sequencelist *cntl,int findData)
{
if (isCntlEmpty(cntl))
{
printf("顺序表为空.\n");
return -1;
}
quickSort(cntl,0,cntl->last);
printf("此为二分法查找,顺序表已排序好.\n");
int left = 0,right = cntl->last ;
while (right >= left)
{
int mid = left+(right-left)/2;
if (cntl->data[mid].num==findData)
{
return mid;
}
else if (cntl->data[mid].num > findData)
{
right = mid-1;
}
else if (cntl->data[mid].num < findData)
{
left = mid+1;
}
}
return -1;
}
删除顺序表的元素:
/// @brief 删除顺序表中的数据
/// @param cntl 顺序表
/// @param delData 待删除的数据
/// @return 删除成功返回真,删除失败返回假
bool delCntl(sequencelist *cntl, int delData)
{
int del;
// 查找待删除的数据,并返回其下标
del = findCntl(cntl, delData);
if (del == -1)
{
printf("未查找到数据.\n");
return false;
}
for (int i = del; i < cntl->last; i++)
{
// 将数据左移并覆盖待删除的数据
memcpy(cntl->data + i, cntl->data + i + 1, sizeof(data_t));
}
// 更新顺序表下标
cntl->last--;
return true;
}
销毁顺序表:
// 销毁顺序表
//先销毁顺序表的数据域
if (cntl->data != NULL)
{
free(cntl->data);
cntl->data = NULL; //避免野指针
}
free(cntl);
cntl = NULL; //避免野指针
判断堆内存空间是否全部释放:
ubuntu下运行代码加上运行命令valgrind ./a.out ,如命令行提示未知命令,则需要先安装valgrind
顺序表示例代码:
/**顺序表
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
// 顺序表数据类型
typedef struct data
{
int num;
char name[30];
} data_t;
// 管理顺序表的结构体
typedef struct
{
int capcity; // 顺序表容量
int last; // 顺序表最末元素下标
data_t *data; // 顺序表
} sequencelist;
/// @brief 初始化管理顺序表的结构体
/// @param len 顺序表的容量
/// @return 初始化完成的结构体地址
sequencelist *initCntl(int len)
{
// 在堆内存中申请一个管理顺序表结构体的空间
sequencelist *cntl = (sequencelist *)calloc(1, sizeof(sequencelist));
if (cntl == NULL)
{
printf("申请管理顺序表结构体空间失败.\n");
perror("CALLOC ERROR");
return NULL;
}
// 初始化管理结构体各成员
// 顺序表入口地址以及申请存放顺序表数据空间
cntl->data = (data_t *)calloc(len, sizeof(data_t));
if (cntl->data == NULL)
{
printf("申请顺序表数据空间失败.\n");
perror("CALLOC ERROR");
return NULL;
}
// 顺序表总容量
cntl->capcity = len;
// 顺序表最末元素下标
cntl->last = -1;
return cntl;
}
/// @brief 判断顺序表是否为空
/// @param cntl 顺序表
/// @return 为空返回真,不为空返回假
bool isCntlEmpty(sequencelist *cntl)
{
return (cntl->last == -1);
}
/// @brief 获得新数据
/// @return data_t类型的新数据
data_t getData()
{
data_t newData = {0};
printf("请输入编号名字.\n");
while(scanf("%d%s",&newData.num,newData.name)!=2)
{
printf("输入错误,请重新输入.\n");
while(getchar()!='\n');
continue;
}
return newData;
}
/// @brief 将数据头插到顺序表中
/// @param cntl 顺序表
/// @param newData 新数据的地址
/// @return 插入成功返回真,失败返回假
bool addCntlHead(sequencelist *cntl, data_t *newData)
{
// 判断顺序表是否已满,注意顺序表下标从零开始,容量从1开始
if (cntl->last == cntl->capcity - 1)
{
printf("顺序表已满\n");
return false;
}
// 添加数据前更新顺序表下标
cntl->last++;
// 将数据拷贝到顺序表中
// *(cntl->data+cntl->last) = *newData;
// cntl->data[cntl->last] = *newData;
memcpy(cntl->data + cntl->last, newData, sizeof(data_t));
return true;
}
/// @brief 将数据按排序顺序插入到顺序表中
/// @param cntl 顺序表
/// @param newData 新数据的地址
/// @return 插入成功返回真,失败返回假
bool addCntlOrder(sequencelist *cntl, data_t *newData)
{
// 判断顺序表是否已满,注意顺序表下标从零开始,容量从1开始
if (cntl->last == cntl->capcity - 1)
{
printf("顺序表已满.\n");
return false;
}
// 寻找合适位置插入
int tmp;
for (tmp = 0; (newData->num > cntl->data[tmp].num) &&
tmp <= cntl->last;
tmp++)
;
// 将tmp之后的数据右移,留出tmp的位置插入
for (int i = cntl->last; i >= tmp; i--)
{
memcpy(cntl->data + i + 1, cntl->data + i, sizeof(data_t));
}
// 拷贝新数据到顺序表中
memcpy(cntl->data + tmp, newData, sizeof(data_t));
// 更新顺序表下标
cntl->last++;
return true;
}
/// @brief 打印顺序表
/// @param cntl 顺序表
void display(sequencelist *cntl)
{
if (isCntlEmpty(cntl))
{
printf("顺序表为空.\n");
return;
}
for (int i = 0; i <= cntl->last; i++)
{
printf("[%d]:%s\n", cntl->data[i].num, cntl->data[i].name);
}
return;
}
/// @brief 交换顺序表的数据
/// @param a 待交换的数据
/// @param b 待交换的数据
void swapCntl(data_t *a, data_t *b)
{
data_t tmp;
memcpy(&tmp, a, sizeof(*a));
memcpy(a, b, sizeof(*b));
memcpy(b, &tmp, sizeof(tmp));
return;
}
/// @brief 快排划分支点左右两边元素
/// @param cntl 顺序表
/// @param low 参与排序元素的最小下标
/// @param high 参与排序元素的最大下标
/// @return 支点的坐标
int pivot(sequencelist *cntl, int low, int high)
{
//临时变量tmp存放选取支点的值
int tmp = cntl->data[low].num;
//high=low一次支点左右划分结束
while (high > low)
{
//循环遍历比支点小的high的元素
while (high > low && cntl->data[high].num >= tmp)
{
high--;
}
//跳出循环时high定位到比支点小的元素,交换元素把比支点小的元素放到low的位置
swapCntl(&cntl->data[low], &cntl->data[high]);
//循环遍历比支点大的low的元素
while (high > low && cntl->data[low].num <= tmp)
{
low++;
}
//跳出循环时low定位到比支点大的元素,交换元素把比支点大的元素放到high的位置
swapCntl(&cntl->data[low], &cntl->data[high]);
}
// 循环结束,此时low等于high也是tmp的位置,把支点放进去
memcpy(&cntl->data[low], &tmp, sizeof(tmp));
return high;
}
/// @brief 快速排序
/// @param cntl 顺序表
/// @param low 参与排序元素最小下标
/// @param high 参与排序元素最大下标
void quickSort(sequencelist *cntl, int low, int high)
{
//high=low递归结束条件,快速排序结束
if (high > low)
{
//调用一次支点划分并返回支点
int piv = pivot(cntl, low, high);
//对支点左边继续划分
quickSort(cntl, low, piv - 1);
//对支点右边继续划分
quickSort(cntl, piv + 1, high);
return;
}
return;
}
/// @brief 查找数据并返回查找到的数据的下标
/// @param cntl 顺序表
/// @param findData 要查找的数据
/// @return 查找成功返回查找到的数据的下标,失败返回-1
int findCntl(sequencelist *cntl, int findData)
{
if (isCntlEmpty(cntl))
{
printf("顺序表为空.\n");
return -1;
}
for (int i = 0; i <= cntl->last; i++)
{
if (cntl->data[i].num == findData)
{
return i;
}
}
return -1;
}
/// @brief 二分法查找
/// @param cntl 顺序表
/// @param findData 待查找的数据
/// @param left
/// @param right
/// @return 数据查找成功返回其下标,失败返回-1
int twoFindCntl(sequencelist *cntl,int findData)
{
if (isCntlEmpty(cntl))
{
printf("顺序表为空.\n");
return -1;
}
//二分法查找数据的前提是数据是有序的
quickSort(cntl,0,cntl->last);
printf("此为二分法查找,顺序表已排序好.\n");
int left = 0,right = cntl->last ;
//right和left不断靠近,最后循环遍历完整个顺序表
while (right >= left)
{
//取中间下标
int mid = left+(right-left)/2;
//判断中间元素值是否等于要查找的值
if (cntl->data[mid].num==findData)
{
return mid;
}
//如果要查找的值比中间值小,right=mid-1往中间值左边寻找
else if (cntl->data[mid].num > findData)
{
right = mid-1;
}
//如果要查找的值比中间值大,left=mid+1往中间值右边寻找
else if (cntl->data[mid].num < findData)
{
left = mid+1;
}
}
//没有找到值返回-1
return -1;
}
/// @brief 修改顺序表的数据
/// @param cntl 顺序表
/// @return 成功修改返回true,失败返回false
bool reviseCntl(sequencelist* cntl)
{
int reviseData,reviseResult;
printf("请输入要修改的数据.\n");
scanf("%d",&reviseData);
reviseResult = findCntl(cntl,reviseData);
if (reviseResult==-1)
{
printf("未查找到要修改的数据.\n");
return false;
}
printf("请输入编号名字修改:\n");
//第一个的类型为int类型,所以需要取地址&,第二个的类型为char *类型,所以不用取地址符
scanf("%d%s",&cntl->data[reviseResult].num,cntl->data[reviseResult].name);
printf("修改成功.\n");
return true;
}
/// @brief 删除顺序表中的数据
/// @param cntl 顺序表
/// @param delData 待删除的数据
/// @return 删除成功返回真,删除失败返回假
bool delCntl(sequencelist *cntl, int delData)
{
int del;
// 查找待删除的数据,并返回其下标
del = findCntl(cntl, delData);
if (del == -1)
{
printf("未查找到数据.\n");
return false;
}
for (int i = del; i < cntl->last; i++)
{
// 将数据左移并覆盖待删除的数据
memcpy(cntl->data + i, cntl->data + i + 1, sizeof(data_t));
}
// 更新顺序表下标
cntl->last--;
return true;
}
int main(int argc, char const *argv[])
{
int len, n, findData, findResult, delData, overFlag = 0, optSort;
char opt;
bool result;
printf("请输入顺序表的容量.\n");
scanf("%d", &len);
// 顺序表初始化
sequencelist *cntl = initCntl(len);
if (cntl != NULL)
{
printf("顺序表初始化成功.\n");
}
while (getchar() != '\n');
while (1)
{
printf("请输入选择:\n");
printf("i.添加数据\ts.排序顺序表\td.显示顺序表\tf.查找数据\tg.修改数据\tx.删除数据\tt.退出\n");
scanf("%c", &opt);
switch (opt)
{
case 'i':
printf("请输入要输入多少个数据.\n");
scanf("%d", &n);
printf("输入1.将数据头插顺序表中\t2.将数据有序插入到顺序表中\n");
scanf("%d", &optSort);
for (int i = 0; i < n; i++)
{
// 获得新数据
data_t newData = getData();
if (optSort == 1)
{
// 将数据头插添加到顺序表中
result = addCntlHead(cntl, &newData);
}
else if (optSort == 2)
{
// 将数据有序插入到顺序表中
result = addCntlOrder(cntl, &newData);
}
if (!result)
{
printf("数据添加失败.\n");
break;
}
}
break;
case 's':
if (isCntlEmpty(cntl))
{
printf("顺序表为空.\n");
break;
}
//快速排序排序顺序表
quickSort(cntl, 0, cntl->last);
break;
case 'd':
display(cntl);
break;
case 'f':
printf("请输入要查找的数据.\n");
scanf("%d", &findData);
//二分法查找
findResult = twoFindCntl(cntl,findData);
// findResult = findCntl(cntl, findData);
if (findResult == -1)
{
printf("未查找到数据.\n");
}
else
{
printf("查找成功,查找数据在顺序表中的下标位置为:[%d]->[%d]:%s\n",
findResult, cntl->data[findResult].num, cntl->data[findResult].name);
}
break;
case 'g':
result = reviseCntl(cntl);
if (result)
{
printf("修改成功.\n");
}else
{
printf("修改失败.\n");
}
break;
case 'x':
printf("请输入要删除的数据\n");
scanf("%d", &delData);
if (delCntl(cntl, delData))
{
printf("删除成功.\n");
}
else
{
printf("删除失败.\n");
}
break;
case 't':
overFlag = 1;
break;
default:
printf("输入错误,请重新输入.\n");
break;
}
//判断是否退出循环
if (overFlag)
{
break;
}
while (getchar() != '\n'); // 清空缓冲区,防止影响下一次的%c输入
}
// 销毁顺序表
free(cntl);
return 0;
}