链表
线型数据结构
为什么链表很重要
-
链表是真正的动态数据结构
-
链表是最简单的动态数据结构
-
可以更深入的理解引用(指针)
-
可以更深入的理解递归
-
可以辅助组成其他的数据结构
什么是链表(LinkedList)
-
数据存储在"节点"(Node)中
class Node {
/**
* 所存储的真正的数据
*/
E e;
/**
* 指向当前节点的下一个节点
*/
Node next;
}
-
优点:真正的动态数据结构,不需要处理固定容量的问题
-
缺点:丧失了随机访问的能力
数组和链表的对比
-
数组最好用于索引有语意的情况,如:scores[2]
-
数组最大的优点就是:支持快速查询
-
链表不适合于索引有语意的情况
-
链表最大的优点:动态
链表的基本实现
package com.ldc.datastructures.linkedlist;
/**
* @author lengdongcheng
*/
public class LinkedList<E> {
/**
* 内部类
*/
private class Node{
/**
* 存放的真实的数据
*/
public E e;
/**
* 指向该节点的下一个节点
*/
public Node next;
/**
* 构造函数
* @param e
* @param next
*/
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
/**
* 只传一个e的构造函数
* @param e
*/
public Node(E e) {
this(e, null);
}
/**
* 默认构造函数
*/
public Node() {
this(null, null);
}
/**
* 重写toString方法
* @return
*/
@Override
public String toString() {
return e.toString();
}
}
}
链表头添加元素
图示
-
将新增加的节点挂接到原来的节点上,让新增的Node节点的next指向将要挂在的节点的头部
-
维护head,将head头部指向新增的节点
-
最终效果
代码演示
package com.ldc.datastructures.linkedlist;
/**
* @author lengdongcheng
*/
public class LinkedList<E> {
/**
* 内部类
*/
private class Node {
/**
* 存放的真实的数据
*/
public E e;
/**
* 指向该节点的下一个节点
*/
public Node next;
/**
* 构造函数
*
* @param e
* @param next
*/
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
/**
* 只传一个e的构造函数
*
* @param e
*/
public Node(E e) {
this(e, null);
}
/**
* 默认构造函数
*/
public Node() {
this(null, null);
}
/**
* 重写toString方法
*
* @return
*/
@Override
public String toString() {
return e.toString();
}
}
/**
* 头部元素
*/
private Node head;
/**
* 链表中元素的个数
*/
private int size;
/**
* 默认的构造函数
*/
public LinkedList() {
//头部元素为空
head = null;
//元素的个数为0
size = 0;
}
/**
* 获取链表中元素的个数
*
* @return
*/
public int getSize() {
return size;
}
/**
* 判断链表是否为空
*
* @return
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 在链表的头部添加元素
*/
public void addFirst(E e) {
/* //将元素e包装成一个Node对象
Node node = new Node(e);
//将Node的next指向将要挂载的Node对象
node.next = head;
//维护一下head的指向,指向新添加的Node节点
head = node;*/
//上面三行代码可以用下面一行代码来表示
head = new Node(e, head);
//维护一下size
size++;
}
}
在链表中间添加元素
图示
-
需求
-
将node节点的next指向prev的下一个节点:node.next = prev.next
-
将prev节点的next指向node节点:prev.next = node
-
最后效果
-
需要注意下顺序
代码实现
/**
* 在链表的index(从0开始)位置添加元素
* 在链表的中间添加元素不是一个常用的操作,这里只是练习用
* @param index
* @param e
*/
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed,index < 0 || index > size.");
}
//新添加的元素添在链表的头部,则调用addFirst就好了(添加到0的位置需要特殊处理)
if (index == 0) {
addFirst(e);
} else {
//刚开始将prev初始化为head的位置
Node prev = head;
//一直遍历,刚开始指向的是head的这个位置,然后一直将head.next赋值给prev
for (int i = 0; i < index - 1; i++) {
prev = prev.next;
}
//将新添加的元素包装成Node对象
/* Node node = new Node(e);
//将node的下一个节点指向prev的下一个节点
node.next = prev.next;
//将prev的下一个节点指向node
prev.next = node;*/
//同样,可以将上面三行代码合成一行代码
//new Node(e, prev.next); ----->将node这个节点于后面的节点挂接在一起
prev.next = new Node(e, prev.next);
size++;
}
}
/**
* 在链表的末尾位置添加元素
* @param e
*/
public void addLast(E e) {
add(size, e);
}
为链表设置虚拟头节点
-
代码实现
/**
* 在链表的index(从0开始)位置添加元素
* 在链表的中间添加元素不是一个常用的操作,这里只是练习用
* @param index
* @param e
*/
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed,index < 0 || index > size.");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
//将虚拟头节点的next指向新节点与虚拟头节点后面节点挂载后的节点
prev.next = new Node(e, prev.next);
size++;
}
/**
* 在链表的头部添加元素
*/
public void addFirst(E e) {
add(0, e);
}
/**
* 在链表的末尾位置添加元素
* @param e
*/
public void addLast(E e) {
add(size, e);
}
查询以及更新链表中的元素
/**
* 根据索引获取链表中的元素(根据索引获取元素不常用,只是练习用)
* @param index
* @return
*/
public E get(int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("get failed,index < 0 || index > size.");
}
//虚拟节点后面的节点
Node cur = dummyHead.next;
for (int i = 0; i < index; i++) {
//获取index位置的Node节点
cur = cur.next;
}
//获取index位置的Node节点的元素
return cur.e;
}
/**
* 获取第一个节点的元素
* @return
*/
public E getFirst(){
return get(0);
}
/**
* 获取最后一个节点的元素
* @return
*/
public E getLast(){
return get(size-1);
}
/**
* 修改链表中的第index位置的元素
* @param index
* @param e
*/
public void set(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("set failed,index < 0 || index > size.");
}
Node cur = dummyHead.next;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
cur.e = e;
}
/**
* 查看链表是否包含元素e
* @param e
* @return
*/
public boolean contains(E e) {
Node cur = dummyHead.next;
//如果cur为空的话,那么则表示整的链表都已经遍历完了一遍
while (cur != null) {
if (cur.e.equals(e)) {
return true;
}
cur = cur.next;
}
return false;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
Node cur = dummyHead.next;
while (cur != null) {
sb.append(cur + "->");
cur = cur.next;
}
sb.append("NULL");
return sb.toString();
}
测试案例
package com.ldc.datastructures.linkedlist;
/**
* @author lengdongcheng
*/
public class Main {
public static void main(String[] args) {
LinkedList<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < 5; i++) {
linkedList.addFirst(i);
System.out.println(linkedList);
}
linkedList.add(2, 666);
System.out.println(linkedList);
}
}
测试结果
0->NULL
1->0->NULL
2->1->0->NULL
3->2->1->0->NULL
4->3->2->1->0->NULL
4->3->666->2->1->0->NULL
从链表中删除元素
图示
-
需求
-
实现
代码实现
/**
* 删除指定索引位置的元素
* @param index
* @return
*/
public E remove(int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("remove failed,index < 0 || index > size.");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
//找到待删除节点之前的节点
prev = prev.next;
}
Node retNode = prev.next;
prev.next = retNode.next;
retNode = null;
size--;
return retNode.e;
}
/**
* 删除链表中的第一个元素
* @return
*/
public E removeFirst() {
return remove(0);
}
/**
* 删除链表中的最后一个元素
* @return
*/
public E removeLast() {
return remove(0);
}