目录
链表数据结构
链表分类类型
1. 单向链表
2. 双向链表
3. 循环链表
✍️实现一个链表
1. 链表节点
2. 头插节点
3. 尾插节点
4. 拆链操作
5. 删除节点
6. 按照index查询对象
7. 打印
链表使用测试
️常见面试问题
在计算机科学中,链表是数据元素的线性集合,元素的线性顺序不是由它们在内存中的物理地址给出的。它是由一组节点组成的数据结构,每个元素指向下一个元素,这些节点一起,表示线性序列。
在最简单的链表结构下,每个节点由数据和指针(存放指向下一个节点的指针)两部分组成,这种数据结构允许在迭代时有效地从序列中的任何位置插入或删除元素。
链表的数据结构通过链的连接方式,提供了可以不需要扩容空间就更高效的插入和删除元素的操作,在适合的场景下它是一种非常方便的数据结构。但在一些需要遍历、指定位置操作、或者访问任意元素下,是需要循环遍历的,这将导致时间复杂度的提升。
看了这些还是很懵,下面手写个双向链表很快就清楚链表结构啦
链表的主要表现形式分为;单向链表、双向链表、循环链表,接下来我们分别介绍下。
单链表包含具有数据字段的节点以及指向节点行中的下一个节点的“下一个”字段。可以对单链表执行的操作包括插入、删除和遍历。
在“双向链表”中,除了下一个节点链接之外,每个节点还包含指向序列中“前一个”节点的第二个链接字段。这两个链接可以称为'forward('s')和'backwards',或'next'和'prev'('previous')。
在列表的最后一个节点中,链接字段通常包含一个空引用,一个特殊的值用于指示缺少进一步的节点。一个不太常见的约定是让它指向列表的第一个节点。在这种情况下,列表被称为“循环”或“循环链接”;否则,它被称为“开放”或“线性”。它是一个列表,其中最后一个指针指向第一个节点。
简化结构的方式把 LinkedList 手写实现
先看看我们要实现的一些链表方法吧
package main.pjp.linked_List;
public interface PjpList {
// 添加
boolean add(E e);
// 头插
boolean addFirst(E e);
// 尾插
boolean addLast(E e);
// 删除
boolean remove(Object o);
// 按照index查询对象
E get(int index);
// 打印
void printLinkList();
}
然后建个实现类开始实现这些方法
package main.pjp.linked_List;
public class PjpLinkedList implements PjpList {}
/**
* ?表示不确定的 java 类型
* T (type) 表示具体的一个 java 类型
* K V (key value) 分别代表 java 键值中的Key Value
* E (element) 代表 Element
*/
private static class Node {
E item; // 值
Node next; // 后继节点
Node prev; // 前驱节点
public Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
void linkFirst(E e) {
final Node f = first; // 获取双向链表的第一个元素
final Node newNode = new Node<>(null, e, f); // 新初始化一个元素,并将它的后继节点指向原链表的第一元素
first = newNode; // 插入的元素变成第一个元素
if (f == null) {
last = newNode;
} else {
f.prev = newNode; // 原第一个元素的前驱节点指向插入元素
}
size++;
}
void linkLast(E e) {
final Node l = last;
final Node newNode = new Node<>(l, e, null);
last = newNode;
if (l == null) {
first = newNode;
} else {
l.next = newNode;
}
size++;
}
E unlink(Node a) {
final E element = a.item; // 值
final Node next = a.next; // 后一个元素
final Node prev = a.prev; // 前一个元素
if (prev == null) { // 删除的是第一个元素
first = next; // 直接让下一个元素成为第一个元素
} else { // 删除的不是第一个元素
prev.next = next; // 前一个元素的后继节点直接指向后一个元素
a.prev = null;
}
if (next == null) { // 删除的是最后一个元素
last = prev;
} else {
next.prev = prev; // 后一个元素的前继节点直接指向前一个元素
a.next = null; // 和前面 a.prev = null; 配合前后节点都置为null,真正剔除删除元素
}
a.item = null;
size--;
return element;
}
public boolean remove(Object o) {
if (o == null) {
for (Node a = first; a != null; a = a.next) {
if (a.item == null) {
unlink(a);
return true;
}
}
} else {
for (Node a = first; a != null; a = a.next) {
if (o.equals(a.item)) {
unlink(a);
return true;
}
}
}
return false;
}
public E get(int index) {
return node(index).item;
}
Node node(int index) {
if (index < (size >> 1)) {
Node f = first;
for (int i = 0; i < index; i++) {
f = f.next;
}
return f;
} else {
Node l = last;
for (int i = size - 1; i > index; i--) {
l = l.prev;
}
return l;
}
}
为了提高效率,做了以下操作
public void printLinkList() {
if (size == 0) {
System.out.println("链表为空");
} else {
Node temp = first;
System.out.print("目前的列表,头节点:" + first.item + " 尾节点:" + last.item + " 整体:");
while (temp != null) {
System.out.print(temp.item + ",");
temp = temp.next;
}
System.out.println();
}
}
package main.pjp.test;
import main.pjp.linked_List.PjpLinkedList;
import main.pjp.linked_List.PjpList;
public class test {
public static void main(String[] args) {
PjpList list = new PjpLinkedList<>();
// 添加元素
list.add("a");
list.addFirst("b");
list.addLast("c");
// 打印列表
list.printLinkList();
// 头插元素
list.addFirst("d");
// 删除元素
list.remove("b");
// 打印列表
list.printLinkList();
System.out.println(list.get(2));
}
}
测试结果
目前的列表,头节点:b 尾节点:c 整体:b,a,c,
目前的列表,头节点:d 尾节点:c 整体:d,a,c,
c
1. 描述一下链表的数据结构?
一组称为节点的对象组成,每个节点包含两个部分:数据元素和指针。链表中的节点通过指针 连接起来,形成有序的数据序列。
2. Java 中 LinkedList 使用的是单向链表、双向链表还是循环链表?
采用的是循环双向链表数据结构,链表的最后一个节点的next指向第一个节点。
3. 链表中数据的插入、删除、获取元素,时间复杂度是多少?
4. 什么场景下使用链表更合适
链表最适合在需要频繁进行插入和删除操作的场景下使用;不适用于频繁访问元素,链表访问元素的时间复杂度为 O(n)