算法通关村第一关 —— 链表青铜挑战笔记
开门见山,本文简单介绍了数据结构中的链表,并使用java实现一个简单的demo;
now action
什么是链表?
链,顾名思义为链子、锁链,其特性为一环扣一环、线性排列;
好比现在Bob在茫茫人海中找Alice,但他只有一个未知联系人的电话号码(头节点地址),且每个人最多只有一份别人的电话号码(节点地址),现在Bob拨打手头上的电话(头节点),确认一下对方"Are you Alice(节点数据)? No",遂获取该陌生人持有的电话号码call下一位(遍历),如此,到某一位仁兄时,发现"Are you Alice? Yes",即开启了他们的传奇;
curd之旅
既然牵扯到数据,就离不开增删改查,下面就简单的叙述一下链表的curd;
先上完整版
package Elin.first_level;
public class Link<T> {
private Node<T> head;
private Node<T> tail;
private int length;
public Link() {
}
public int getLength() {
return this.length;
}
public void append(Node<T> addElement) {
if (addElement == null){
throw new NullPointerException("节点为空");
}
if (this.length == 0){
this.tail = addElement;
this.head = addElement;
this.length++;
return;
}
addElement.setNextNode(null);
this.tail.setNextNode(addElement);
this.tail = addElement;
this.length++;
}
//增
public void add(Node<T> head, Node<T> addElement, int position) {
if (addElement == null){
throw new NullPointerException("节点为空");
}
//头元素
if (position == 0) {
addElement.setNextNode(head);
head = addElement;
this.length++;
return;
}
//尾元素
if (position == this.length - 1) {
this.tail.setNextNode(addElement);
this.tail = addElement;
this.length++;
return;
}
Node<T> node = get(position);
Node<T> next = node.getNext();
node.setNextNode(addElement);
addElement.setNextNode(next);
this.length++;
}
//删
public void remove(int position) {
//头元素
if (position == 0) {
head = head.getNext();
this.length--;
return;
}
Node<T> node = get(position - 1);
Node<T> target = node.getNext();
//先赋值,再断开
node.setNextNode(target.getNext());
target.setNextNode(null);
//若为最后一个元素,尾节点替换
if (position == this.length - 1) {
this.tail = node;
}
this.length--;
}
//改
public void set(int position, T val) {
Node<T> node = get(position);
node.setVal(val);
}
//查
public Node<T> get(int position) {
if (this.head == null) {
throw new NullPointerException("目前为空链表");
}
if (position >= length || position < 0) {
throw new NullPointerException("下标超出范围");
}
int len = 0;
Node<T> node = head;
while (len != position) {
len++;
node = node.getNext();
}
return node;
}
}
class Node<T> {
/*
* 值
* */
private T val;
/*
* 下一个节点
* */
private Node<T> nextNode;
private Node() {
}
public Node(T val) {
this.val = val;
this.nextNode = null;
}
public T getVal() {
return this.val;
}
public void setVal(T val) {
this.val = val;
}
public Node<T> getNext() {
return this.nextNode;
}
public void setNextNode(Node<T> nextNode) {
this.nextNode = nextNode;
}
}
查
从头节点开始遍历,获取相应位置的元素
public Node<T> get(int position) {
if (this.head == null) {
throw new NullPointerException("目前为空链表");
}
if (position >= length || position < 0) {
throw new NullPointerException("下标超出范围");
}
int len = 0;
Node<T> node = head;
while (len != position) {
len++;
node = node.getNext();
}
return node;
}
增
在给定位置后追加一个元素,当position = 0时表示为头节点,只需要把新增的节点nextNode设置为当前头节点,然后链表中的头节点换成当前节点就行;当position为链表长度-1时为尾节点,即把尾节点的nextNode设置为新增节点的地址
此处设置了一个变量length保存当前链表的长度,在增删操作中维护,以便获取长度时的效率更高,当然,添加一个方法遍历获取长度也可以;
此处有个需要注意的地方,当新增的位置在中间时,需要用临时变量保存当前位置节点(node )的nextNode,或者先把新增节点(addElement)的nextNode赋值为当前位置节点的nextNode,再把当前位置节点(node )的nextNode设置为新增节点(addElement)
//增
public void add(Node<T> head, Node<T> addElement, int position) {
if (addElement == null){
throw new NullPointerException("节点为空");
}
//头元素
if (position == 0) {
addElement.setNextNode(head);
head = addElement;
this.length++;
return;
}
//尾元素
if (position == this.length - 1) {
this.tail.setNextNode(addElement);
this.tail = addElement;
this.length++;
return;
}
Node<T> node = get(position);
Node<T> next = node.getNext();
node.setNextNode(addElement);
addElement.setNextNode(next);
this.length++;
}
删
入参为删除的节点位置(当然根据值的内容删除也可以),当position = 0时,删除头节点,直接把头节点换成下一个节点就行(head = head.getNext()),失去引用的头节点会在young gc时回收掉,当position == this.length - 1时,表示删除尾节点,此时获取倒数第二个节点,把该节点的nextCode设置为null即可;
若为其他的节点,则老老实实的遍历,把坐标节点的nextCode替换为下下个节点的地址;
当position == this.length - 1,即为删除尾节点,此处用一变量tail保存了尾节点的值,以便在新增尾节点时效率更高
//删
public void remove(int position) {
//头元素
if (position == 0) {
head = head.getNext();
this.length--;
return;
}
Node<T> node = get(position - 1);
Node<T> target = node.getNext();
//先赋值,再断开
node.setNextNode(target.getNext());
target.setNextNode(null);
//若为最后一个元素,尾节点替换
if (position == this.length - 1) {
this.tail = node;
}
this.length--;
}
改
改就简单了,获取坐标节点,更换下值
//改
public void set(int position, T val) {
Node<T> node = get(position);
node.setVal(val);
}
拓展
此处仅为一个简单的单向链表的demo,链表还有双向链表、环(循环链表),双向环(双向循环链表)等分支,各具特色,如:
双向链表
每个节点不仅记录下一个节点的地址,并且同时记录着上一个节点的地址,此时,单行道变为双行道,数据的遍历方式可以有两种方向
环(循环链表)
在单项链表的基础上,为节点的下一个节点为头节点,形成一个环
双向环(双向循环链表)
在循环链表的基础上,加上双向链表的特性,使之成为两个方向互通的环
位置太窄,后面这几个就不上代码了,绝对不是因为懒得写(手动狗头)