C语言-静态链表


一、静态链表

静态链表:也是线性存储结构的一种,数据全部存储在数组中(和顺序表一样);
但存储位置是随机的,数据之间通过一对一的逻辑关系通过一个整形变量(游标)维持

静态链表最少要包含两部分信息:
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;
}

结语

个人记录学习所用,如用错误之处还请指出,谢谢

  • 6
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值