高级线性表——静态链表(最全静态链表解读)

写在前面:博主是一位普普通通的19届二本大学生,平时最大的爱好就是听听歌,逛逛B站。博主很喜欢的一句话花开堪折直须折,莫待无花空折枝:博主的理解是头一次为人,就应该做自己想做的事,做自己不后悔的事,做自己以后不会留有遗憾的事,做自己觉得有意义的事,不浪费这大好的青春年华。博主写博客目的是记录所学到的知识并方便自己复习,在记录知识的同时获得部分浏览量,得到更多人的认可,满足小小的成就感,同时在写博客的途中结交更多志同道合的朋友,让自己在技术的路上并不孤单。

目录:
1.静态链表的概述
2.静态链表的实现
       
静态链表的结点
       
备用链表
       
静态链表的详细实现过程
3.静态链表的代码实现
       
静态链表初始化
       
静态链表的提取分配空间
       
静态链表添加元素
       
释放空间
       
静态链表删除元素
       
修改元素
       
查找元素
4.静态链表的完整代码实现及演示
5.静态链表和动态链表的联系和区别
       
静态链表
       
动态链表

1.静态链表的概述

我们了解线性表两种存储结构各自的特点,那么,是否存在一种存储结构,可以融合顺序表和链表各自的优点,从而 既能快速访问元素,又能快速增加或删除数据元素。

使用静态链表存储数据,数据全部存储在数组中(和顺序表一样),但存储位置是随机的,数据之间"一对一"的逻辑关系通过一个整形变量(称为"游标",和指针功能类似)维持(和链表类似)。

数据存放到数组中时,给各个数据元素配备一个整形变量,此变量用于指明各个元素的直接后继元素所在数组中的位置下标,如图 :
在这里插入图片描述

通常,静态链表会将第一个数据元素放到数组下标为 1 的位置(a[1])中。

上图中,从 a[1] 存储的数据元素 1 开始,通过存储的游标变量 3,就可以在 a[3] 中找到元素 1 的直接后继元素 2;同样,通过元素 a[3] 存储的游标变量 5,可以在 a[5] 中找到元素 2 的直接后继元素 3,这样的循环过程直到某元素的游标变量为 0 截止(因为 a[0] 默认不存储数据元素)。

如上通过 "数组+游标" 的方式存储具有线性关系数据的存储结构就是静态链表。

2.静态链表的实现

2.1静态链表的结点

静态链表存储数据元素也需要自定义数据类型,至少需要包含以下 2 部分信息:

  1. 数据域:用于存储数据元素的值;
  2. 游标:其实就是数组下标,表示直接后继元素所在数组中的位置;
因此,静态链表中节点的构成用 C 语言实现为:
typedef struct {
    int data;//数据域
    int cur;//游标
}component;

2.2备用链表

其实上面显示的静态链表还不够完整,静态链表中,除了数据本身通过游标组成的链表外,还需要有一条连接各个空闲位置的链表,称为备用链表。

备用链表的作用是:

回收数组中未使用或之前使用过(目前未使用)的存储空间,留待后期使用。

也就是说,静态链表使用数组申请的物理空间中,存有两个链表,一条连接数据,另一条连接数组中未使用的空间。通常,备用链表的表头位于数组下标为 0(a[0]) 的位置,而数据链表的表头位于数组下标为 1(a[1])的位置。

静态链表中设置备用链表的好处是:

可以清楚地知道数组中是否有空闲位置,以便数据链表添加新数据时使用。比如,若静态链表中数组下标为 0 的位置上存有数据,则证明数组已满。

例如,使用静态链表存储 {1,2,3},假设使用长度为 6 的数组 a,则存储状态可能如图所示:
 上图备用链表上连接的依次是 a[0]、a[2] 和 a[4],而数据链表上连接的依次是 a[1]、a[3] 和 a[5]。

2.3静态链表的详细实现过程

假设使用静态链表(数组长度为 6)存储 {1,2,3},则需经历以下几个阶段。

1.在数据链表未初始化之前,数组中所有位置都处于空闲状态,因此都应被链接在备用链表上
在这里插入图片描述

当向静态链表中添加数据时,需提前从备用链表中摘除节点,以供新数据使用。
备用链表摘除节点最简单的方法是摘除 a[0] 的直接后继节点;同样,向备用链表中添加空闲节点也是添加作为 a[0] 新的直接后继节点。因为 a[0] 是备用链表的第一个节点,我们知道它的位置,操作它的直接后继节点相对容易,无需遍历备用链表,耗费的时间复杂度为 O(1)。

2 .因此,在上图的基础上,向静态链表中添加元素 1 的过程下图 所示:
在这里插入图片描述
添加元素 2 的过程如图:

在这里插入图片描述

添加元素3的过程如图

