链表是一种数据结构,不同与集合,链表添加、删除容易,查找慢。因为链表添加时增加和节点的连接就行,删除时断开和节点的连接也就可以了。
刚手写了一个单链表的简单实现代码,将自己的感悟写出来。
单链表的数据结构比较简单,包含有一个Node节点,节点代码如下。
class Node {
T data;
Node next;
public Node(T data, Node next) {
this.data = data;
this.next = next;
}
}
因为单链表只指向下一个节点,所以只有next。双链表执行前后节点,所以有pre和next两项。因为每一个节点都是数据,所以是Node类型。在调用单链表的构造方法的时候,会自动指向下一个节点next。
在单链表默认定义了一个list节点和一个size变量,代码如下:
Node list;
int size;// 表示有多少个节点
list节点表示链表的头节点,也就是head节点,size表示链表的大小。
接下来列举一下单链表的增删改查方法,首先说一下添加数据的方法,put方法,代码如下:
// 添加节点
// 在链表头部添加节点
public void put(T data) {
Node head = list;
Node curNode = new Node(data, list);
list = curNode;
size++;
}
该方法时往链表头部添加节点的方法。代码解读:将list节点赋值给head节点,表示头节点,用Node的构造方法,new一个node,将传入的list指代data的下一个节点,将new出来的节点指向头节点,这样就完成了头部添加节点的操作。
在指定位置添加节点,需要传入一个index表示插入的位置,一个data,表示插入的节点数据。需要对出传入的index做判断,避免超出链表的大小或是值小于0。如果超出范围,判读那越界,判断的代码如下:
public void checkPositionIndex(int index) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("index: " + index + ",size: " + size);
}
}
在指定位置添加节点的代码操作如下:
// 指定位置添加节点
public void put(int index, T data) {
checkPositionIndex(index);
Node head = list;
Node curNode = new Node(data, list);
for (int i = 0; i < index; i++) {
head = curNode;
curNode = curNode.next;
}
Node node = new Node(data, curNode);
head.next = node;
size++;
}
代码解读: 首先对插入节点的位置做判断,判断当前节点是否合法,有没有越界。然后定义一个head节点和一个当前节点,当前节点指向头节点,用轮询的方式去找到要插入的位置。在轮询的过程中,把当前节点的前一个节点赋值给head节点,用next方式去找下一个节点。在轮询到index时,找到了当前节点的前一个节点和当前节点,轮询结束。然后用new的方法,将要插入的数据的next节点指向当前节点,将当前节点的上一个节点指向new出来的节点。
增加的方法就这两个,接下来说一下删除的方法,remove方法,先看代码:
// 删除节点
// 删除链表头部节点
public T remove() {
if (list != null) {
Node node = list;
list = list.next;
node.next = null;// GC回收
size--;
return node.data;
}
return null;
}
代码解读: 因为list节点默认指头部节点,所以先判断list是否为空。判断为空,返回null;判断不为空,继续执行。将list赋值给node节点,将list的下一个节点赋值给list节点。调用node节点的next方法断开连接,将node节点孤立,便于GC回收。如果不断开节点,可能node节点会指向其他的节点。然后将size–,返回删除的node的data。
// 删除指定位置的节点
public T remove(int index) {
checkPositionIndex(index);
Node head = list;
Node curNode = list;
for (int i = 0; i < index; i++) {
head = curNode;
curNode = curNode.next;
}
head.next = curNode.next;
curNode.next = null;// GC
return curNode.data;
}
这个和put方法类似,可以自己理解一下。
// 删除链表尾部节点
public T removeLast() {
if (list != null) {
Node head = list;
Node curNode = list;
while (curNode.next != null) {
head = curNode;
curNode = curNode.next;
}
curNode.next = null;
size--;
return curNode.data;
}
return null;
}
代码解读: 删除尾部节点也是采用轮询的方式,curNode指代要删除的最后一个节点的上一个节点,curNode.next表示最后一个节点也就是要删除的节点,head的作用就是用来存curNode值。
通过curNode.next = null; 这种用断开上一个节点指向下一个节点链接的方式孤立最后一个节点,使其被GC回收,删除节点。
// 修改节点
public void set(int index, T data) {
checkPositionIndex(index);
Node head = list;
for (int i = 0; i < index; i++) {
head = head.next;
}
head.data = data;
}
修改节点的方法不细讲,可以参考上面的逻辑思考一番。
// 查询头部节点
public T get() {
Node head = list;
if (head != null) {
return head.data;
} else {
return null;
}
}
// 查询指定位置的节点
public T get(int index) {
checkPositionIndex(index);
Node node = list;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node.data;
}
查询头部节点和查询指定位置的节点也不细讲,逻辑类似
以上是笔者自己的总结,有疏漏的地方,欢迎各位指正。
笔者QQ:1391197821
邮箱:ssrswk@163.com