文章目录
一、链表是什么?
这里引入百度百科的解释:
- 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。相比于线性表顺序结构,操作复杂。
- 链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
- 由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。
进行补充:
每个节点Node,一般来说要包含:前驱,元素,后继
链表也真正意义上实现了动态扩容
节点的实现在JAVA中可以采用内部类的方式实现
虚拟头节点:虚拟头节点的元素是null,指向了实际的头节点,采用虚拟头节点,执行添加操作时,时间复杂度可以达到O(1)
这里引入一张单链表图片:
二、单链表的实现
1.需要实现的方法:
- 在链表的任意位置添加元素
- 删除链表中任意位置的元素
- 更新链表中任意位置的元素
- 遍历操作
- 小功能的实现
2.引入内部Node(节点)类:
private class Node {
private E e;
private Node next;//后继
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
public Node(E e) {
this(e, null);
}
public Node() {
this(null, null);
}
@Override
public String toString() {
return e.toString();
}
}
3.在链表的任意位置添加元素:
- 这里的实现思路:
- 首先循环判断找到首先要插入位置的前一个节点
- 将前一个节点的后继赋值给要插入的节点的后继
- 而后将前一个节点的后继指向要插入的节点‘
- 这样便实现了链表在任意位置的添加,这里大家就会发现
- 在头节点添加时时间复杂度要远远大于在尾结点添加(仅对单链表实用)
实现代码:
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("索引位置不合法,添加失败");
}
Node pre = visualHead;
for (int i = 0; i < index; i++) {
pre = pre.next;
}
Node node = new Node(e);
node.next = pre.next;
pre.next = node;
size++;
}
4.删除链表中任意位置的元素:
- 这里的实现思路:
- 基本上与添加类似
- 这里删除时将被删除节点的后继赋值给被删除节点的前一个节点的后继
- 然后将被删除节点的后继设置成null,垃圾回收
实现代码如下:
public E remove(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("索引位置不合法,删除失败");
}
Node pre = visualHead;
for (int i = 0; i < index; i++) {
pre = pre.next;
}
Node retNode = pre.next;
pre.next = retNode.next;
retNode.next = null;
size--;
return retNode.e;
}
5.更新链表中任意位置的元素:
- 这里的实现思路:
- 最简单的查询操作,遍历到需要修改节点的位置,将value改为newValue,即可
实现代码如下:
public void set(int index, E e) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("索引位置不合法,修改失败");
}
Node current = visualHead.next;
for (int i = 0; i < index; i++) {
current = current.next;
}
current.e = e;
}
6.整体实现方法:
public class LinkedList<E> {
private Node visualHead;
private int size;
public LinkedList() {
visualHead = new Node();
size = 0;
}
//获得元素数量
public int getSize() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
//在头部添加元素
public void addFirst(E e) {
// Node node = new Node(e);
// node.next = head;
// head = node;
// size++;
add(0, e);
}
//在任意位置添加
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("索引位置不合法,添加失败");
}
Node pre = visualHead;
for (int i = 0; i < index; i++) {
pre = pre.next;
}
Node node = new Node(e);
node.next = pre.next;
pre.next = node;
size++;
}
public void addLast(E e) {
add(size, e);
}
//根据索引查找元素
public E get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("索引位置不合法,查询失败");
}
Node current = visualHead.next;
for (int i = 0; i < index; i++) {
current = current.next;
}
return current.e;
}
public E getFirst() {
return get(0);
}
public E getLast() {
return get(size - 1);
}
//修改链表中某处的元素
public void set(int index, E e) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("索引位置不合法,修改失败");
}
Node current = visualHead.next;
for (int i = 0; i < index; i++) {
current = current.next;
}
current.e = e;
}
//根据索引删除元素
public E remove(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("索引位置不合法,删除失败");
}
Node pre = visualHead;
for (int i = 0; i < index; i++) {
pre = pre.next;
}
Node retNode = pre.next;
pre.next = retNode.next;
retNode.next = null;
size--;
return retNode.e;
}
//删除链表中第一个元素
public E removeFirst() {
return remove(0);
}
public E removeLast() {
return remove(size - 1);
}
//判断是否含有该元素
public boolean contain(E e) {
Node node = visualHead.next;
for (int i = 0; i < size; i++) {
if (node.e.equals(e)) {
return true;
}
node = node.next;
}
return false;
}
//根据元素删除元素
public E removeElement(E e) {
if (!contain(e)) {
throw new IllegalArgumentException("元素不合法,删除失败");
}
Node pre = visualHead;
while (pre.next != null) {
if (pre.next.e.equals(e)) {
break;
}
pre = pre.next;
}
Node reNode = pre.next;
pre.next = reNode.next;
reNode.next = null;
size--;
return reNode.e;
}
//遍历
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
Node current = visualHead.next;
for (int i = 0; i < size; i++) {
sb.append(current.e + "->");
current = current.next;
}
sb.append("null");
return sb.toString();
}
private class Node {
private E e;
private Node next;
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
public Node(E e) {
this(e, null);
}
public Node() {
this(null, null);
}
@Override
public String toString() {
return e.toString();
}
}
}
三、单链表实现栈、队列、集合、映射
1.实现栈:
栈的底层实现可参考:
https://blog.csdn.net/GGB__/article/details/120250106
- 实现思路:
- 栈是先进后出的一种数据结构
- 这时候可能你会想我在单链表在末尾添加,在链表头删除或者在末尾删除,在链表头添加不就可以直接实现了吗?这么简单难不了我
- 当你在嘻嘻哈哈实现的时候就会发现,为啥我的栈变成队列了?
- 这时候就说明你进坑了,wc,无情啊
- 好了,言归正传,上面的做法完全错误。具体为什么,自己好好思考下链表的添加与删除就会明白了
- 这里实现栈,我们只需要在链表的头添加,在链表的头删除,此时的时间复杂度最低效率最高
实现代码如下:
public class LinkedListStack<E> implements Stack<E> {
private LinkedList<E> linkedList;
@Override
public void push(E e) {
linkedList.addFirst(e);
}
@Override
public E pop() {
return linkedList.removeFirst();
}
@Override
public E peek() {
return linkedList.get(0);
}
@Override
public int getSize() {
return linkedList.getSize();
}
@Override
public boolean isEmpty() {
return linkedList.isEmpty();
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append("Stack:栈顶");
res.append(linkedList);
return res.toString();
}
}
2.实现队列:
队列的底层实现可参考:
https://blog.csdn.net/GGB__/article/details/120250389
- 实现思路:
- 队列是先进先出的一种数据结构
- 这时候可能你会想我在单链表在末尾添加,在链表头删除或者在末尾删除,在链表头添加不就可以直接实现了吗?这么简单难不了我
- 当你在嘻嘻哈哈实现成功测试效率的时候,直接傻眼,千万级数据效率怎么这么低?无情啊!!!此时泪流满面的你,突然大师兄摸了摸你的头说
- 大师兄:小瓜瓜啊,如果在加一个tail,是不是在末尾添加节点的时间复杂度直接降为O(1)了呢?
- 好,到这里我们来实现一下
- 这里加的tail类似于虚拟头节点的概念
实现代码如下:
public class LinkedListQueue<E> implements Queue<E> {
public class Node {
private E e;
private Node next;
public Node(E e) {
this(e, null);
}
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
@Override
public String toString() {
return e.toString();
}
}
;
private int size;
private Node head;
private Node tail;
//前 入删
//后 入
public LinkedListQueue() {
head = null;
tail = null;
size = 0;
}
@Override
public void enqueue(E e) {
if (tail == null) {
tail = new Node(e);
head = tail;
} else {
tail.next = new Node(e);
tail = tail.next;
}
size++;
}
@Override
public E dequeue() {
if (isEmpty()) {
throw new IllegalArgumentException("无法从空队列中执行出队操作");
}
Node reNode = head;
head = head.next;
reNode.next = null;
size--;
return reNode.e;
}
@Override
public E getFront() {
if (isEmpty()) {
throw new IllegalArgumentException("队列为空");
}
return head.e;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append("Queue:队首");
Node current = head;
while (current != null) {
res.append(current + "->");
current = current.next;
}
res.append("队尾");
return res.toString();
}
}
3.实现Set集合
- 因为Set集合是无序且不重复的,唯一需要注意的是
- 添加节点元素时,先判断是否已经包含该节点元素
代码实现如下:
package com.lingo.set;
import com.lingo.linkedList.LinkedList;
public class LinkedSet<E> implements Set<E> {
private LinkedList linkedList;
public LinkedSet() {
linkedList = new LinkedList();
}
@Override
public void add(E e) {
if (!linkedList.contain(e)) {
linkedList.addFirst(e);
}
}
@Override
public boolean contains(E e) {
return linkedList.contain(e);
}
@Override
public void remove(E e) {
linkedList.removeElement(e);
}
@Override
public boolean isEmpty() {
return linkedList.isEmpty();
}
@Override
public int getSize() {
return linkedList.getSize();
}
}
再附上Set 接口
package com.lingo.set;
public interface Set<E> {
void add(E e);
boolean contains(E e);
void remove(E e);
boolean isEmpty();
int getSize();
}
4.实现映射
- 与前三个比较类似,相对来说难度系数为0
- 就不仔细介绍了 上代码
Map接口:
package com.lingo.map;
/**
* 集合映射
*
* @param <K> key值
* @param <V> value值
*/
public interface Map<K, V> {
void put(K key, V value);
V remove(K key);
void set(K key, V newValue);
boolean contains(K key);
int getSize();
boolean isEmpty();
V get(K key);
}
实现类:
package com.lingo.map;
/**
* 用链表来实现映射 Map
*
* @param <K>
* @param <V>
*/
public class LinkedListMap<K extends Comparable<K>, V> implements Map<K, V> {
private class Node {
public K key;
public V value;
public Node next;
public Node(K key, V value, Node next) {
this.key = key;
this.value = value;
this.next = next;
}
public Node(K key, V value) {
this(key, value, null);
}
public Node() {
this(null, null, null);
}
@Override
public String toString() {
return key.toString() + "-" + value.toString();
}
}
private int size;
private Node dummyHead;
public LinkedListMap() {
this.size = 0;
this.dummyHead = null;
}
@Override
public void put(K key, V value) {
//在头节点添加键值对时时间复杂度最低
if (getNode(key) == null) {
Node newNode = new Node(key, value);
newNode.next = dummyHead.next;
dummyHead.next = newNode;
size++;
} else {
throw new IllegalArgumentException("已经存在该key值");
}
}
@Override
public V remove(K key) {
//删除时 待删除节点的头节点指向待删除结点的下一个结点
Node prevNode = dummyHead;
while (prevNode.next != null) {
if (prevNode.next.key.equals(key)) {
break;
}
prevNode = prevNode.next;
}
//是否需要判断prevNode.next是否为空
if (prevNode.next == null) {
throw new IllegalArgumentException("集合无key值");
}
//待删除元素的结点
Node delNode = prevNode.next;
prevNode.next = delNode.next;
delNode.next = null;
size--;
return delNode.value;
}
@Override
public void set(K key, V newValue) {
Node currentNode = getNode(key);
if (currentNode == null) {
throw new IllegalArgumentException("不存在该Key");
} else {
currentNode.value = newValue;
}
}
@Override
public boolean contains(K key) {
return getNode(key) != null;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
private Node getNode(K key) {
Node current = dummyHead.next;
while (current != null) {
if (current.equals(key)) {
return current;
}
current = current.next;
}
return null;
}
@Override
public V get(K key) {
Node node = getNode(key);
if (node != null) {
return node.value;
}
return null;
}
}
四、双链表
这里本次未给大家设计双链表结构,源码可参考JDK1.8 中 java.util.LinkedList源码,相信现在的你,读双链表的源码不在话下;