LinkedList与链表
- 1. ArrayList的缺陷
- 2. 链表
- 2.1 链表的概念及结构
- 2.2 链表的实现
- 3. LinkedList的模拟实现
- 4.LinkedList的使用
- 4.1 什么是LinkedList
- 4.2LinkedList的使用
- 5. ArrayList和LinkedList的区别
1. ArrayList的缺陷
上节课已经熟悉了ArrayList的使用,并且进行了简单模拟实现。通过源码知道,ArrayList底层使用数组来存储元素:
public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable{// ...// 默认容量是10private static final int DEFAULT_CAPACITY = 10;//...// 数组:用来存储元素transient Object[] elementData; // non-private to simplify nested class access// 有效元素个数private int size;public ArrayList(int initialCapacity) {if (initialCapacity > 0) {this.elementData = new Object[initialCapacity];} else if (initialCapacity == 0) {this.elementData = EMPTY_ELEMENTDATA;} else {throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);}}// ...
}
由于其底层是一段连续空间,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比较低,因此ArrayList不适合做任意位置插入和删除比较多的场景。因此:java集合中又引入了LinkedList,即链表结构。
2. 链表
2.1 链表的概念及结构
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。
实际中链表的结构非常多样
- 单向或者双向
- 带头或者不带头
- 循环或者非循环
虽然有这么多的链表的结构,但是我们重点掌握两种: - 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多
- 无头双向链表:在Java的集合框架库中LinkedList底层实现就是无头双向循环链表。
2.2 链表的实现
自己实现接口ILIST
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();
}
链表实现
public class MySingleList implements IList {//节点的内部类static class ListNode {public int val;public ListNode next;public ListNode(int val) {this.val = val;}}public ListNode head;//public int usedSize;//可以定义public void createList() {ListNode node1 = new ListNode(12);ListNode node2 = new ListNode(23);ListNode node3 = new ListNode(34);ListNode node4 = new ListNode(45);ListNode node5 = new ListNode(56);node1.next = node2;node2.next = node3;node3.next = node4;node4.next = node5;this.head = node1;} public void addFirst(int data) {ListNode node = new ListNode(data);if (this.head == null) {this.head = node;} else {node.next = this.head;this.head = node;}} public void addLast(int data) {ListNode node = new ListNode(data);ListNode cur = this.head;if (this.head == null) {this.head = node;} else {//找到尾巴while (cur.next != null) {cur = cur.next;}//cur 现在指向了最后一个节点cur.next = node;}} public void addIndex(int index, int data) {if (index < 0 || index > size()) {//抛自定义的异常return;}if (index == 0) {addFirst(data);return;}if (index == size()) {addLast(data);return;}ListNode cur = searchPrev(index);ListNode node = new ListNode(data);node.next = cur.next;cur.next = node;}private ListNode searchPrev(int index) {ListNode cur = this.head;int count = 0;while (count != index - 1) {cur = cur.next;count++;}return cur;} 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 remove(int key) {if (this.head == null) {//一个节点都没有 无法删除!return;}if (this.head.val == key) {this.head = this.head.next;return;}//1. 找到前驱ListNode cur = findPrev(key);//2、判断返回值是否为空?if (cur == null) {System.out.println("没有你要删除的数字");return;}//3、删除ListNode del = cur.next;cur.next = del.next;}/*** 找到关键字key的前一个节点的地址** @param key* @return*/private ListNode findPrev(int key) {ListNode cur = this.head;while (cur.next != null) {if (cur.next.val == key) {return cur;}cur = cur.next;}return null;} public void removeAllKey(int key) {if (this.head == null) {return;}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 int size() {int count = 0;ListNode cur = this.head;while (cur != null) {count++;cur = cur.next;}return count;} public void clear() {ListNode cur = head;while (cur != null) {ListNode curNext = cur.next;//cur.val = null;cur.next = null;cur = curNext;}head = null;} public void display() {ListNode cur = this.head;while (cur != null) {System.out.print(cur.val + " ");cur = cur.next;}System.out.println();}/*** 这个是从指定位置开始打印** @param newHead*/public void display(ListNode newHead) {ListNode cur = newHead;while (cur != null) {System.out.print(cur.val + " ");cur = cur.next;}System.out.println();}
}
3. LinkedList的模拟实现
// 2、无头双向链表实现
public class MyLinkedList {//头插法public void addFirst(int data){ }//尾插法public void addLast(int data){}//任意位置插入,第一个数据节点为0号下标public void addIndex(int index,int data){}//查找是否包含关键字key是否在单链表当中public boolean contains(int key){}//删除第一次出现关键字为key的节点public void remove(int key){}//删除所有值为key的节点public void removeAllKey(int key){}//得到单链表的长度public int size(){}public void display(){}public void clear(){}
}
4.LinkedList的使用
4.1 什么是LinkedList
LinkedList的底层是双向链表结构(链表后面介绍),由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。
在集合框架中,LinkedList也实现了List接口
【说明】
- LinkedList实现了List接口
- LinkedList的底层使用了双向链表
- LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问
- LinkedList的任意位置插入和删除元素时效率比较高,时间复杂度为O(1)
- LinkedList比较适合任意位置插入的场景
4.2LinkedList的使用
- LinkedList的构造
public static void main(String[] args) {// 构造一个空的LinkedListList<Integer> list1 = new LinkedList<>();List<String> list2 = new java.util.ArrayList<>();list2.add("JavaSE");list2.add("JavaWeb");list2.add("JavaEE");// 使用ArrayList构造LinkedListList<String> list3 = new LinkedList<>(list2);
}
- LinkedList的其他常用方法介绍
- LinkedList的遍历
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);System.out.println(list.size());
// foreach遍历for (int e:list) {System.out.print(e + " ");}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();}