文章目录
一、静态链表
静态链表:也是线性存储结构的一种,数据全部存储在数组中(和顺序表一样);
但存储位置是随机的,数据之间通过一对一的逻辑关系通过一个整形变量(游标)维持
静态链表最少要包含两部分信息:
1、数据域:用于存储数据元素的值
2、游标:类似数组下标,表示直接后继元素所在数组中的位置
typedef struct{
int data; // 数据域
int cur; // 游标
}component;
静态链表的基础图解:
假设:第一个数据存储于数组下标为1的a[1]中;
a[1]存储的数据为1,游标为 3 可得出 直接后继元素(下一个节点) 位于a[3]的位置中;
a[3]存储的数据为2,游标为 5 可得出 直接后继元素(下一个节点) 位于a[5]的位置中;
a[5]存储的数据为3,游标为 0 可得出 直接后继元素(下一个节点) 位于a[0]的位置中
本例子到这便结束了,应该也理解静态链表的基本运行原理了,接下来便认识备用链表
例子为了好理解所以假设的很有规律,哈哈
二、备用链表
作用:回收数组中未使用或之前使用过(目前未使用)的存储空间,留后期使用;
好处:可以清楚知道数组中是否有空闲空间,以便添加新的数据;
静态链表使用数组申请的物理空间中,存有两个链表,一个连接数据,一个连接数组中未使用的空间;
静态链表中数组**(备用链表)下标为0的位置上存有数据,则证明数组已满**;
例子为了好理解所以假设的很有规律,哈哈,实际并不一定是这样
假设 表头 的开始位置
备用链表依次是:a[0],a[2],a[4];
数据链表依次是:a[1],a[3],a[5];
三、静态列表的实现
数据为初始化之前,数组中所有位置都处于空闲状态,因此都应该被链接在备用链表上,如图:
在向静态链表中 添加数据 时需提前从备用链表中摘除节点,供新数据使用
假设:摘除 a[0] 的直接后继节点 作为数据链表的 表头,因为我们已经知道了a[0] 的位置,所以操作它的后继节点比较方便;(如果想用其他节点,就遍历筛选一下备用链表,这里还是简单一点吧)
添加数据 1 的过程,如图:
a[0]:备用链表表头:数据域为空,证明备用链表还未满,游标为2:下一个空闲位置的下标;
a[1]:数据链表表头:数据域为1,游标为0:后面已经没有数据了,这是最后一个数据,可以终止了;
添加数据 2 的过程:
a[0]:备用链表表头:数据域为空,证明备用链表还未满,游标为3:下一个空闲位置的下标;
a[1]:数据链表表头:数据域为1,游标为2:下一个数据位置的下标;
a[2]:数据链表节点:数据域为2,游标为0:后面已经没有数据了,这是最后一个数据,可以终止了;
添加数据 3 的过程:
a[0]:备用链表表头:数据域为空,证明备用链表还未满,游标为4:下一个空闲位置的下标;
a[1]:数据链表表头:数据域为1,游标为2:下一个数据位置的下标;
a[2]:数据链表节点:数据域为2,游标为3,下一个数据位置的下标;
a[3]:数据链表节点:数据域为3,游标为0:后面已经没有数据了,这是最后一个数据,可以终止了
例子便举到这里了;
1、定义
// 结构体重命名
typedef struct{
int data; // 数据
int cur; // 游标
}component;
2、main函数
body变量非常重要,
int main()
{
component array[maxSize];
int body = initArr(array); //初始静态链表
printf("静态链表为:\n");
displayArr(array,body);
return 0;
}
3、初始化静态链表
返回的body已经是数据链表表头的位置了,请注意
int initArr(component *array)
{
int tempBody = 0, body = 0;
reserveArr(array);//创建了拥有 6 个空结构的数组,游标为1,2,3,4,5,0
//此时备用链表表头的游标已经改变了,请注意,返回的是数据链表表头的位置
body = mallocArr(array);
//建立数据链表表头,这里
array[body].data = 1;
array[body].cur = 0;
//声明一个变量,因为body 需要返回现在的值,不能有改变
tempBody = body;
//这里仅添加两个数据做实验
for(int i=2;i<4;i++){
int j = mallocArr(array);//从备用链表中返回下一个直接后继元素的位置
array[j].data = i;//初始化新得到的空间结点
array[tempBody].cur = j;//将新得到的结点链接到数据链表的尾部
tempBody = i;//将指向链表最后一个结点的游标后移
}
//数据列表的表尾的游标必须为0,表示没有数据了
array[tempBody].cur = 0;
return body;
}
4、创建备用链表
void reserveArr(component *array)
{
for(int i=0;i<maxSize;i++){
array[i].cur = i+1;//将每个数组分量链接到一起
array[i].data = 0;
}
array[maxSize-1].cur = 0;//链表最后一个结点的游标值为 0
}
5、空闲节点
这里非常非常重要,必须理解,理解了这里基本就没什么了:
根据备用链表表头的游标可以得出下一个空闲位置的下标;(空闲位置处的位置一直存储在备用链表表头中)
游标一直在变,因为它一直指向空闲位置;
如果位置被占了,游标 就要及时变成 被占位置处 的游标,表示重新指向 被占位置处 的下一个节点
int mallocArr(component *array)
{
//若备用链表非空,则返回分配的结点下标,否则返回 0(当分配最后一个结点时,该结点的游标值为 0)
int i = array[0].cur;
if(array[0].cur){
array[0].cur = array[i].cur;//这里是重点,第一个游标的(数据)一直在变,导致返回值 i 也一直在变
}
return i;
}
6、打印静态链表数据
此时的 body 已经是 数据链表的表头了
void displayArr(component *array,int body)
{
int tempBody = body;//tempBody准备做遍历使用
while(array[tempBody].cur){
printf("%d,%d\n",array[tempBody].data,array[tempBody].cur);
tempBody = array[tempBody].cur;
}
printf("%d,%d\n",array[tempBody].data,array[tempBody].cur);
}
7、创建好的静态链表图
四、其他操作
1、添加元素
假设将 元素 4 添加到 数据链表 的第三个位置;
(1)备用链表中摘除一个结点 用于存储元素
(2)找到数据链表中的第二个结点(添加处的前一个节点),将第二个结点的游标 设置成 摘除结点的位置(当使用mallocArr函数分配节点时会返回);
(3)摘除节点的游标 设置成 第二个结点原来的游标
//body表示数据链表的头结点在数组中的位置,add表示插入元素的位置,num表示要插入的数据
void insertArr(component *array,int body,int add,int num)
{
int tempBody = body;//结构体循环条件,并记录直接后继的游标
int headBody = 0;//记录直接前驱的游标
//找到要插入位置的上一个结点在数组中的位置
for(int i=1;i<add;i++){
// 在tempBody值改变之前保存住它的值
headBody = tempBody;
tempBody = array[tempBody].cur;
}
int insert = mallocArr(array);//申请空间,准备插入
array[insert].data = num;
// 添加结点的游标 改成 直接前驱的游标
array[insert].cur = tempBody;
// 直接前驱的游标 指向 添加结点的位置
array[headBody].cur = insert;
}
结果图:
2、删除元素
(1)存有目标元素的结点从数据链表中摘除
(2)将摘除结点添加到备用链表
删除分两种情况:
删除中间结点和删除头结点;(只是多了一步)
头结点:
(1)删除结点的游标 设置成 备用链表表头的游标
(2)将备用链表表头的游标 指向 数据链表表头的位置;
中间结点:
(1)找到删除结点的直接前驱元素,设置它的游标为 删除结点 的游标
(2)删除结点的游标 设置成 备用链表表头的游标
(3)将备用链表表头的游标 指向 删除结点的位置;
代码如下:
不要忘记判断一下删除元素 是否是 数据链表表头
//备用链表回收空间的函数,其中array为存储数据的数组,k表示未使用节点所在数组的下标
void freeArr(component* array, int k) {
array[k].cur = array[0].cur;
array[0].cur = k;
}
//删除结点函数,num表示删除结点中数据域存放的数据,函数返回新数据链表的表头位置
int deletArr(component *array,int body,int num)
{
int tempBody = body;
int del = 0;
int newbody = 0;
//找到被删除结点的位置
while(array[tempBody].data != num){
tempBody = array[tempBody].cur;
//当tempBody为0时,表示链表遍历结束,说明链表中没有存储该数据的结点
if(tempBody==0){
printf("链表中没有此数据");
return -1;
}
}
//运行到这,说明该结点存在
del = tempBody;
tempBody = body;
//删除首元结点(第一个结点),需要特殊考虑
if(del==body){
newbody = array[del].cur;
freeArr(array,del);
return newbody;
} else {
//找到该结点的上一个结点就,做删除操作
while(array[tempBody].cur != del){
tempBody = array[tempBody].cur;
}
//将被删除结点的游标直接给被删除结点的上一个结点
array[tempBody].cur = array[del].cur;
//回收被摘除结点的空间
freeArr(array,del);
return body;
}
}
3、查找元素
不要忘记最后一个结点的判断
int selectNum(component* array, int body, int num) {
//当游标值为0时,表示链表结束
while (array[body].cur != 0) {
if (array[body].data == num) {
return body;
}
body = array[body].cur;
}
//判断最后一个结点是否符合要求
if (array[body].data == num) {
return body;
}
return -1;//返回-1,表示在链表中没有找到该元素
}
4、更改元素
void amendElem(component* array, int body, int oldElem, int newElem) {
int add = selectNum(array, body, oldElem);
if (add == -1) {
printf("无更改元素\n");
return;
}
array[add].data = newElem;
}
五、全部代码
//
// Created by 醉人饮梦 on 2023/2/19.
//
#include <stdio.h>
// 定义静态链表的大小
#define maxSize 6
// 结构体重命名
typedef struct{
int data; // 数据
int cur; // 游标
}component;
//创建备用链表
void reserveArr(component *array)
{
for(int i=0;i<maxSize;i++){
array[i].cur = i+1;//将每个数组分量链接到一起
array[i].data = 0;
}
array[maxSize-1].cur = 0;//链表最后一个结点的游标值为 0
}
//提取分配空间
int mallocArr(component *array)
{
//若备用链表非空,则返回分配的结点下标,否则返回 0(当分配最后一个结点时,该结点的游标值为 0)
int i = array[0].cur;
if(array[0].cur){
array[0].cur = array[i].cur;//这里是重点,第一个游标的(数据)一直在变,导致返回值 i 也一直在变
}
return i;
}
//初始化静态链表
int initArr(component *array)
{
int tempBody = 0, body = 0;
reserveArr(array);//创建了拥有 6 个空结构的数组,游标为1,2,3,4,5,0
body = mallocArr(array);//返回一个游标,得到直接后继元素的位置
//建立首元结点
array[body].data = 1;
array[body].cur = 0;
//声明一个变量,把它当指针使,指向链表的最后一个结点,当前和首元结点重合
tempBody = body;
for(int i=2;i<4;i++){
int j = mallocArr(array);//从备用链表中返回下一个直接后继元素的位置
array[j].data = i;//初始化新得到的空间结点
array[tempBody].cur = j;//将新得到的结点链接到数据链表的尾部
tempBody = i;//将指向链表最后一个结点的指针后移
}
array[tempBody].cur = 0;//新的链表最后一个结点的游标设置为 0
return body;
}
void displayArr(component *array,int body)
{
int tempBody = body;//tempBody准备做遍历使用
while(array[tempBody].cur){
printf("%d,%d\n",array[tempBody].data,array[tempBody].cur);
tempBody = array[tempBody].cur;
}
printf("%d,%d\n",array[tempBody].data,array[tempBody].cur);
}
//body表示链表的头结点在数组中的位置,add表示插入元素的位置,num表示要插入的数据
void insertArr(component *array,int body,int add,int num)
{
int tempBody = body;//记录直接后继的游标
int endBody = 0;//记录直接前驱的游标
//找到要插入位置的上一个结点在数组中的位置
for(int i=1;i<add;i++){
// 在tempBody值改变之前保存住它的值
endBody = tempBody;
tempBody = array[tempBody].cur;
}
int insert = mallocArr(array);//申请空间,准备插入
array[insert].data = num;
// 添加结点的游标 改成 直接前驱的游标
array[insert].cur = tempBody;
// 直接前驱的游标 指向 添加结点的位置
array[endBody].cur = insert;
}
//备用链表回收空间的函数,其中array为存储数据的数组,k表示未使用节点所在数组的下标
void freeArr(component* array, int k) {
array[k].cur = array[0].cur;
array[0].cur = k;
}
//删除结点函数,num表示删除结点中数据域存放的数据,函数返回新数据链表的表头位置
int deleteArr(component *array,int body,int num)
{
int tempBody = body;
// 删除结点的位置
int del = 0;
// 新的数据链表表头位置
int newBody = 0;
//找到被删除结点的位置
while(array[tempBody].data != num){
tempBody = array[tempBody].cur;
//当tempBody为0时,表示链表遍历结束,说明链表中没有存储该数据的结点
if(tempBody==0){
printf("链表中没有此数据");
return -1;
}
}
//运行到这,说明该结点存在
del = tempBody;
tempBody = body;
//删除首元结点,需要特殊考虑
if(del==body){
newBody = array[del].cur;
freeArr(array,del);
return newBody;
} else {
//找到该结点的上一个结点就,做删除操作
while(array[tempBody].cur != del){
tempBody = array[tempBody].cur;
}
//将被删除结点的游标直接给被删除结点的上一个结点
array[tempBody].cur = array[del].cur;
//回收被摘除结点的空间
freeArr(array,del);
return body;
}
}
//在以body作为头结点的链表中查找数据域为elem的结点在数组中的位置
int selectNum(component* array, int body, int num) {
//当游标值为0时,表示链表结束
while (array[body].cur != 0) {
if (array[body].data == num) {
return body;
}
body = array[body].cur;
}
//判断最后一个结点是否符合要求
if (array[body].data == num) {
return body;
}
return -1;//返回-1,表示在链表中没有找到该元素
}
//在以body作为头结点的链表中将数据域为oldElem的结点,数据域改为newElem
void amendElem(component* array, int body, int oldElem, int newElem) {
int add = selectNum(array, body, oldElem);
if (add == -1) {
printf("无更改元素\n");
return;
}
array[add].data = newElem;
}
int main()
{
component array[maxSize];
int body = initArr(array); //初始静态链表
printf("静态链表为:\n");
displayArr(array,body);
printf("在位置3插入4:\n");
insertArr(array,body,3,4);
displayArr(array,body);
printf("删除元素2:\n");
body = deleteArr(array,body,2);
displayArr(array,body);
printf("查找元素4的位置:%d\n",selectNum(array, body, 4));
printf("更改元素1为8:\n");
amendElem(array,body,1,8);
displayArr(array,body);
return 0;
}
结语
个人记录学习所用,如用错误之处还请指出,谢谢