赞
踩
链表是指数据使用一个一个的结点连接起来的数据结构,这样的数据结构中不像顺序表一样,顺序表中不同的数据在内存中的存储空间是相邻的,链表中的相邻结点在内存中的存储位置是没有任何关系的,每一个链表都是由许多的结点组成的,而一个链表,我们需要一个对应的头指针来管理这个链表,也就是说,如果我们拥有一个链表的头指针,那么我们就可以拿到这个链表中的所有的结点,在此基础上,如果我们还学会对链表中的结点进行一些常见的操作(增删查改),那么我们就可以熟练使用链表来解决现实中的问题了
我们知道,链表是一种数据结构,那么所谓的数据结构就是用来存放数据的,因此,一个链表中的结点肯定需要一块空间来存储数据,再者,链表中不同的结点在内存中的存放位置是没有关系的,所以,我们需要想办法能够使相邻的链表结点之间能够建立一定的关系,那么这个就是通过指针来实现的。每一个链表的结点中,都会有一个数据域,和一个指针域,数据域就是用来存放数据的,指针域是用来存放逻辑上的下一个结点在内存中的存储地址。综上,一个结点需要解决的问题就是存放数据和记录下一个结点的地址,因此,需要一个**复杂的对象(结构体)**才能够解决这样的问题。为了方便表示结点中存放的数据的类型,通常我们会将存储的数据类型进行重定义,这样,在改变存放的数据类型的时候,就只需要改变这个重定义的位置就可以了,而不需要改变其他位置,相当于加入刚开始链表存储的数据类型是int类型,后面想存放的数据类型是double,那么我们只需要将代码中下面这行代码的int改成double即可,不需要改变其他位置的代码内容,代码实现如下:
struct ListNode
,而不是ListNode
。为了每次使用struct ListNode这个类型的时候不用加上struct,我们通常会对这个类型进行重定义,如下:上面这个代码还需要注意一个细节:
typedef
出来的ListNode
是在这个typedef
之后才能够使用的,因为代码只会向上进行查找,不会向下进行查找,就像前置声明一样:在使用定义在当前行下面的函数接口的时候,通常是会报错的,那么这种情况下我们需要在前面对定义在下面的函数接口进行前置声明,告诉编译器当前文件中已经定义该函数。
对一个链表的常见操作包括:打印链表中的所有结点存储的值,对一个链表进行初始化,申请一个新节点,在链表的尾部进行插入,在链表尾部进行删除,在链表头部进行插入,在链表头部进行删除,销毁链表。
在上面的函数声明中,我们发现,在传参的时候,有些接口传的是ListNode* head
(链表头指针的内容即对应结点的地址),有些接口传的是ListNode** phead
(头指针的地址,二级指针)。其实本质都是我们要对一个链表进行操作,所以需要将对应链表的头指针传给函数,对应的函数才能够拿到这个链表的所有结点,主要的区别在于这个接口中是否需要改变头指针的值,如果需要改变这个链表的头指针,那么就需要将头指针的地址传给函数接口,函数接口中才能够通过头指针的地址将头指针的值进行改变,如果传的是头指针的内容,那么在函数中的变量是形式参数,我们知道形式参数其实是实际参数的一份拷贝,也就是说对形式参数的改变是不会影响实际参数的值的,所以如果我们传的是ListNode* head
,那么在函数接口中的head
就是实际参数(链表头指针)的一份拷贝,我们在函数中对形式参数的改变不会影响实际参数的值的,下面我们一一分析上面各个函数中是否需要改变头指针的值的情况:
打印链表(链表的遍历方法)
打印链表本质就是要我们学习链表的遍历方法:使用一个cur指针从头开始记录每一个结点的地址,依次往后,直到空就结束,注意:循环判断条件中的cur本身是一个指针,当cur还在遍历链表中的结点的时候,此时cur指向的是链表中的结点,是有效的结点,此时cur不为空,循环条件为真,当cur遍历完链表时,cur会走到链表最后一个结点指向的NULL,此时cur就是空指针,循环判断条件为假,循环结束。
初始化链表
初始化链表本质就是将链表的头指针置成空指针即可,一般情况下可能不需要这个初始化函数,因为我们在定义链表的头指针的时候通常会习惯将头指针的值置成空指针,表示刚开始链表为空。
申请一个新节点
在对链表进行各种插入的时候通常都需要定义一个新节点,因此,为了避免代码冗余,我们可以将这个定义链表的行为重新封装成一个新函数,当需要使用定义新节点这个行为的时候就可以调用了,基本的思路就是使用malloc开辟空间,对结点中的内容进行处理
在使用malloc申请空间的时候需要注意以下问题:
头删我们需要注意的是,一定要注意链表是否为空,如果链表为空,此时是不需要删除的,因为没有内容可以删除,如果链表不为空,要删除的是第一个结点,先保存第一个结点的下一个位置(可能为空,可能不为空),当我们保存了第一个结点的下一个位置之后,再删除第一个结点(地址保存在头指针),再让头指针指向原来链表的第一个结点的下一个位置,如果原来链表只剩下一个结点,也就是第一个结点的下一个位置是空指针,那么此时就是头指针指向空指针,此时链表就为空,也就是这个头删完成链表最后一个结点的删除。如果原来链表剩下多个结点,那么原先保存的第一个结点的下一个位置就不为空,也就是原来链表中的第二个结点,那么此时就是删除头指针指向的链表(第一个结点),再让头指针指向保存的第二个结点即可。
9. 销毁链表
销毁链表一定要注意需要先将链表中的每一个结点free之后,才free头指针,一定不能只free头指针,如果只free头指针,就会造成链表中的结点没有被释放,此时就会造成严重的内存泄露。所以正确的做法就是使用一个记录指针,记录遍历每一个结点,挨个删除释放,注意,在删除之前,一定要先保存删除结点的下一个结点,如果先删除不保存下一个结点的画,删除释放该结点之后,就找不到该结点的下一个结点了。
注意:在上面的代码中,phead保存的是链表头指针的地址,我们对phead进行断言,原因是下面我们需要通过phead进行解引用找到链表的头指针,因此,这个phead是一定不能为空的,如果为空,那么我们对其进行解引用就相当于对空指针进行解引用了,那么肯定就会导致程序崩溃
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。