LinkeList
LinkeList 的低层是由双向链表结构组成的,所有元素都是存放到单独的节点当中,通过地址引用将节点串联起来
因此在任意位置插入或删除元素时,都不在需要移动元素,效率较高
下面是双向链表的结构图:
在集合框架中,LinkedList也实现了List接口,具体如下:
- LinkedList实现了List接口
- LinkedList的底层使用了双向链表
- LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问
- LinkedList的任意位置插入和删除元素时效率比较高,时间复杂度为O(1)
LinkeList 的模拟实现
之前模拟过单向链表,所以前三个方法就不画图了,很简单
public class MyLinkedList {
static class ListNode{
public int val; // 数值
public ListNode prev;
public ListNode next;
// 初始化数值
public ListNode(int val) {
this.val = val;
}
}
public ListNode head; //标记头节点
public ListNode tail; //标记尾节点
//打印双向链表的节点
public void display(){
ListNode cur = head;
while (cur != null){
System.out.println(cur.val+" ");
}
System.out.println();
}
//获取双向链表的长度
public int size() {
ListNode cur = this.head;
int count = 0;
while(cur != null){
count++;
cur = cur.next;
}
return count;
}
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key) {
ListNode cur = this.head;
while(cur != null){
if(cur.val == key){
return true;
}
cur = cur.next;
}
return false;
}
}
头插法
在双向链表的第一个节点的前面里添加一个新的节点
图解:
源代码:
public void addFirst(int data) {
ListNode node = new ListNode(data);
// 如果链表里没有节点的情况
if(head == null){
this.head = node;//第一个节点
this.tail = node;// 最后一个节点
}else {
node.next = this.head;
this.head.prev = node;
this.head = node;
}
}
尾插法
在双向链表的最后一个节点的后里添加一个新的节点
图解
源代码:
public void addLast(int data) {
ListNode node = new ListNode(data);
// 如果链表里没有节点的情况
if(this.head == null){
this.head = node;//第一个节点
this.tail = node;// 最后一个节点
}else {
this.tail.next = node;
node.prev = this.tail;
tail = node;
}
}
在任意位置插入节点
在双链表的任意位置插入一个新的节点
图解
源代码:
//任意位置插入节点
public void addIndex(int index,int data) {
// 1. 判断位置的合法性
if(index < 0 || index > size()){
System.out.println("index位置不合法");
throw new IndxWrongFulException("index位置不合法");
}
// 2. 特殊情况:头节点和尾节点
if(index == 0){
addFirst(data);
return;
}
if(index == size()){
addLast(data);
return;
}
// 3. 找到要插入节点的位置
ListNode cur = findIndexListNOde(index);
// 4. 修改指向
ListNode node = new ListNode(data); // 创建插入节点
node.next = cur;
cur.prev.next = node;
node.prev = cur.prev;
cur.prev = node;
}
// 查找插入节点的位置
public ListNode findIndexListNOde(int index){
ListNode cur = this.head;
while (index != 0){
cur = cur.next;
index--;
}
return cur;
}
删除第一次出现的节点
给一个数值,删除第一次的位置的节点
源代码:
//删除第一次出现关键字为key的节点
public void remove(int key) {
ListNode cur = this.head;
// 历遍链表
while(cur != null){
// 1. 找到要删除的节点
if(cur.val == key){
// 如果要删除的是头节点的情况下
if(cur == head){
head = head.next; // 把 head 向前挪一步
if(head != null){
head.prev = null; // 把 head.prev 置为 null
}else {
// 如果只有一个节点的情况下
this.tail = null;
}
}else{
// 把 cur.next 赋值给后面的节点的next
cur.prev.next = cur.next;
// 如果是删除中间节点
if(cur.next != null){
// 把 cur.prev 赋值给前一个节点的prev
cur.next.prev = cur.prev;
}else {
// 如果删除的是尾节点 tail向后走一步
this.tail = cur.prev;
}
}
return;// 删除完节点后返回
}
cur = cur.next;
}
}
清空链表
删除链表的所有元素
不能直接把 head 和 tail 置空,因为中间的节点还在引用
只能一个一个节点置空
public void clear() {
ListNode cur = head.next;
while (cur != null){
ListNode curNode = cur.next;
cur.next = null;
cur.prev = null;
cur = curNode;
}
head = null;
tail = null;
}
LinkeList 的使用
构造方法
方法 | 解释 |
---|---|
LinkedList() | 无参构造 , 创建一个新的空linkedList |
LinkedList(Collection<? extends E> c) | 一个其他集合容器中元素构造List |
示例:
public static void main(String[] args) {
//无参构造
LinkedList<Integer> list = new LinkedList<>();
list.add(1); // 默认尾插法
list.add(2);
list.add(3);
System.out.println(list);
//传参数构造 只要实现list接口都可以传
ArrayList<Integer> arr = new ArrayList<>();
arr.add(11);
arr.add(12);
arr.add(13);
LinkedList<Integer> list2 = new LinkedList<>(arr);
System.out.println(list2);
}
结果:
LinkeList 常用方法
方法 | 解释 |
---|---|
boolean add(E e) | 尾插法 |
void add (int index, E element) | 将 e 插入到 index 位置 |
boolean addAll(Collection<? extends E> c) | 尾插 c 中的元素 |
boolean addAll(int index, Collection<? extends E> c) | 将 c 中的元素添加到指定位置 |
E remove(int index) | 删除 index 位置元素 |
boolean remove(Object o) | 删除遇到的第一个 o |
E remove() | 删除第一个元素 |
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<E> subList(int fromIndex, int toIndex) | 截取部分 lis |
方法使用示例:
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);
System.out.println(list);
// 在任意位置插入元素
System.out.println("=======在任意位置插入元素========");
list.add(0,0);
System.out.println(list);
// 删除元素
System.out.println("=======删除元素========");
list.remove();// 删除第一个元素
list.removeLast();// 删除最后一个元素
list.remove(2);// 删除第二个下标的元素
System.out.println(list);
// 判断是否存在
System.out.println("=======判断元素是否存在========");
System.out.println(list.contains(1));
//找元素的位置
System.out.println("=======找 index 元素的位置========");
System.out.println(list.indexOf(1)); // 从前往后找第一个1的位置
System.out.println(list.lastIndexOf(1)); // 从后往前找第一个1的位置
// 获取 index 下标的元素
System.out.println("=======找 index 下标的元素========");
int elem = list.get(0); // get(index): 获取指定位置元素
System.out.println(elem);
// 将index位置的元素设置为elem
System.out.println("=======将index位置的元素设置为elem========");
list.set(0, 100); // set(index, elem): 将index位置的元素设置为elem
System.out.println(list);
// subList(from, to) 复制链表中 frow 到 to 之间的元素存放到新的变量中
System.out.println("=======复制链表from到to的元素到新的变量中========");
List<Integer> cur = list.subList(0,3);
System.out.println(cur);//打印复制的元素
// 清空链表的所有元素
System.out.println("=======清空链表的所有元素========");
list.clear(); // 将list中元素清空
}
LinkedList的遍历
LinkedList的遍历可以通过二种方式:for循环
或者 foreach遍历
public static void main(String[] args) {
LinkedList<Integer> list = new LinkedList<>();
list.add(1); // add(elem): 表示尾插
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
list.add(7);
//通过for循环遍历
int size = list.size();
for (int i = 0; i < size; i++) {
System.out.print(list.get(i)+" ");
}
System.out.println();
//通过foreach遍历
for (int e:list) {
System.out.print(e + " ");
}
System.out.println();
}
- 通过迭代器遍历
public static void main(String[] args) {
LinkedList<Integer> list = new LinkedList<>();
list.add(1); // add(elem): 表示尾插
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
list.add(7);
// 使用迭代器遍历---正向遍历
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();
}
ArrayList和LinkedList的区别
不同点 | ArrayList | LinkedList |
---|---|---|
存储空间上 | 物理上一定连续 | 逻辑上连续 , 但物理上不一定连续 |
随机访问 | 支持 : 时O(1) | 不支持 : 时O(N) |
插入/删除 | 需要搬移元素 , 效率低 , 时O(N) | 只需要修改引用的指向 , 时O(1) |
插入 | 空间不够时需要扩容 | 没有容量的概念 |
应用场景 | 有限元素个数高效存储+频繁访问 | 任意位置插入和删除频繁 |