数据结构 链表
文章目录
1. ArrayList的缺陷
在上篇文章中我们已经熟悉了ArrayList
的使用,并且进行了简单的模拟实现。通过源码知道,ArrayList底层使用数组来存储元素,但由于其底层是一段连续的空间,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(N),效率较低。因此ArrayList不适合做任意位置插入和删除比较多的场景。因此:Java集合中有引入了LinkedList
,即链表结构。
2. 链表
2.1 链表的概念及结构
链表是一种物理存储结构上非连续的存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的
链表的结构非常多,以下情况组合起来就有8种链表结构:
-
单向或双向:
-
带头或不带头:
-
循环或非循环:
链表结构较多,本文重点介绍以下两种:
- 无头单向非循环链表: 结构简单,一般不会单独用来存数据,实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等
- 无头双向链表:在Java的集合框架库中LinkedList底层实现的就是无头双向循环链表
2.2 单链表的实现
-
IList接口
package demo2; public interface IList { //头插法 void addFirst(int data); //尾插法 void addLast(int data); //任意位置插入,第一个数据节点为0号下标 void addIndex(int index,int data); //查找是否包含关键字key是否在单链表当中 boolean contains(int key); //删除第一次出现关键字为key的节点 void remove(int key); //删除所有值为key的节点 void removeAllKey(int key); //得到单链表的长度 int size(); void clear(); void display(); }
-
MyLinkList实现IList接口
关键步骤: 设置内部类定义链表结构:
public class MyLinkList implements IList{ static class ListNode { public int val; public ListNode next; public ListNode(int val) { this.val = val; } } }
package demo2; public class MyLinkList implements IList{ static class ListNode { public int val; public ListNode next; public ListNode(int val) { this.val = val; } } public ListNode head; /*初始化链表*/ public void creatList() { ListNode node1 = new ListNode(1); ListNode node2 = new ListNode(12); ListNode node3 = new ListNode(123); ListNode node4 = new ListNode(1234); node1.next = node2; node2.next = node3; node3.next = node4; node4.next = null; this.head = node1; } /*头插法*/ @Override public void addFirst(int data) { ListNode cur = new ListNode(data); cur.next = this.head; this.head = cur; } /*尾插法*/ @Override public void addLast(int data) { ListNode node = new ListNode(data); if (this.head == null) { this.head = node; return; } ListNode cur = this.head; while(cur.next != null) { cur = cur.next; } cur.next = node; } /*指定位置插入元素*/ @Override public void addIndex(int index, int data) { if (index < 0 || index > size()) { throw new ArrayIndexOutOfBoundsException("下标超出范围"); } if (index == 0) { addFirst(data); } else if (index == size()) { addLast(data); } else { ListNode cur = this.head; ListNode node = new ListNode(data); int count = 0; while(count < index - 1) { cur = cur.next; count++; } node.next = cur.next; cur.next = node; } } /*判断链表是否包含该元素*/ @Override public boolean contains(int key) { ListNode cur = this.head; while(cur != null) { if (cur.val == key) { return true; } cur = cur.next; } return false; } /*清空指定元素*/ @Override public void remove(int key) { if (this.head == null) { System.out.println("无元素可以删除"); return; } if (this.head.val == key) { this.head = head.next; } ListNode cur = this.head; while(cur.next != null) { if (cur.next.val == key) { ListNode del = cur.next; cur.next = del.next; return; } cur = cur.next; } } /*清空所有指定元素*/ @Override public void removeAllKey(int key) { if (this.head == null) { System.out.println("链表为空"); return; } ListNode prev = this.head; ListNode cur = this.head.next; while(cur != null) { if (cur.val == key) { prev.next = cur.next; cur = cur.next; } else { prev = cur; cur = cur.next; } } } @Override public int size() { ListNode cur = this.head; int count = 0; while(cur != null) { count++; cur = cur.next; } return count; } /*清空链表*/ @Override public void clear() { ListNode cur = this.head; while(cur != null) { ListNode curNext = cur.next; cur.next = null; cur = curNext; } head = null; } @Override public void display() { ListNode cur = this.head; while(cur != null) { System.out.print(cur.val + " "); cur = cur.next; } System.out.println(); } }
-
Main主程序
package demo2; public class Main { public static void main(String[] args) { MyLinkList myLinkList = new MyLinkList(); myLinkList.creatList(); myLinkList.display(); //头插法 MyLinkList myLinkList1 = new MyLinkList(); myLinkList1.addFirst(25); myLinkList1.addFirst(72); myLinkList1.display(); //尾插法 MyLinkList myLinkList2 = new MyLinkList(); myLinkList2.addLast(11); myLinkList2.addLast(150); myLinkList2.addLast(99); myLinkList2.display(); System.out.println(myLinkList2.contains(99)); System.out.println(myLinkList2.contains(101)); //指定位置插入 myLinkList2.addIndex(1,77); myLinkList2.display(); //移除指定元素 myLinkList2.remove(99); myLinkList2.display(); //清空链表 myLinkList2.clear(); myLinkList2.display(); System.out.println("===="); //清空所有指定元素 MyLinkList myLinkList3 = new MyLinkList(); myLinkList3.addLast(13); myLinkList3.addLast(14); myLinkList3.addLast(15); myLinkList3.addLast(14); myLinkList3.addLast(17); myLinkList3.display(); myLinkList3.removeAllKey(14); myLinkList3.display(); } } //执行结果 1 12 123 1234 ========== 72 25 ========== 11 150 99 true false ========== 11 77 150 99 ========== 11 77 150 ========== ========== 13 14 15 14 17 13 15 17 ==========
2.3 双链表的实现
-
IList接口
package demo3; public interface IList { //头插法 void addFirst(int data); //尾插法 void addLast(int data); //任意位置插入,第一个数据节点为0号下标 void addIndex(int index,int data); //查找是否包含关键字key是否在单链表当中 boolean contains(int key); //删除第一次出现关键字为key的节点 void remove(int key); //删除所有值为key的节点 void removeAllKey(int key); //得到单链表的长度 int size(); void clear(); void display(); }
-
MyLinkedList类实现IList接口
关键步骤:设置内部类定义链表结构:
public class MyLinkedList implements IList{ static class ListNode { public int val; public ListNode prev; public ListNode next; public ListNode(int data) { this.val = data; } } public ListNode head; public ListNode last; }
这里比单链表多了一个
prev指针
(指向当前节点的上一个节点)和last尾指针
(指向最后一个节点)package demo3; import demo2.MyLinkList; import java.util.List; public class MyLinkedList implements IList{ static class ListNode { public int val; public ListNode prev; public ListNode next; public ListNode(int data) { this.val = data; } } public ListNode head; public ListNode last; /*头插法*/ @Override public void addFirst(int data) { ListNode node = new ListNode(data); if (this.head == null) { head = node; last = node; } else { this.head.prev = node; node.next = this.head; head = node; } } /*尾插法*/ @Override public void addLast(int data) { ListNode node = new ListNode(data); if (this.head == null) { head = node; last = node; } else { last.next = node; node.prev = last; last = node; } } /*指定位置插入元素*/ @Override public void addIndex(int index, int data) { if (index < 0 || index > size()) { throw new ArrayIndexOutOfBoundsException("下标超出范围"); } if (index == 0) { addFirst(data); } else if (index == size()) { addLast(data); } else { ListNode cur = this.head; ListNode node = new ListNode(data); for (int i = 0;i < index - 1;i++) { cur = cur.next; } node.prev = cur.prev; node.next = cur; cur.prev.next= node; cur.prev = node; } } /*判断元素是否在链表中*/ @Override public boolean contains(int key) { ListNode cur = this.head; while(cur != null) { if (cur.val == key) { return true; } cur = cur.next; } return false; } /*移除指定元素*/ @Override public void remove(int key) { ListNode cur = this.head; while(cur != null) { if (cur.val == key) { if (cur == head) { head = head.next; if (head == null) { last = null; } else { head.prev = null; } } else { cur.prev.next = cur.next; if (cur.next == null) { last = last.prev; } else { cur.next.prev = cur.prev; } } return; } else { cur = cur.next; } } } /*移除所有指定元素*/ @Override public void removeAllKey(int key) { ListNode cur = this.head; while(cur != null) { if (cur.val == key) { if (cur == head) { head = head.next; if (head == null) { last = null; } else { head.prev = null; } } else { cur.prev.next = cur.next; if (cur.next == null) { last = last.prev; } else { cur.next.prev = cur.prev; } } } cur = cur.next; } } /*返回链表长度*/ @Override public int size() { ListNode cur = this.head; int len = 0; while(cur != null) { len++; cur = cur.next; } return len; } /*清空链表*/ @Override public void clear() { ListNode cur = this.head; while(cur != null) { ListNode curNext = cur.next; cur.prev = null; cur.next = null; cur = curNext; } this.head = null; this.last = null; } @Override public void display() { ListNode cur = this.head; while(cur != null) { System.out.print(cur.val + " "); cur = cur.next; } System.out.println(); } }
-
Main主程序
package demo3; public class Main { public static void main(String[] args) { //头插法 MyLinkedList myLinkedList1 = new MyLinkedList(); myLinkedList1.addFirst(1); myLinkedList1.addFirst(2); myLinkedList1.addFirst(3); myLinkedList1.addFirst(4); myLinkedList1.display(); //尾插法 MyLinkedList myLinkedList2 = new MyLinkedList(); myLinkedList2.addLast(11); myLinkedList2.addLast(12); myLinkedList2.addLast(13); myLinkedList2.addLast(14); myLinkedList2.display(); //指定位置插入(首节点下标为0) myLinkedList2.addIndex(3,32); // myLinkedList2.addIndex(4,17); // myLinkedList2.addIndex(5,22); myLinkedList2.display(); //判断元素是否在链表中 System.out.println(myLinkedList2.contains(11)); System.out.println(myLinkedList2.contains(25)); //删除指定元素值 myLinkedList2.remove(14); myLinkedList2.display(); //清空链表 myLinkedList1.clear(); myLinkedList1.display(); //删除链表中所有指定元素 myLinkedList2.addLast(11); myLinkedList2.display(); myLinkedList2.removeAllKey(11); myLinkedList2.display(); } } //执行结果 4 3 2 1 11 12 13 14 11 12 32 13 14 true false 11 12 32 13 11 12 32 13 11 12 32 13
3. LinkedList的使用
3.1 什么是LinkedList
LinkedList的底层是双向链表结构,由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。
在集合框架中,LinkedList也实现了List接口,如下图所示:
注:
- LinkedList实现了List接口
- LinkedList的底层使用了双链表
- Linked List没有实现RandomAccessjiekou,因此LinkedList不支持随机访问
- LinkedList的任意位置插入和删除元素效率比较高,时间复杂度为O(1)
- LinkedList比较适合任意位置插入的场景
3.2 LinkedList的使用
3.2.1 LinkedList的构造
方法 | 解释 |
---|---|
LinkedList() | 无参构造 |
public LinkedList(Collection<? extends E> c) | 使用其他集合容器中元素构造List |
代码示例:
public static void main(String[] args) {
List<Integer> list1 = new LinkedList<>();
list1.add(1);
list1.add(12);
list1.add(123);
List<String> list2 = new java.util.ArrayList<>();
list2.add("张三");
list2.add("李四");
list2.add("王五");
//使用ArrayList构造LinkListed
List<String> list3 = new LinkedList<>(list2);
System.out.println(list1);
System.out.println(list2);
System.out.println(list3);
}
//执行结果
[1, 12, 123]
[张三, 李四, 王五]
[张三, 李四, 王五]
3.2.2 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 |
代码示例:
package demo4;
import java.util.LinkedList;
import java.util.List;
public class Main1 {
public static void main(String[] args) {
LinkedList<Integer> list = new LinkedList<>();
//添加元素,表示尾插
list.add(1);
list.add(12);
list.add(123);
list.add(1234);
list.add(12345);
System.out.println(list);
System.out.println("============");
//返回链表长度
System.out.println(list.size());
System.out.println("============");
//从起始位置插入
list.add(0,0);
System.out.println(list);
System.out.println("============");
//删除指定位置的元素
list.remove(2);
System.out.println(list);
//删除第一个元素
list.removeFirst();
//删除最后一个元素
list.removeLast();
System.out.println(list);
System.out.println("============");
//检查元素是否存在,如果是返回true,否则返回false
if (!list.contains(100)) {
System.out.println("元素不存在");
}
System.out.println("============");
//从前往后找到第一个指定元素值的位置
System.out.println(list.indexOf(1));
//从后往前找到第一个指定元素值的位置
System.out.println(list.lastIndexOf(1));
System.out.println("============");
//获取指定位置元素
int elem = list.get(2);
System.out.println(elem);
System.out.println("============");
//将指定位置元素改成指定元素
System.out.println(list);
list.set(2,11111);
System.out.println(list);
System.out.println("============");
//subList(from,to): 用list中[from,to)之间的元素构造一个新的LinkedList返回
List<Integer> newList = list.subList(0,2);
System.out.println(list);
System.out.println(newList);
System.out.println("============");
//将list中的元素清空
list.clear();
System.out.println(list);
System.out.println(list.size());
}
}
//执行结果
[1, 12, 123, 1234, 12345]
============
5
============
[0, 1, 12, 123, 1234, 12345]
============
[0, 1, 123, 1234, 12345]
[1, 123, 1234]
============
元素不存在
============
0
0
============
1234
============
[1, 123, 1234]
[1, 123, 11111]
============
[1, 123, 11111]
[1, 123]
============
[]
0
3.2.3 LinkedList的遍历
代码示例:
package demo5;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
public class Main {
public static void main(String[] args) {
LinkedList<Integer> list = new LinkedList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
list.add(7);
System.out.println(list);
//foreach遍历链表
for (int x:list) {
System.out.print(x + " ");
}
System.out.println();
//使用迭代器遍历-正向遍历
ListIterator<Integer> it = list.listIterator();
while(it.hasNext()) {
System.out.print(it.next() + " ");
}
System.out.println();
//使用迭代器遍历-反向遍历
ListIterator<Integer> rit = list.listIterator(list.size());
while(rit.hasPrevious()) {
System.out.print(rit.previous() + " ");
}
System.out.println();
}
}
//执行结果
[1, 2, 3, 4, 5, 6, 7]
1 2 3 4 5 6 7
1 2 3 4 5 6 7
7 6 5 4 3 2 1
4. ArrayList和LinkedList的区别
不同点 | ArrayList | LinkedList |
---|---|---|
存储空间上 | 物理上一定连续 | 逻辑上连续,但物理上不一定连续 |
随机访问 | 支持O(1) | 不支持:O(N) |
头插 | 需要搬移元素,效率低O(N) | 只需修改引用的指向,时间复杂度为O(1) |
插入 | 空间不够时需要扩容 | 没有容量的概念 |