在这里插入图片描述

当然我们有时候会看到静态链表最后数组最后一个位置的数据域不存放任何东西,游标域存放首元结点的数组下标(相当于头结点)

3.静态链表的代码实现(最后一个数组元素相当于头结点)

3.1静态链表初始化

void InitArr(component *array) {
    int i = 0;
    for (i = 0; i < maxSize; i++) {
        array[i].cur = i + 1;//将每个数组分量链接到一起
        array[i].data = 0;
    }
    array[maxSize - 1].cur = 0;//链表最后一个结点的游标值为0
}

3.2静态链表的提取分配空间

int mallocArr(component * array) {
    //若备用链表非空,则返回分配的结点下标,否则返回 0(当分配最后一个结点时,
    //该结点的游标值为 0)
    int i = array[0].cur;
    if (array[0].cur) {
        array[0].cur = array[i].cur;
    }
    return i;
}

3.3静态链表添加元素

//向链表中插入数据,body表示链表的头结点在数组中的位置,add表示插入元素的位置,num表示要插入的数据
void insertArr(component * array, int body, int add, int num) {
    int tempBody = body;//tempBody做遍历结构体数组使用
    int i = 0, insert = 0;
    //找到要插入位置的上一个结点在数组中的位置
    for (i = 1; i < add; i++) {
        tempBody = array[tempBody].cur;
    }
    insert = mallocArr(array);//申请空间,准备插入
    array[insert].data = num;

    array[insert].cur = array[tempBody].cur;//新插入结点的游标等于其直接前驱结点的游标
    array[tempBody].cur = insert;//直接前驱结点的游标等于新插入结点所在数组中的下标
}

3.4释放空间

void freeArr(component * array, int k) {
    array[k].cur = array[0].cur;
    array[0].cur = k;
}

3.5静态链表删除元素

//删除结点函数,num表示被删除结点中数据域存放的数据
int deletArr(component * array, int num) {
    int tempBody =maxSize-1;
    int del = 0;
    int newbody = 0;
    //找到被删除结点的位置
    while (array[tempBody].data != num) {
        tempBody = array[tempBody].cur;
        //当tempBody为0时,表示链表遍历结束,说明链表中没有存储该数据的结点
        if (tempBody == 0) {
            printf("链表中没有此数据");
            return 0;
        }
    }
    //运行到此,证明有该结点
    del = tempBody;
    tempBody = maxSize-1;
    //删除首元结点,需要特殊考虑
    if (del == array[maxSize-1].cur) {
        newbody = array[del].cur;
        array[maxSize-1].cur=newbody; 
        freeArr(array, del);
    }
    else
    {
        //找到该结点的上一个结点,做删除操作
        while (array[tempBody].cur != del) {
            tempBody = array[tempBody].cur;
        }
        //将被删除结点的游标直接给被删除结点的上一个结点
        array[tempBody].cur = array[del].cur;
        //回收被摘除节点的空间
        freeArr(array, del);
    }  
}

3.6修改元素

void amendElem(component * array, int oldElem, int newElem) {
    int add = selectNum(array,oldElem);
    if (add == -1) {
        printf("无更改元素");
        return;
    }
    array[add].data = newElem;
}

3.7查找元素

int selectNum(component * array, int num) {
    //当游标值为0时,表示链表结束
     int tempBody = maxSize-1; 
    while (array[tempBody].cur != 0) {
        if (array[tempBody].data == num) {
            return tempBody;
        }
        tempBody = array[tempBody].cur;
    }
    //判断最后一个结点是否符合要求
    if (array[tempBody].data == num) {
        return tempBody;
    }
    return -1;//返回-1,表示在链表中没有找到该元素
}

4.静态链表完整代码实现及演示

#include <stdio.h>
#define maxSize 6
typedef struct {
    int data;
    int cur;
}component;
void InitArr(component *array) {
    int i = 0;
    for (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;
    }
    return i;
}
//初始化几个元素 
void add(component * array)
{
	printf("请输入你想添加的数据成员的数量:");
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		printf("请输入第%d个数据成员:",i);
		scanf("%d",&array[i].data);	
	}
	array[0].cur=n+1;
	array[n].cur=0;
	array[maxSize-1].cur=1;
}
//向链表中插入数据,add表示插入元素的位置,num表示要插入的数据
void insertArr(component * array, int add, int num) {
    int tempBody = maxSize-1;//tempBody做遍历结构体数组使用
    int i = 0, insert = 0;
    //找到要插入位置的上一个结点在数组中的位置
    for (i = 1; i < add; i++) {
        tempBody = array[tempBody].cur;
    }
    insert = mallocArr(array);//申请空间,准备插入
    array[insert].data = num;

    array[insert].cur = array[tempBody].cur;//新插入结点的游标等于其直接前驱结点的游标
    array[tempBody].cur = insert;//直接前驱结点的游标等于新插入结点所在数组中的下标
}
void displayArr(component * array) {
    int tempBody = maxSize-1;//tempBody准备做遍历使用
    tempBody=array[tempBody].cur;
    while (array[tempBody].cur) {
        printf("%d,%d ", array[tempBody].data, array[tempBody].cur);
        tempBody = array[tempBody].cur;
    }
    printf("%d,%d\n", array[tempBody].data, array[tempBody].cur);
}
//查找数据域为elem的结点在数组中的位置
int selectNum(component * array, int num) {
    //当游标值为0时,表示链表结束
     int tempBody = maxSize-1; 
    while (array[tempBody].cur != 0) {
        if (array[tempBody].data == num) {
            return tempBody;
        }
        tempBody = array[tempBody].cur;
    }
    //判断最后一个结点是否符合要求
    if (array[tempBody].data == num) {
        return tempBody;
    }
    return -1;//返回-1,表示在链表中没有找到该元素
}

