目录
文章目录
Java数据结构之线性表
链表结构
链式存储结构是基于指针实现,我们把一个数据元素和一个指针成为节点
链式存储结构是用指针把相互直接关联的结点(即直接前驱节点或直接后继节点)链接起来。 链式存储结构的线性表成为链表
链表类型
根据链表的构造方式的不同可以分为:
1. 单向链表
2. 循环链表
3. 双向链表
线性表的抽象数据类型
1、线性表的置空操作 | clear() |
2、线性表判空操作: | isEmpty() |
3、求线性表的长度: | size( ) |
4、取元素操作: | get( i ) |
5、插入操作: | insert( i, x ) |
6、删除操作: | delete( i) |
双向链表
1、基本概念
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。
2、图形理解
相对于单链表而言,每个节点不仅有一个next,而且有一个指向前一个元素的pre
3、链表特性
每个数据结点中都有两个指针,分别指向直接后继和直接前驱
4、优缺点
优点
可以找到前驱和后继,可进可退
缺点
增加删除节点复杂,多需要分配一个指针存储空间
优缺点总结
适用于需要双向查找节点值的情况
5、代码展示
5.1、基础代码
/** 保存该链表的头结点 */
private Node head;
/** 保存该链表的尾结点 */
private Node tail;
/** 保存该链表中已包含的节点数 */
private int size;
/** 内部类 */
private class Node {
// 保存节点数据
private T data;
// 指向上个节点的引用
private Node prev;
// 指向下个节点的引用
private Node next;
// 无参构造器
public Node() {
}
// 初始化构造器
public Node(T data) {
this.data = data;
}
public Node(T data, Node prev) {
this.data = data;
this.prev = prev;
}
public Node(T data, Node prev, Node next) {
this.data = data;
this.prev = prev;
this.next = next;
}
}
5.2、插入数据
a、链表为空时
/**
* <p>以指定数据元素来创建链表,Ps.(当前链表为空)</p>
*
* @param element 数据
* @author Kallen
* @since 2020/12/23 13:33
*/
public DoubleLink(T element) {
head = new Node(element);
tail = head;
size++;
}
b、根据索引添加
/**
* <p>根据索引插入元素</p>
*
* @param element 元素
* @param index 索引
* @author Kallen
* @since 2020/12/28 16:05
*/
public void insert(T element, int index) {
if (index < 0 || index > size - 1) {
throw new IndexOutOfBoundsException("索引越界");
}
// 判断当前链表是否为空
if (head == null) {
DoubleLink(element);
}else {
if (index == 0) {
// 当index为0时,即为在链表头插入
addAtHeader(element);
}else {
// 获取插入点的前一个节点
Node prev = getNodeByIndex(index - 1);
// 获取插入点的节点
Node next = prev.next;
// 让新节点的next引用指向next节点,prev引用指向prev节点
Node newNode = new Node(element , prev , next);
// 让prev的next指向新节点。
prev.next = newNode;
// 让prev的下一个节点的prev指向新节点
next.prev = newNode;
size++;
}
}
}
c、尾插法
/**
* <p>尾插法</p>
*
* @param element 元素
* @author Kallen
* @since 2020/12/28 16:25
*/
public void add(T element) {
// 如果该链表为空
if (head == null) {
DoubleLink(element);
}else {
// 创建新节点,新节点的pre引用指向原tail节点
Node newNode = new Node(element , tail , null);
// 让尾节点的next指向新增的节点
tail.next = newNode;
// 以新节点作为新的尾节点
tail = newNode;
}
size++;
}
d、头插法
/**
* <p>头插法</p>
*
* @param element 元素
* @author Kallen
* @since 2020/12/28 16:10
*/
public void addAtHeader(T element) {
// 创建新节点,让新节点的next指向原来的head,并以新节点作为新的head
head = new Node(element, head);
// 如果插入之前是空链表
if (tail == null)
{
tail = head;
}
size++;
}
5.3、查询数据
a、根据索引查询
/**
* <p>获取指定索引处的元素</p>
*
* @param index 指定索引
* @return {@link T} 元素
* @author Kallen
* @since 2020/12/23 13:35
*/
public T get(int index) {
return getNodeByIndex(index).data;
}
b、根据指定元素查找索引
/**
* <p>查询指定元素的索引</p>
*
* @param element 元素
* @return {@link int} 索引
* @author Kallen
* @since 2020/12/25 15:48
*/
public int locate(T element) {
Node current = head;
for (int i = 0; i < size && current != null; i++, current = current.next) {
if (current.data.equals(element)) {
return i;
}
}
return -1;
}
c、获取当前链表长度
/**
* <p>获取链表长度</p>
*
* @return {@link int} 链表长度
* @author Kallen
* @since 2020/12/23 13:34
*/
public int size() {
return size;
}
5.4、删除数据
a、根据索引删除
/**
* <p>根据索引删除</p>
*
* @param index 索引
* @author Kallen
* @since 2020/12/28 17:08
*/
public void delete(int index) {
if (index < 0 || index > size - 1) {
throw new IndexOutOfBoundsException("索引越界");
}
if (index == 0) {
head = head.next;
head.prev = null;
}else {
// 获取删除节点的前一个节点
Node prev = getNodeByIndex(index-1);
// 让prev节点的next指向被删除节点的next
prev.next = prev.next.next;
// 让被删除节点的next节点的prev指向prev节点 Ps(被删除节点的next节点不为尾节点)
if (prev.next != null) {
prev.next.prev = prev;
}
// 将被删除节点的prev、next引用赋为null
prev.next.prev = null;
prev.next.next = null;
}
size--;
}
b、删除最后一个元素
/**
* <p>删除链表的最后一个元素</p>
*
* @author Kallen
* @since 2020/12/28 17:15
*/
public void remove() {
delete(size-1);
}
5.5、判断是否为空
/**
* <p>判断链表是否为空链表</p>
*
* @author Kallen
* @since 2020/12/28 17:16
*/
public boolean isEmpty() {
return size == 0;
}
5.6、清空链表
/**
* <p>清空链表</p>
*
* @author Kallen
* @since 2020/12/28 17:17
*/
public void clear() {
head = null;
tail = null;
size = 0;
}
5.7、私有方法
/**
* <p>根据指定索引获取结点</p>
*
* @param index 索引
* @return {@link DoubleLink<T>.Node} 节点
* @author Kallen
* @since 2020/12/23 13:36
*/
private Node getNodeByIndex(int index) {
if (index < 0 || index > size - 1) {
throw new IndexOutOfBoundsException("索引越界");
}
// 当索引值小于该链表长度的一半时,应从头结点开始搜索,PS(值相同从头结点开始搜索)
if (index <= size / 2) {
Node current = head;
for (int i = 0; i <= size / 2 && current != null; i++, current = current.next) {
if (i == index) {
return current;
}
}
}else {
// 从tail节点开始搜索
Node current = tail;
for (int i = size - 1; i > size / 2 && current != null; i++, current = current.prev) {
if (i == index) {
return current;
}
}
}
return null;
}