LinkedList与链表
前言
本篇博客主要讲述 - 以下内容
- ArrayList的缺陷
- 链表
- 链表相关oj
- LinkedList的模拟实现
- LinkedList的使用
- ArrayList和LinkedList的区别
ArrayList的缺陷
上篇博客讲述了ArrayList的使用 ,我们知道ArrayList底层使用数组来存储元
素
由于其底层是一段连续空间,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比较低,因此ArrayList不适合做任意位置插入和删除比较多的场景。因此:java集合中又引入了LinkedList,即链表结构。
链表
链表的概念及结构
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
- 单向或者双向
- 带头或者不带头节点
- 循环或者非循环链表
我们主要讲解俩种链表结构
- 无头单向非循环链表
结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等
- 无头双向链表:在Java的集合框架库中LinkedList底层实现就是无头双向循环链表。
链表的实现
import java.util.List;
public class MySingleList {
static class ListNode {
public int val;//存储的数据
public ListNode next;//存储下一个节点的地址
public ListNode() {
}
public ListNode(int val) {
this.val = val;
}
}
public ListNode head;// 代表当前链表的头节点的引用
public void createLink() {
ListNode listNode1 = new ListNode(12);
ListNode listNode2 = new ListNode(45);
ListNode listNode3 = new ListNode(23);
ListNode listNode4 = new ListNode(90);
listNode1.next = listNode2;
listNode2.next = listNode3;
listNode3.next = listNode4; /* */
head = listNode1;
}
/**
* 遍历链表
*/
public void display() {
//如果说 把整个链表 遍历完成 那么 就需要 head == null
// 如果说 你遍历到链表的尾巴 head.next == null
ListNode cur = head;
while (cur != null) {
System.out.print(cur.val+" ");
cur = cur.next;
}
System.out.println();
}
/**
* 从指定位置开始打印链表
* @param newHead
*/
public void display(ListNode newHead) {
//如果说 把整个链表 遍历完成 那么 就需要 head == null
// 如果说 你遍历到链表的尾巴 head.next == null
ListNode cur = newHead;
while (cur != null) {
System.out.print(cur.val+" ");
cur = cur.next;
}
System.out.println();
}
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key){
ListNode cur = head;
while (cur != null) {
if(cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
//得到单链表的长度 O(N)
public int size(){
int count = 0;
ListNode cur = head;
while (cur != null) {
count++;
cur = cur.next;
}
return count;
}
//头插法 O(1)
public void addFirst(int data){
ListNode listNode = new ListNode(data);
listNode.next = head;
head = listNode;
}
//尾插法 O(N) 找尾巴的过程
public void addLast(int data){
ListNode listNode = new ListNode(data);
if(head == null) {
head = listNode;
return;
}
ListNode cur = head;
while (cur.next != null) {
cur = cur.next;
}
cur.next = listNode;
}
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index,int data)
throws ListIndexOutOfException{
checkIndex(index);
if(index == 0) {
addFirst(data);
return;
}
if(index == size()) {
addLast(data);
return;
}
ListNode cur = findIndexSubOne(index);
ListNode listNode = new ListNode(data);
listNode.next = cur.next;
cur.next = listNode;
}
/**
* 找到 index-1位置的节点的地址
* @param index
* @return
*/
private ListNode findIndexSubOne(int index) {
ListNode cur = head;
int count = 0;
while (count != index-1) {
cur = cur.next;
count++;
}
return cur;
}
private void checkIndex(int index) throws ListIndexOutOfException{
if(index < 0 || index > size()) {
throw new ListIndexOutOfException("index位置不合法");
}
}
//删除第一次出现关键字为key的节点 O(N)
public void remove(int key){
if(head == null) {
return ;//一个节点都没有
}
if(head.val == key) {
head = head.next;
return;
}
ListNode cur = searchPrev(key);
if(cur == null) {
return;
}
ListNode del = cur.next;//要删除的节点
cur.next = del.next;
}
/**
* 找到关键字key的前一个节点
* @param key
* @return
*/
private ListNode searchPrev(int key) {
ListNode cur = head;
while (cur.next != null) {
if(cur.next.val == key) {
return cur;
}
cur = cur.next;
}
return null;//没有你要删除的节点
}
//删除所有值为key的节点
public void removeAllKey(int key){
if(head == null) {
return;
}
/*while(head.val == key) {
head = head.next;
}*/
ListNode prev = head;
ListNode cur = head.next;
while (cur != null) {
if(cur.val == key) {
prev.next = cur.next;
cur = cur.next;
}else {
prev = cur;
cur = cur.next;
}
}
if(head.val == key) {
head = head.next;
}
}
/**
* 保证链表当中 所有的节点 都可以被回收
*/
public void clear() {
head = null;
//StringBuilder sb = "fdfa";
}
}
说明: 链表的习题将会出一篇博客,尽量将结构与题型分开
LinkedList的使用
LinkedList的官方文档
https://docs.oracle.com/javase/8/docs/api/java/util/LinkedList.html
LinkedList的底层是双向链表结构(链表后面介绍),由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。
在集合框架中,LinkedList也实现了List接口
- LinkedList实现了List接口
- LinkedList的底层使用了双向链表
- LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问
- LinkedList的任意位置插入和删除元素时效率比较高,时间复杂度O(1)
- LinkedList比较适合任意位置插入的场景
LinkedList的其他常用方法介绍
方法 | 解释 |
---|---|
boolean add(E e) | 尾插 e |
void add(int index, E element) | 将 e 插入到 index 位置 |
boolean addAll(Collection<? extends E> c) | 尾插 c 中的元素 |
E remove(int index) | 删除 index 位置元素 |
boolean remove(Object o) | 删除遇到的第一个 o |
E get(int index) | 获取下标 index 位置元素 |
E set(int index, E element) | 将下标 index 位置元素设置为 element |
void clear() | 清空 |
boolean contains(Object o) | 判断 o 是否在线性表中 |
int indexOf(Object o) | 返回第一个 o 所在下标 |
int lastIndexOf(Object o) | 返回最后一个 o 的下标 |
List subList(int fromIndex, int toIndex) | 截取部分 list |
LinkedList的遍历
// foreach遍历
for (int e:list) {
System.out.print(e + " ");
}
// 使用迭代器遍历---正向遍历
ListIterator<Integer> it = list.listIterator();
while(it.hasNext()){
System.out.print(it.next()+ " ");
}
// 使用反向迭代器---反向遍历
ListIterator<Integer> rit = list.listIterator(list.size());
while (rit.hasPrevious()){
System.out.print(rit.previous() +" ");
}
ArrayList和LinkedList的区别
ArrayList | LinkedList |
---|---|
物理上一定连续 逻辑上连续 | 物理上不一定连续 |
随机访问 支持O(1) | 随机访问 支持O(N) |
插入O(N) | 插入(1) |
应用场景 元素高效存储+频繁访问 | 任意位置插入和删除频繁 |