双向链表、双端(双向)链表、循环(双向)链表示意(简)图:
声明:下面各图中,箭头指向的是整个节点,而不是指向节点中的prior或next。
双向链表:只有一个指针指向最开始的结点。
双端(双向)链表:有两个指针分别指向两端的节点。
循环(双向)链表:指向形成一个闭环。
双端(双向)链表的Java实现(简单示例):
注:双向链表、双端(双向)链表、循环(双向)链表的实现思路几乎一样,这里以实现双端(双向)链表示例。
/**
* 双端(双向)链表的(简单)实现
*
* @author JustryDeng
* @date 2019/5/21 20:51
*/
public class DoublesidedLinkedlistImpl<T> {
/** 节点的个数 */
private int size;
/**
* 尾节点
* 注:本人新增节点时,从尾巴上新增
* 注:也可以从头上进行新增节点
*/
private Node rear;
/**
* 当前节点
* 即:链表中最前面的节点,可理解为 队列中的头结点
*/
private Node head;
/**
* 成员内部类, 节点
*/
private class Node {
/** 内部类的元素,Object类型 */
private T data;
/** 上一个节点(即:前驱节点)的地址 */
private Node prior;
/** 下一个节点(即:后继节点)的地址 */
private Node next;
private Node(T data) {
this.data = data;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public Node getPrior() {
return prior;
}
public void setPrior(Node prior) {
this.prior = prior;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
/**
* 新增节点
*
* @param obj
* 添加的元素
* @return 添加的元素对应的节点
* @date 2019/5/20 20:48
*/
public Node addNode(T obj) {
// obj不能为null
notNullCheck(obj);
// 每次增加的元素,都作为新的尾节点
Node newRear = new Node(obj);
if (size == 0) {
head = newRear;
rear = newRear;
} else {
newRear.prior = rear;
rear.next = newRear;
// 更新尾节点
rear = rear.next;
}
size++;
return newRear;
}
/**
* 删除当前(头)节点
*
* @return 被删除了的(头)节点
* @date 2019/5/20 20:50
*/
public Node deleteHead() {
if(isEmpty()) {
return null;
}
// targetObj存放原来的头节点数据
Node targetNode = head;
if (head.next != null) {
// 断开后继节点对要删除的节点的引用
head.next.prior = null;
}
// 更新头节点
head = head.next;
size--;
return targetNode;
}
/**
* 删除指定元素对应的第一个节点
*
* @return 删除成功与否(若元素不存在,则返回false)
* @date 2019/5/20 20:50
*/
public boolean deleteFirstNode(T obj) {
// obj不能为null
notNullCheck(obj);
// 当head未null时,说明,当前为空链表
if (size == 0) {
return false;
}
// 当前节点
Node current = head;
// 前驱节点
Node previous = head;
// 判断条件,如果当前节点的值和待删除的值不一致,则继续
while (!obj.equals(current.data)) {
// 如果下一个节点不存在(即:当前节点是尾节点)
// 则说明,不存在 元素值为obj的节点,直接返回false
if (current.next == null) {
return false;
} else {
// 更新前驱节点、当前节点
previous = current;
current = current.next;
}
}
// 如果要删除的节点是第一个节点;
if (head.equals(current)) {
if (head.next != null) {
// 断开后继节点对要删除的节点的引用
head.next.prior = null;
}
// 更新头结点
head = current.next;
// 如果要删除的节点是尾节点
} else if ((rear.equals(current))) {
// 前驱节点断开对要删除的节点的应用
previous.next = null;
// 如果要删除的节点不是头结点(是中间的某个节点或尾节点)
} else {
// 将当前节点“抠除”,并将“两端”接上
previous.next = current.next;
current.prior = previous;
}
size--;
return true;
}
/**
* 删除指定元素对应的所有节点
* 注:此方法性能较低,少用或不用
*
*
* @return 删除了的节点数
* @date 2019/5/20 20:50
*/
public int deleteAllNode(T obj) {
int count = 0;
while(deleteFirstNode(obj)){
count++;
}
return count;
}
/**
* 查找元素的(第一个)节点
*
* @param obj
* 待查找的元素对象
* @return 元素的(第一个)节点, 无则返回null
*/
public Node find(T obj) {
// obj不能为null
notNullCheck(obj);
Node currNode = head;
// 临时大小为size
int tempSize = size;
while (tempSize > 0) {
if (obj.equals(currNode.data)) {
return currNode;
} else {
currNode = currNode.next;
}
tempSize--;
}
return null;
}
/**
* 非空检验
*
* @param obj
* 被验证的对象
* @throws NullPointerException 空指针异常(运行时异常)
* @date 2019/5/20 21:00
*/
private void notNullCheck(T obj){
if(obj == null) {
throw new NullPointerException("data must not be null!");
}
}
/**
* 判断链表是否为空
*
* @return 链表是否为空
* @date 2019/5/20 20:49
*/
public boolean isEmpty() {
return size == 0;
}
@Override
public String toString() {
if (isEmpty()) {
return "[]";
}
StringBuilder sb = new StringBuilder(16);
sb.append("[");
Node currentNode = head;
for (int i = 0; i < size; i++) {
sb.append(currentNode.data);
sb.append(", ");
currentNode = currentNode.next;
}
int length = sb.length();
sb.delete(length - 2, length).append("]");
return sb.toString();
}
public static void main(String[] args) {
DoublesidedLinkedlistImpl<String> uli = new DoublesidedLinkedlistImpl<>();
// 增
uli.addNode("A");
uli.addNode("B");
uli.addNode("C");
uli.addNode("C");
uli.addNode("D");
uli.addNode("C");
uli.addNode("E");
uli.addNode("E");
uli.addNode("F");
uli.addNode("E");
// toString
System.out.println("增加节点后的初始化链表:\t" + uli);
// 找到C元素对应的第一个节点
System.out.println("找到C元素对应的第一个节点:" + uli.find("C")
+ ",该节点的下下个节点的数据内容为:"
+ uli.find("C").getNext().getNext().getData());
// 删除头结点
uli.deleteHead();
// toString
System.out.println("删除头结点后:\t" + uli);
// 删除C元素对应的第一个节点
uli.deleteFirstNode("C");
// toString
System.out.println("删除C元素对应的第一个节点后:\t" +uli);
// 删除E元素对应的所有节点
uli.deleteAllNode("E");
// toString
System.out.println("删除E元素对应的所有节点后:\t" +uli);
}
}
测试一下:
运行(写在实现类里面的)主方法,控制台输出:
由此可见:简单的双端(双向)链表实现完毕!