//在以body作为头结点的链表中将数据域为oldElem的结点,数据域改为newElem
void amendElem(component * array, int oldElem, int newElem) {
    int add = selectNum(array,oldElem);
    if (add == -1) {
        printf("无更改元素");
        return;
    }
    array[add].data = newElem;
}


void freeArr(component * array, int k) {
    array[k].cur = array[0].cur;
    array[0].cur = k;
}

//删除结点函数,num表示被删除结点中数据域存放的数据,函数返回新数据链表的表头位置
int deletArr(component * array, int num) {
    int tempBody =maxSize-1;
    int del = 0;
    int newbody = 0;
    //找到被删除结点的位置
    while (array[tempBody].data != num) {
        tempBody = array[tempBody].cur;
        //当tempBody为0时,表示链表遍历结束,说明链表中没有存储该数据的结点
        if (tempBody == 0) {
            printf("链表中没有此数据");
            return 0;
        }
    }
    //运行到此,证明有该结点
    del = tempBody;
    tempBody = maxSize-1;
    //删除首元结点,需要特殊考虑
    if (del == array[maxSize-1].cur) {
        newbody = array[del].cur;
        array[maxSize-1].cur=newbody; 
        freeArr(array, del);
    }
    else
    {
        //找到该结点的上一个结点,做删除操作
        while (array[tempBody].cur != del) {
            tempBody = array[tempBody].cur;
        }
        //将被删除结点的游标直接给被删除结点的上一个结点
        array[tempBody].cur = array[del].cur;
        //回收被摘除节点的空间
        freeArr(array, del);
    }  
}
int main()
{
	component array[maxSize];
    InitArr(array);
    add(array);
    int selectAdd;
    printf("静态链表为:\n");
    displayArr(array);
    printf("在第3的位置上插入元素4:\n");
    insertArr(array, 3, 4);
    displayArr(array);
    printf("删除数据域为1的结点:\n");
    deletArr(array, 1);
    displayArr(array);
    printf("查找数据域为4的结点的位置:\n");
    selectAdd = selectNum(array, 4);
    printf("%d\n", selectAdd);
    printf("将结点数据域为4改为5:\n");
    amendElem(array, 4, 5);
    displayArr(array);
    return 0;

}

在这里插入图片描述

静态链表和动态链表的联系和区别

5.1静态链表

使用静态链表存储数据,需要预先申请足够大的一整块内存空间,也就是说,态链表存储数据元素的个数从其创建的那一刻就已经确定,后期无法更改
比如,如果创建静态链表时只申请存储 10 个数据元素的空间,那么在使用静态链表时,数据的存储个数就不能超过 10 个,否则程序就会发生错误。不仅如此,静态链表是在固定大小的存储空间内随机存储各个数据元素,这就造成了静态链表中需要使用另一条链表(通常称为"备用链表")来记录空间存储空间的位置,以便后期分配给新添加元素使用,如图 2 所示。这意味着,如果你选择使用静态链表存储数据,你需要通过操控两条链表,一条是存储数据,另一条是记录空闲空间的位置。

有一点需要特别的注意:

有的静态链表最后一个数组下标存放的是头结点,而有的静态链表最后一个数组下标和普通数组下标作用一样

5.2动态链表(普通链表)

使用动态链表存储数据,不需要预先申请内存空间,而是在需要的时候才向内存申请。也就是说,动态链表存储数据元素的个数是不限的,想存多少就存多少。
同时,使用动态链表的整个过程,你也只需操控一条存储数据的链表。当表中添加或删除数据元素时,你只需要通过 malloc 或free 函数来申请或释放空间即可,实现起来比较简单。

猜你喜欢

转载自blog.csdn.net/qq_45737068/article/details/107533976
今日推荐