链表指针(一定要看完)
我想了两年,终于彻底的理解了指针,现在我将毫不保留的传授给你们,因为只有我懂你O(∩_∩)O。 绝对让你对指针有了进一步的认知。
无敌重点: 结点存放的仅仅只是地址。
假如 第一个节点是 ListNode head
然后 ListNode p =head
假如,我现在 让head = null;
你是不是以为第一个节点变成null了。 错。 第一个节点完好无损
P并没有受到了改变, 变量head只是存放了第一个节点的地址而已。
你可能会疑问, 一开始 head.val = 5 ,第一个节点的值确实会变成5
head.next = 某个节点, 那么 head.next确实存放的是某个节点。
但是他仅仅可以操作他而已。你第一个节点的地址不管怎么弄,都是不会变的,内存已经生成好定在那了。 所以 head = null; 仅仅是把 head 变成了null 而已。 那个地址代表的节点完好无损。
所以在head= null 前,我们定义了 ListNode p =head 。 即使你Head = null ,但是我们p已经存放了那个节点的地址啊,我们还是可以找到那个节点,他还是好好的。
我就拿题目来讲吧。(可能有点啰嗦,但是我相信你经过上面的话后,已经有点觉悟了。下面我就继续啰嗦。)
题目叫: 反转链表
首先我们来认识指针的结构。 java中指针就是一个类。c++应该是结构体。拿java来说。
他就是有两个属性 : 一个是int类型。这个大家都理解。 主要难理解的就是 next,其实理解了也很简单。
类名字和属性名字 可以随便取,但是题目定义了你就得用他的。
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
下面开始讲解
next ,其实就是一个指针类型也是ListNode 有点像套娃懂吧(牢记)
超级重点: 指针存储的是地址,地址,地址
下面为了方便,我拿一下题目解答的图片来讲解(牛客拿的,我不知道怎么去掉我的水印)
题目给定了一个头节点 head ,他代表图片第一个结点,也就是存有 数据为1 的那个结点。
那么 说明 head.val = 1 (我就按照上面定义的指针 , val代表存放的是数据, next代表存放的是下一个结点地址) 因为head其实也是一个类,所以 head. 属性(head.val) 这是java基础,我就不说这么多了 ,反正就是 类. 属性
然后 head.next 代表的就是 第二个节点的地址(其实也就是第二个节点)
下面我们来认识这句话,
head =head.next
这代码的意思就是, 把head的next指向的地址,赋值给head。 重点:那么此时head存放的就是head.next(也就是第二个结点)的地址了。
所以head就变成了 第二个结点(也就是存有数据为2的那个节点)
// head即为给定的头结点,即第一个节点(他就是一个节点而已。只是他的next存放了下一个结点的地址,而他下一个结点又存放了下下一个结点的地址,然后把他们连了起来。)
这里我额外演示一下,链表插入的过程。
这里我要把一个名为p的节点插入到L的结点的后面。
根据我前面的讲解,相信你已经有一丝丝的觉悟了,那么接下来,你就做好真正的觉悟吧!
这里我把L右边的那个节点叫做 M 结点,方便说明(注意是L右边那个,不是要插入的p节点)
第一步: p.next = L.next;
L.next 本来存放的是 下一个结点(M)的地址(也就是L的右边那个节点M,看图片) 。
p是要插入的结点, 没插入之前, p.next指向的是null,你可以理解为, 他什么都没指向,就单一 一个结点。
现在我们要把p插入到L和M的之间,那么我们是不是就要 拿到 M结点, 然后 p.next指向 M,就跟图片蓝色字体画的那样去做。
M节点 其实就是 L.next 即 L.next == M
所以根据蓝色字体的做法,其实就是 P.next = L.next
第二步: L.next = p ;
然后我们去完成绿色字体的步骤。
这个时候 L.next 存放(指向)的还是 M的地址,而我们需要把 L.next指向节点P(即存放P的字地址)
所以 L.next = p
这样,我们就完成了一个节点的插入。
是不是有头绪了,下面我们来讲上面说的那道 反转链表的题目,边看图片边看代码,
边看我注释,代码我里面写了注释。
下面的代码我就只描述图片的第一步和第二步,不然文字描述太难了。
注意, head.next在右边和在左边的含义。
head.next在右边的意义是,把head指向的下一个结点赋值给左边的结点
此时 temp 存放的是 head.next指向的结点的地址。
ListNode temp = head.next;
而head.next在左边的意义是, 让head的next 指向(存放) 右边的节点(即重新指向)
此时 head.next存放(指向)的是 newHead这个节点的地址
//让head的next指向 新链表节点。注意
head.next = newHead;
不知道,到这里,你头脑清晰一点,如果没有,可能还是我写的有点绕吧。继续看完下面的代码,就是反转链表那道题的题解。
无敌重点: 结点存放的仅仅只是地址。
假如 第一个节点是 ListNode head
然后 ListNode p =head
假如,我现在 让head = null;
你是不是以为第一个节点变成null了。 错。 第一个节点完好无损
P并没有受到了改变, 变量head只是存放了第一个节点的地址而已。
你可能会疑问, 一开始 head.val = 5 ,第一个节点的值确实会变成5
head.next = 某个节点, 那么 head.next确实存放的是某个节点。
但是他仅仅可以操作他而已。你第一个节点的地址不管怎么弄,都是不会变的,内存已经生成好定在那了。 所以 head = null; 仅仅是把 head 变成了null 而已。 那个地址代表的节点完好无损。
所以在head= null 前,我们定义了 ListNode p =head 。 即使你Head = null ,但是我们p已经存放了那个节点的地址啊,我们还是可以找到那个节点,他还是好好的。
现在,我相信你是不是觉悟了,觉悟了就开始看下面题目答案的代码吧!
/* 题目给的链表的结构
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public ListNode ReverseList(ListNode head) {
// head即为给定的头结点,即第一个节点(他就是一个节点而已。只是他的next存放了下一个结点的地址,而他下一个结点又存放了下下一个结点的地址,然后把他们连了起来。)
// 定义一个节点, 代表图片中的新链表的第一个节点
ListNode newHead = null;
while (head != null) {
//保存原链表中头节点的下一个节点(地址)。
ListNode temp = head.next;
//让head的next指向 新链表节点。(重新指向地址, 即指向那个null的地址)
head.next = newHead;
//更新新链表 (让newHead 存放的地址变成head存放的地址)
newHead = head;
// 把头结点的下一个地址赋值给 head (此时head的地址是原链表的第二个节点)
head = temp;
}
//返回新链表(他只代表一个节点而已,关系是通过指针next连接起来的)
return newHead;
}