链表定义
链表(Linked list)是一种在物理上不连续、非顺序的数据结构,由若干个节点(Node)组成。链表中元素的逻辑顺序是通过链表中的指针链接次序实现的。
主要具有以下特点:
- 相邻两个数据元素之间有指针连接。
- 最后一个元素的后续指针为NULL(双端链表,循环链表除外)。
- 链表与数组比较相比较,链表的空间能够按需分配,没有内存空间的浪费(但是元素间相连接的指针需要一些额外的开销)。
- 链表中会有至少两个部分:数据项,以及引用指针。
- 链表可以在常数时间内扩展(时间复杂度O(1))。
- 链表在访问某个元素时开销比较大(时间复杂度O(N))。
存储原理
链表在内存中的存储方式是随机存储(链式存储)的, 链表的每一个节点分布在内存的不同位置, 依靠next指针关联起来。
单向链表
单向链表包含多个结点,每个结点有一个指向后续元素的next指针,表中的最后一个结点的next指针值为NULL,标识链表的结束。具体模型如下:
单项链表的结点类:
package com.xxliao.datastructure.linerar_list.linked_list.single;
/**
* @author xxliao
* @description: 单向链表节点类
* @date 2024/5/28 12:31
*/
public class Node {
//存储数据
private Object data;
//下个节点
public Node next;
//构造
public Node(Object data) {
this.data = data;
}
//toString查看输出
public String toString() {
return "Node [data=" + data + ", next=" + next + "]";
}
}
单向链表实现:
package com.xxliao.datastructure.linerar_list.linked_list.single;
/**
* @author xxliao
* @description: 单向链表
* @date 2024/5/28 12:32
*/
public class SingleLinkedList {
//表头结点
private Node head;
//链表长度
private int size;
//构造,使表头节点为null
public SingleLinkedList() {
head = null;
size = 0;
}
//是否为空,判断表头是否为空
public boolean isEmpty() {
return head == null;
}
//在开始位置添加
public void addFirst(Object data) {
//创建新节点
Node addNode = new Node(data);
//使新节点的next指向链表的表头
addNode.next = head;
//表头指向新节点
head = addNode;
//长度加1
size++;
}
//从表头开始删除
public Object deleteFirst() {
Node temp = head;
head = head.next;
size--;
return temp.data;
}
//在末尾位置添加
public void addLast(Object data) {
//创建新节点
Node addNode = new Node(data);
//当前节点
Node current = head;
//循环找出最末节点
while(current.next != null) {
current = current.next;
}
//最末节点指向新节点
current.next = addNode;
size++;
}
//查看链表的所有元素
public void display() {
//当前节点
Node current = head;
//循环找出最末节点
while(current.next != null) {
System.out.println(current.toString());
current = current.next;
}
}
//判断某元素是否在链表中
public boolean contain(Object data) {
//当前节点
Node current = head;
if(data == null) {
//循环找出最末节点
while(current.next != null) {
if(current.data == null) {
return true;
}
current = current.next;
}
}else {
//循环找出最末节点
while(current.next != null) {
if(data.equals(current.data)) {
return true;
}
current = current.next;
}
}
return false;
}
//删除链表中的某元素
public Object delete(Object data) {
//当前节点
Node current = head;
//前一个节点
Node pred = head;
if(data == null) {
//循环找出最末节点
while(current.data != null) {
if(current.next == null) {
return null;
}
pred = current;
current = current.next;
}
}else {
//循环找出最末节点
while(!data.equals(current.data)) {
if(current.next == null) {
return null;
}
pred = current;
current = current.next;
}
}
if(current == head) {
deleteFirst();
}else {
pred.next = current.next;
}
size--;
return current.data;
}
}
双端链表
双端链表在单向链表的实现上多了一个成员变量(last)记录表尾节点,因此双端链表的特点是可以快速的找到表头表尾,在尾部插入以及删除元素时效率将会有很大的提高。双端链表模型如下:
双端链表节点类:
package com.xxliao.datastructure.linerar_list.linked_list.doubly_end;
/**
* @author xxliao
* @description: 双端链表节点类
* @date 2024/5/28 12:46
*/
public class Node {
//存储数据
public Object data;
//下个节点
public Node next;
//构造
public Node(Object data) {
this.data = data;
}
//toString查看输出
public String toString() {
return "Node [data=" + data + ", next=" + next + "]";
}
}
双端链表实现:
package com.xxliao.datastructure.linerar_list.linked_list.doubly_end;
/**
* @author xxliao
* @description: 双端链表实现类
* @date 2024/5/28 12:47
*/
public class DoublyEndLinkedList {
//表头节点
private Node head;
//表尾节点
private Node tail;
//链表长度
private int size;
//构造,使表头节点为null
public DoublyEndLinkedList() {
head = null;
tail = null;
size = 0;
}
//是否为空,判断表头是否为空
public boolean isEmpty() {
return head == null;
}
//在开始位置添加
public void addFirst(Object data) {
//创建新节点
Node addNode = new Node(data);
//使新节点的next指向链表的表头
addNode.next = head;
//表头指向新节点
head = addNode;
//长度加1
size++;
}
//从表头开始删除
public Object deleteFirst() {
if(isEmpty()) {
return null;
}
Node temp = head;
head = head.next;
size--;
return temp.data;
}
//在末尾位置添加
public void addLast(Object data) {
Node addNode = new Node(data);
if(isEmpty()) {
//空链表,first,last均为addNode
head = addNode;
}else {
tail.next = addNode;
}
//最末节点指向新节点
tail = addNode;
size++;
}
//查看链表的所有元素
public void display() {
//当前节点
Node current = head;
//循环找出最末节点
while(current.next != null) {
System.out.println(current.toString());
current = current.next;
}
}
//判断某元素是否在链表中
public boolean contain(Object data) {
//当前节点
Node current = head;
if(data == null) {
//循环找出最末节点
while(current.next != null) {
if(current.data == null) {
return true;
}
current = current.next;
}
}else {
//循环找出最末节点
while(current.next != null) {
if(data.equals(current.data)) {
return true;
}
current = current.next;
}
}
return false;
}
}
双向链表
双向链表的每个节点均有一个前驱节点记录指向上一节点,有一个后驱节点指向下一节点,表尾节点后驱节点为NULL。因为双向链表节点间相互指引,那么在查找过程中可以双向遍历。
Java代码,链表节点声明如下:
package com.xxliao.datastructure.linerar_list.linked_list.doubly;
/**
* @author xxliao
* @description: 双向链表的节点类实现
* @date 2024/5/28 12:54
*/
public class Node {
//数据
public Object data;
//前驱结点
public Node pre;
//后驱结点
public Node next;
//构造方法
public Node(Object data) {
this.data = data;
}
//toString
@Override
public String toString() {
return "DLLNode [data=" + data + ", pre=" + pre + ", next=" + next + "]";
}
}
Java代码,双向链表(这里采用双向双端链表)的实现如下:
package com.xxliao.datastructure.linerar_list.linked_list.doubly;
/**
* @author xxliao
* @description: 双向双端链表的实现类
* @date 2024/5/28 12:55
*/
public class DoublyLinkedList {
// 表头节点
private Node head;
// 表尾节点
private Node tail;
// 链表长度
private int size;
// 构造方法,使表头、表尾节点为null
public DoublyLinkedList() {
head = null;
tail = null;
size = 0;
}
// 判断是否为空
public boolean isEmpty() {
return head == null;
}
// 从表头添加
public void addFirst(Object data) {
Node newNode = new Node(data);
if (isEmpty()) {
// 空链表,表头表尾都是新结点
tail = newNode;
} else {
// 不是空链表,将新结点添加在first的前驱结点上
head.pre = newNode;
}
// 新结点的后驱结点指向原来的first结点(原来链表表头),双向指定
newNode.next = head;
// 新结点作为表头节点
head = newNode;
size++;
}
// 从表头删除
public Object deleteFirst() {
Node deleteNode = head;
if (head.next == null) {
// 只有一个元素,last置为null
tail = null;
} else {
// 将原表头结点的后驱结点(将作为新的表头结点)的前驱结点置为null,
head.next.pre = null;
}
head = head.next;
size--;
return deleteNode.data;
}
// 从表尾添加
public void addLast(Object data) {
Node newNode = new Node(data);
if (isEmpty()) {
// 空链表,表头表尾都是新结点
head = newNode;
} else {
// 不是空链表,将新结点添加在last的后驱结点上
tail.next = newNode;
}
// 新结点的前驱结点指向原来的last结点(原来链表表尾)
newNode.pre = tail;
// 新结点作为表尾节点
tail = newNode;
size++;
}
// 从表尾删除
public Object deleteLast() {
Node deleteNode = tail;
if (head.next == null) {
// 只有一个元素,first置为null
head = null;
} else {
// 将原表尾结点的前驱结点(将作为新的表尾结点)的后驱结点置为null,
tail.pre.next = null;
}
tail = tail.pre;
size--;
return deleteNode.data;
}
// 添加在某元素后面
public boolean addAfter(Object d, Object data) {
// 当前节点
Node current = head;
while (!d.equals(current.data)) {
// 不相等,继续查找
if (current.next == null) {
// 没有找到
return false;
}
}
Node newNode = new Node(data);
if (current == tail) {
// 表尾添加
addLast(data);
} else {
// 中间添加
newNode.next = current.next;
current.next.pre = newNode;
}
current.next = newNode;
newNode.pre = current;
size++;
return true;
}
//判断链表是否包含某元素
public boolean contain(Object data) {
//当前节点
Node current = head;
if(data == null) {
// 遍历,查找是否有元素相同存在
while(current.next != null) {
if(current.data == null) {
return true;
}
}
}else {
// 遍历,查找是否有元素相同存在
while(current.next != null) {
if(data.equals(current.data)) {
return true;
}
}
}
return false;
}
}
完整代码
https://github.com/xxliao100/datastructure_algorithms.git