链表基本的单元是节点Node。
单向链表中每个节点都由两部分组成:存储的数据和下一个节点的内存地址Node。
双向链表则在单向链表的基础上,多了一个Node,用来存储上一个节点的内存地址。
对比下面的两张图,很容易就能理解“单向”和“双向”。
![单向链表(图源B站老杜)](https://i-blog.csdnimg.cn/blog_migrate/ad5b356f8c4125baeef6763f20c3dea9.png)
![双向链表(图源B站老杜)](https://i-blog.csdnimg.cn/blog_migrate/202755d1c0d5ee7d8e433b65e4b4558c.png)
图看懂了,下面我们来看看Java中如何实现单项链表。
类
Node类
上面说过了,每一个节点Node中都由两部分组成:下一个节点的内存地址(它也是一个节点Node)以及存储的数据。
所有一个Node类应该有两个实例变量Node next和Object data,也可以使用泛型,声明为Node<E> next和E data。
SingleLink类
一个单向链表类应该包含一个节点Node,单向链表的长度int size。在程序中我把这个节点Node命名为header(头节点)。此外,SingleLink类中还要有两个指针Node<E> last和Node<E> next,分别指向当前节点的上一个节点和下一个节点,方便程序的编写。
Test类
用于测试SingleLink中的方法,不多赘述了。
类中的方法
Node类
重写toString()、hashCode()、equals()三个方法,提供有参和无参两个构造方法,没什么好说的。
Single类
最基本的“增删改查”四个方法要有,其他的写不写都无所谓。
boolean add(E data):增加元素;
boolean remove(E data):删除元素;
void set(int index,E newData):根据下标修改元素;
E get(int index):根据下标获取元素;
boolean contains(E data):判断链表中是否包含某个元素;
boolean isEmpty():判断链表是否为空;
boolean checkIndex():由set()和get()两个方法调用,判断提供的下标是否正确。
Node<E> findLast():由add()方法调用,查找尾节点;
Node<E> node(int index):根据下标查找节点,配合checkIndex()方法使用;
void unlink(Node<E> removedNode): 让被删除的元素所在的节点removedNode的上一个节点不再指向它。
代码
代码如下,我写了好久,写到自己都晕了,如有不妥之处欢迎指出。
Node类:
import java.util.Objects;
public class Node<E> {
E data;
Node<E> next;
/*---------------------------- 构造方法 ----------------------------*/
public Node() {
}
public Node(E data, Node<E> next) {
this.data = data;
this.next = next;
}
/*---------------------------- 重写的方法 ----------------------------*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Node)) return false;
Node<?> node = (Node<?>) o;
return Objects.equals(data, node.data) &&
Objects.equals(next, node.next);
}
@Override
public int hashCode() {
return Objects.hash(data, next);
}
@Override
public String toString() {
return "Node{" +
"data=" + data +
", next=" + next +
'}';
}
}
SingleLink类:
//每个单链表都有一个头节点和长度
public class SingleLink<E> {
Node<E> header;
int size;
// 指针指向上一个节点
Node<E> last;
// 指针指向下一个节点
Node<E> next;
/**
* 将元素添加到链表中
* @param e 要添加的元素
* @return 添加成功返回true,否则返回false
*/
public boolean add(E e){
if (null == header){ //如果头节点为空
header = new Node<E>(e,null);
size++;
System.out.println("添加成功!");
return true;
}
else {
Node<E> node = findLast(header);
node.data = e;
node.next = null;
last.next = node;
size++;
System.out.println("添加成功!");
return true;
}
}
/**
* 查找尾节点
* @param node 从哪个节点开始找
* @return 返回尾节点
*/
private Node<E> findLast(Node<E> node) {
if (null == node){
return new Node<E>();
}
last = node; // 指向尾节点的上一个节点
return findLast(node.next);
}
/**
* 从链表中删除元素
* @param e 要删除的元素
* @return 删除成功返回true,否则返回false
*/
public boolean remove(E e){
// 如果要删除的元素为空
if(null == e) {
for (Node<E> currentNode = header; currentNode != null;) {
if (null == currentNode.next.data) {
last = currentNode;
next = null;
unlink(currentNode.next);
System.out.println("删除成功");
return true;
}
}
}
else{
// 重置指针
last = null;
next = null;
for (Node<E> currentNode = header; currentNode != null;){
if(e.equals(currentNode.data)){
unlink(currentNode);
System.out.println("删除成功");
return true;
}
last = currentNode; // 指针指向当前节点的上一个节点
currentNode = currentNode.next;
if (currentNode == null){
next = null;
}
else {
next = currentNode.next; // 指针指向当前节点的下一个节点
}
}
}
System.out.println("删除失败,元素不存在");
return false;
}
/**
* 让被删除的元素所在的节点removedNode的上一个节点不再指向它
* @param removedNode 被删除的元素所在的节点
*/
private void unlink(Node<E> removedNode){
// removedNode一定不为空
if (last == null && removedNode.next != null){ //如果被删除的节点是头节点
header = removedNode.next; // 头节点被删除了,它的下一个节点成为了头节点
}
else if (null == removedNode.next){ // 如果被删除的元素所在的节点是尾节点
last = null;
}
else {
// 如果不是尾节点,就让当前节点的”上一个节点的next“指向当前节点的下一个节点。
last.next = removedNode.next;
}
removedNode.data = null;
size--;
}
/**
* 查找链表中是否包含指定的元素
* @param data 要查找的元素
* @return 如果包含要查找的元素则返回true,否则返回false
*/
public boolean contains(E data){
if (null == data){
return true;
}
else {
for (Node<E> currentNode = header; currentNode != null; currentNode = currentNode.next) {
if (data.equals(currentNode.data)) {
return true;
}
}
}
return false;
}
/**
* 修改节点中的元素
* @param index 被修改的元素的下标
* @param newData 修改为哪个元素
*/
public void set(int index,E newData){
if (checkIndex(index)) { // 下标一定正确
node(index).data = newData;
}
}
/**
* 根据下标取出元素
* @param index 要取出的元素所在的下标
* @return 返回要取出的元素
* @throw 如果下标不正确就抛出下标越界异常
*/
public E get(int index){
if (checkIndex(index)){
return node(index).data;
}
else {
throw new IndexOutOfBoundsException("下标【"+index+"】超过了链表的长度【"+size+"】");
}
}
/**
* 根据下标查找节点
* @param index 提供的下标
* @return 返回下标对应的节点
*/
private Node<E> node(int index){
// 下标一定正确
Node<E> gettedNode = null;
if (0 == index){
gettedNode = header;
}
else if (index == size){
gettedNode = last;
}
else{
Node<E> current = null;
gettedNode = header;
for (int count = 0; count < index; count++) {
current = gettedNode;
if (null == gettedNode){
gettedNode = current;
break;
}
gettedNode = gettedNode.next;
}
}
return gettedNode;
}
/**
* 检查下标是否正确
* @param index 要检查的下标
* @return 下标正确返回true,否则返回false
*/
private boolean checkIndex(int index){
if (index<0 || index >=size){
return false;
}
return true;
}
/**
* 检查链表是否为空
* @return 为空则返回true,否则返回flase
*/
public boolean isEmpty(){
return size == 0;
}
}
测试类就不放出来了。
感觉挺乱的。。。