✅作者简介:别人以梦为马,而我要以码为梦。我是叶落秋白,努力学后端中
✨个人主页:叶落秋白的主页
🔥系列专栏:数据结构干货分享
📃推荐一款模拟面试、刷题神器👉进入刷题的世界
🔥前言
这篇博客即将解决你看不懂或者不会写链表的基本操作的问题,对于初学者而言,有很多地方肯定是费解的。比如函数的**参数列表**的多样化,动态分配内存空间函数**mallo**c等,其实这些知识和**指针**联系紧密,尤其是二级指针。那么开始好好的学习这篇博客吧!
二级指针讲解
简述:其实就是一个指针指向另一个指针的地址。
我们都知道指针指向地址,但是指针自身也是一个变量,当然也可以被二级指针所指向。
语法:形如 int x = 10; int *q = &x; int **p = & q;
那么这里的q指针指向x的地址,p指针指向指针q的地址,q可以得到x的值,p可以得到q指针本身,**p也可以得到x的值。
代码示例:
int main(void)
{
int x = 10;
int* q = &x;
int** p = &q;
printf("x 的地址为: %d\n", &x);
printf("q 指向的地址为:%d\n", q);
printf("*p的值为: %d\n", *p); //p指向指针q的地址,那么*p是解引用操作,
//就等于了q本身
printf("x 的值为: %d\n", x);
printf("q 存取的值为: %d\n", *q);
printf("**p的值为: %d\n", **p); //**p相当于解引用解了两次,第一次先得到q本身,
//第二次得到q指向地址的值
return 0;
}
运行结果:
链表的应用
** 这里以带头结点的双链表为例**
定义双链表的结构体
typedef int ElemType;//将整型数据重命名为int
typedef int Status;//整型重命名为Status
//双链表的数据结构定义
typedef struct DouNode {
ElemType data; //数据域
struct DouNode* head; //前驱指针
struct DouNode* next; //后继指针
}DousList, * LinkList;// 结点指针
代码解释:
利用**typedef**对数据类型进行重命名,只要在后面遇到的 **ElemType**和 **Status**都是整型就够了。双链表结构体包含三个部分:数据域、前驱指针、后继指针,与单链表的区别就是多了一个前驱指针。然后大括号结束部分也是重命名,此时**DousList**和**DouNode**效果一样,都是结构体名,然后**LinkList**是指向结点的指针。
具体使用:
LinkList L,L是一个指针,DousList *P,P也是一个指针,属于两种创建方式。
创建双链表
** 使用两种正确的创建链表形式和一种错误的形式,对比着记忆创建方法**
传入一级指针
** 这种方式并不能成功创建**
代码演示:
void CreateDouList(LinkList L, int n)
{
LinkList ptr;
int i;
L = (LinkList)malloc(sizeof(DousList)); //为头结点申请空间
L->next = NULL;
L->head = NULL;
L->data = n;//L->data记录结点的个数
ptr = L;
for (i = 0; i < n; i++)
{
int value = 0;
scanf("%d",&value);
LinkList me = (LinkList)malloc(sizeof(DouNode));
me->data = value; //节点数据域
me->next = NULL;
me->head = NULL;
ptr->next = me;
me->head = ptr;
ptr = ptr->next; //尾插法建表
}
}
代码解析:
这里的参数列表是 LinkList L 和 整型数据 n,L是传入的链表头结点指针,n是用来记录插入数据的个数的,在下面的for循环用做循环的次数。接下来使用**malloc**函数为L链表分配内存空间,malloc需要用**指针**来接收,左边的括号是分配的**指针类型**,右边的括号是分配的内存**空间大小**。分配空间完成之后初始化前驱和后继指针为空,数据域data记录数据的个数。ptr指针初始等于L指针,接下来进入n次循环,创建待插入结点指针me并进行分配内存空间和初始化,最后三行代码进行尾插法建立链表:
尾插法:
先让ptr的后继指针指向me,然后me的head指针指向ptr,这就相当于在链表头把me结点插入链表,然后ptr指向这个插入的新结点,这就保证了每次插入的结点都在上一个插入的结点之后。
但是这样真的在链表中插入数据了吗 ,来看看调试结果:
**进入遍历的程序时,让创建的ptr指针指向L链表的后继,立马就出现了空指针异常,但是上面明明插入数据了,原因是什么呢? 很明显,这里的链表L并未完成插入数据。这是因为我们在创建链表的函数里传入的只是链表的指针L,那么在函数里这个指针只是一个副本,在这里给他增大内存空间并不会影响到实参链表,这和普通数据类型的值传递和地址传递的情况一致。**
** 我们利用传入指针地址来解决这个问题,两个方法:指针的引用和二级指针**
传入指针的引用
** 函数的实现部分完全不用修改,只要形参列表加上一个引用符"&"即可。**
void CreateDouList(LinkList &L, int n);
** 查看调试结果:**
同样的调试方法,传入指针的引用之后可以清晰的看到L的**data**等于5,也就是存了五个数据,然后对于的后继结点的值都和尾插的结果一致,最后一个结点的后继指针正好指向NULL,完全**符合**我们的设计的代码。
** 传入指针的引用之后,函数里链表的空间变化会导致实参里的链表空间变化,这样做才能使插入操作完成,将结点插入到链表内。**
传入二级指针
** 这个和指针的引用原理一样,我主要分享给你们使用的形式**
注意调用的时候实参要加“&”符,例:**CreateDouList(&L,n)**;
void CreateDouList(LinkList *L, int n)
{
LinkList ptr;
int i;
*L = (LinkList)malloc(sizeof(DousList)); //为头结点申请空间
(*L)->next = NULL;
(*L)->head = NULL;
(*L)->data = n;//L->data记录结点的个数
ptr = (*L);
printf("开始插入数据:\n");
for (i = 0; i < n; i++)
{
int value = 0;
scanf("%d",&value);
LinkList me = (LinkList)malloc(sizeof(DouNode));
me->data = value; //节点数据域
me->next = NULL;
me->head = NULL;
ptr->next = me;
me->head = ptr;
ptr = ptr->next; //尾插法建表
}
}
这里形参列表的参数是 LinkList *L,和DousLIst **L效果一样,是一个**二级指针**。如果用到指向链表的指针就需要一次接引用操作,写成(*L)的形式。然后再去分配空间、进行初始化、赋值给链表指针ptr等操作,这样链表二级指针L的改变也会使实参的链表发生改变,可以查看调试结果。
调试结果:
✨刷题网推荐
** 数据结构对于小白来说并不是特别友好,好多知识几天不看就会抛之脑后一定要多巩固学到的知识,因此刷题必不可少。在这里我给大家介绍一个我认为对小白比较友好的刷题平台—**牛客网
这些都是经典的链表题,多加练习一定可以巩固知识,拿捏个链表还会是问题吗。
** 希望大家可以充分利用时间,巩固所学,冲击大厂,让我们共同进步!**
版权归原作者 叶落秋白 所有, 如有侵权,请联系我们删除。