我在简单的单链表中有一个很大的困惑,比如:在java中。
节点类别:
public class node {
public int data;
public node next;
public node(int data , node next) {
this.data = data;
this.next = next;
}
}
链表类:
public linked list{
public node first;
public node last;
public void add(int data){
node x = new node(data,null);
if( first == null ) { first= x; }
else { last.next = x; }
last = x;
}
}
这里最大的困惑是,如果链表是空的,并且我正在尝试添加新节点,例如:add(1);
第一个节点的数据将为1,第一个节点下一个节点将为null并且最后一个节点的数据将为1,并且最后一节点的下一个节点将为空
现在,如果我添加了一个新节点,例如:add(2);
最后一个节点的数据将为2,最后一个结点的下一个将为空第一个节点的数据仍然是1,但第一个节点下一个节点将是尾部,这是怎么发生的?
单独链接列表中的head.next是如何更改的?
在single-linkedlist中head.next是如何更改的?
简短回答:因为最初first
和last
都指向同一个节点,所以last.next = x
间接导致first.next = x
。
长答案:
它的核心是对象引用(指针)。
要理解它为什么会改变,首先你必须了解指针在Java中是如何工作的,Java是一种非常高级的语言,在这里你可以避免所有讨厌但乏味的内存操作。
tldr;我想推荐你看这个史诗般的视频,谈论指针,宾基指针视频
指针在Java中的工作原理
1.内存分配:
下面的语句分配新的内存来保存节点值(data&next)。x
现在保持地址值,即:0x1ACD
到实际节点值。换句话说,x
是一个节点指针。
node x = new node(data,null); //0x1ACD
2.指针取消引用:
为了访问实际的节点值,我们需要取消对地址值的引用。当您访问Object中的属性(即:node.Next
)时,指针将被取消引用。以下语句取消引用last
和对象分配将x
中的地址值复制到next
(last
中的属性)。
last.next = x;
3.对象分配:
由于内部Java设计(内存效率),对象分配只复制地址值,而不是克隆整个节点值(在现实世界中,它可能很大)。
first
和last
现在保持与x
相同的地址值,即实际节点值的地址。
public void add(int data){
...
first = x;
last = x;
...
}
你可能会问,这有什么了不起的地方?举以下例子:
public void add(int data){
...
first = x;
last = x;
last.next = new node(123,null);
...
}
请注意,现在,last.next == first.next
。通过last
更改节点值似乎也会修改first
,因为它们都指向相同的节点值。
这是如何在单链列表中更改head.next的完整答案
参考文献:
- https://softwareengineering.stackexchange.com/questions/207196/do-pointers-really-exist-in-java
- https://softwareengineering.stackexchange.com/questions/141834/how-is-a-java-reference-different-from-a-c-pointer
插图:
步骤1:当我们add(1)
到一个空列表时:
public void add(int data) {
...
first= x;
...
last = x;
...
}
// after:
[1] <- first, last // first and last now pointing to the same `node:x`.
|
[/] <- first.next, last.next // first.next, last.next pointing to null.
步骤2:现在,当我们add(2)
到上一个列表(非空)时:
// before:
[1] <- first, last
|
[/] <- first.next, last.next // first.next, last.next pointing to null.
public void add(int data) {
...
// technically, since first and last pointing to the same node[1],
// x actually being assigned to both first.next and last.next.
last.next= x;
...
last = x; // update last from previous-x to current-x.
...
}
// after:
[1] <- first
|
[2] <- last // first.next, last is pointing to the new `node:x`.
|
[/] <- last.next // last.next to null.
步骤3等:现在,让我们add(3)
或列表中的后续值(非空):
// before:
[1] <- first
|
[2] <- last
|
[/] <- last.next // last.next to null.
public void add(int data) {
...
last.next= x; // access previous-x and assign x to previous-x's next.
...
last = x; // update last from previous-x to current-x.
...
}
// after:
[1] <- first
|
[2]
|
[3] <- last // last is pointing to the new `node:x`.
|
[/] <- last.next // last.next to null.
注:
first
总是指向列表中的第一个节点。last
总是指向列表中的最后一个节点。last.next
总是指向null。通过Add()
,新节点总是通过分配给last.next
而附加到列表的末尾。