唔。。感觉新学了链表,好好好
1. 移除链表元素
在操作移除链表元素之前,先看下链表结构是如何用代码申明的。用单链表举例(
struct ListNode{
int val; //定义节点的值
ListNode *next; //定义节点的指针
ListNode() : val(0), next(nullptr) {} //nullptr作为一个字面常量和零指针常数,可以隐式地转换为任何指针类型 C++干货系列——辛苦为你填坑的nullptr,你真的懂它吗? - 知乎
ListNode(int x) : val(x), next(nullptr) {} //
ListNode(int x, ListNode *next) : val(x), next(next) {} //
};
就我理解下面三个是对应不同情况下的初始化构造函数。 (链表基本概念可以去网上搜哈)
注意!上面只是对链表结构的申明!
然后正式移除链表元素。
思路
删除链表中的一个元素就是让目标节点的上一个节点的指针指向目标节点的下一个节点的指针(无图报一丝~~);然后释放被删除的节点;
但是节点分为头节点head和非头节点,且删除它们的方式是不一样的,所以就要判断此时的节点是不是头节点;
方法一:区分是否为头节点
如果目标节点是头节点,则只需让head指针指向下一个节点即可;
head = head->next;
delete 被释放的节点指针;
如果目标节点不是头节点,则让前一节点指针指向当前节点的下一节点的指针;
定义一个临时指针cur,相当于for循环里变量 i 的作用;用来遍历;
若要删除某个目标节点,则要将cur指向该节点的上一个节点,因为删除一个节点是要将上一节点的指针指向该节点的下一个节点,如果cur直接指向了该节点,则首先是无法判断上一节点是否为空,其次无法从当前节点指向上一节点(对于单链表来说)。 所以要将cur指向该节点的上一个节点,且cue从head开始。
【注意:每次循环的判断都要判断cur(目标节点的上一个节点指针)不能为空,且cur->next也不能为空。前者是因为要操作cur->next, 如果为空,则会报空指针的错;后者是因为我要用里面的val来比较,所以也不能为空。 cur->next->next可以不同判断,因为可能是直接指向了最后一个节点NULL;(即删除的是最后一个节点,就直接指向NULL了。
综上,删除一个目标节点,必须要从它的上一个节点开始操作。
代码如下:
写代码时遇到的问题...
1.null忘记大写了。。。NULL!!!!!
2.每次想delete的时候都不知道要delete谁,所以除了cur用来遍历之外,还要创建临时指针tmp or sth else;
3.if之后忘记else了导致进入无限循环了好家伙。。。
4.注意使用while和 if 的区别,如果判断用的是if,比如链表[1 1 1 1],val = 1,则每次删除的都是头节点,如果不用while,就会只删除一个满足条件的节点了,所以用while。
方法二:虚拟头节点 dummy head
如果想要能够将删除头节点的操作也和删除中间节点一样保持统一,则只需用到dummyHead的操作即可!
先实例化一个虚拟的头节点 ListNode* dummyHead = new ListNode(0);,然后让它指向原来的head节点;定义临时指针ListNode* cur = dummyHead, 相当于要对链表中的所有节点做统一的操作,不区分是否为头节点了,因为此时头节点在有虚拟头节点的前提下也变为链表中的一员了。
代码如下:
我最开始写的代码报错了:说第16行访问了空指针;说明此时我的dummyHead是个空指针;
按我理解的就是,现在这个指向链表虚拟头节点的指针还没有被分配一定的内存空间,导致在要指向下一节点的时候先访问dummyHead的时候访问了空指针;所以要对dummyHead进行实例化!
ListNode* dummyHead = new ListNode(0); //使用链表结构中的构造函数
修改后代码如下:
运行通过;
注意return的内容,return的时候return的是dummyhead→next,因为此时head可能已经被改变了。
答案给的操作是把dummyHead释放掉,
先让它的next指向现在的head,然后释放掉它;嗷
【被删除的节点必须进行内存的释放!!!!!!!】
2. 设计链表
(其实也可以不用虚拟头节点,那样的话就也是,一开始初始化链表,初始化方式和虚拟头节点不太一样,其操作是把NULL赋给head节点,然后每次插入头节点的时候就直接找到head,然后new->next = head, 其他地方的操作是和虚拟头节点一样的,只是注意index的大小和指向的位置) (不知道有没有说对。。)
虚拟头节点法
1. 首先在类中定义链表的结构✔️
2. 然后初始化链表(包含头节点了)这里初始化的时候没有定义变量类型是因为下面有声明私有成员变量?
3. 变量名前➕下划线的命名方式,一般类成员变量这样写,但是要注意和包里的撞;【C++】关于以下划线开头的变量名_c++实例化类 下划线命名-CSDN博客
4. 在函数里不是最后一句return也会直接return; //line17
直接上代码:
5. 用链表结构中的函数创建一个新节点的时候有值就直接在后面括号里写val了;
6. 定位到链表的尾部用cur->next != NULL判断,这是与索引中间位置不同的地方;
7. addAtIndex函数,中间判断index和_size大小的时候一开始出了点问题,还是要代入具体的情况去考虑避免出错; if中忘记写等于了,导致索引到链表尾部的下一个的时候没考虑到,即inde=_size的时候;
8. 删除元素之后要释放被删的节点,除了delete之外,还有赋值nullptr;因为delete只是把这个节点中的值所占的内存释放了,并未删除指针本身;C++delete一个指针之后为什么要设置为nullptr_delete nullptr-CSDN博客
9. 注意index的有效范围的判断;
10. 最后别忘了打印链表;
11. 注意!链表的取值都是cur指向当前index,而插入和删除元素都是cur指向目标节点的前一个!!!!!!!!!!!! 从代码中也可以看出cur一开始的指向在不同函数中是不同的!
(这个过程写si我了要,我不记得怎么写,我只知道逻辑,但是写起来还是很费劲,好的还是要多写。。。让我自己写一遍我还不一定写得出来。。
3. 反转链表
方法一:双指针法
好好好,又是双指针,这次是让cur指向head,让pre指向NULL;
然后从链表的头开始向后一次改变节点指向;所以还需定义一个临时指针暂存;
代码如下:
改变指向后,line19;cur和pre一起往后移动一位,先把cur给pre,再把暂存的tmp给cur,反过来不行。
方法二:递归法
tips:递归就是f函数里又调用f函数本身;迭代就是输出又作为输入,类似麦克风对着音箱;
这两个好像啊。。
递归在这个题目里和双指针的区别就在于,使用递归方式来将cur和pre移动;最唯一的一步就是改变节点指向;
代码如下:
定义了一个reverse函数,则在主函数里就是调用这个函数;
reverse函数中,注意传入的参数,一开始传入head和NULL,和双指针一开始的赋值对标,不要传错了;然后通过递归再次自己调用自己这个函数,其实本质就是相当于在双指针里的pre和cur的移动赋值,在双指针的理解下这个递归就比较好理解了。
ps:一开始忘记在line18 return 了导致报错;原因是这个函数不是void,必须每个控制都有return,不然原本的代码就只有在if后return了,其他地方没有。https://wenku.csdn.net/answer/8ac186ab1a0e5db93bdc9ca01f141386
我理解的递归和迭代:
递归就像上述代码,自己的函数里嵌套自己;
迭代就比如 for(int i=0; i<n; i++) {a = f(a)}, 嗯。