LinkedList LinkedList
Write at the beginning
- Before long article mentioned:
动态数组
, ,栈
,队列
underlying all rely on静态数组
, throughresize()
dynamic expansion operation. - However
链表
, it is真正的动态数据结构
and the same is true最简单的动态数据结构
. 链表
This kind of data structure can help us understand the concepts of computer指针(引用)
and so递归
on.
Node
-
Data is stored in
节点
it, and as many nodes are produced for assembling as many nodes are needed, but the ability of random access is lost, which is suitable for the case where the index has no semantics .class node { E e; Node next; }
Linked list data structure creates LinkedList, in order to ensure the security of node information, the internal class method is used to construct
/**
* @author by Jiangyf
* @classname LinkedList
* @description 链表
* @date 2019/9/28 13:08
*/
public class LinkedList<E> {
/**
* 节点内部类
*/
private class Node {
public E e;
public Node next;
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
public Node(E e) {
this(e, null);
}
public Node() {
this(null, null);
}
@Override
public String toString() {
return "Node{" +
"e=" + e +
", next=" + next +
'}';
}
}
private Node head;
int size;
public LinkedList() {
head = null;
size = 0;
}
// 获取链表容量
public int getSize() {
return size;
}
// 判断链表是否为空
public boolean isEmpty() {
return size == 0;
}
}
Add operation method
-
Add elements from the head of the linked list
public void addFirst(E e) { head = new Node(e, head); size ++; }
-
index
Add elements from the middle position of the linked list , note:先连后断
public void add(int index, E e) throws IllegalAccessException { // 索引校验 if (index < 0 || index > size) { throw new IllegalAccessException("Add failed. Illegal index."); } // 判断是操作是否为头部添加 if (index == 0) { addFirst(e); } else { // 创建前置节点 Node prev = head; // 定位到待插入节点前一个节点 for (int i = 0; i < index -1 ; i++) { prev = prev.next; } prev.next = new Node(e, prev.next); size ++; } }
-
Add an element at the end of the linked list
public void addLast(E e) throws IllegalAccessException { add(size, e); }
-
Set up for the linked list to
虚拟头结点(dummyHead)
solve the logical inconsistency between adding from the head and adding from other positions虚拟头结点
It is set as an internal mechanism of the linked list, and the originalhead
node is improved todummyHead.next = head
add logic to the adaptive node.- Modify the code
-
Add
虚拟头结点dummyHead
without storing any contentprivate Node dummyHead; public LinkedList() { dummyHead = new Node(null, null); size = 0; }
-
Modification
add(index, e)
method// 从链表中间添加元素 先连后断 public void add(int index, E e) throws IllegalAccessException { // 索引校验 if (index < 0 || index > size) { throw new IllegalAccessException("Add failed. Illegal index."); } // 创建前置节点 Node prev = dummyHead; // 定位到待插入节点前一个节点,遍历index次原因为 dummyHead为head节点前一个节点 for (int i = 0; i < index ; i++) { prev = prev.next; } prev.next = new Node(e, prev.next); size ++; }
-
Modification
addFirst(e)
methodpublic void addFirst(E e) throws IllegalAccessException { add(0, e); }
-
-
Get
index
the node element at the specified positionpublic E get(int index) throws IllegalAccessException { // 索引校验 if (index < 0 || index > size) { throw new IllegalAccessException("Get failed. Illegal index."); } // 定位到head节点 Node cur = dummyHead.next; for (int i = 0; i < index; i++) cur = cur.next; return cur.e; }
-
Get head node and tail node
public E getFirst() throws IllegalAccessException { return get(0); } public E getLast() throws IllegalAccessException { return get(size - 1); }
-
Update the specified location element
public void set(int index, E e) throws IllegalAccessException { // 索引校验 if (index < 0 || index > size) { throw new IllegalAccessException("Set failed. Illegal index."); } Node cur = dummyHead.next; for (int i = 0; i < index ; i++) cur = cur.next; cur.e = e; }
-
Find if there is an element in the linked list
public boolean contains(E e) { Node cur = dummyHead.next; while(cur != null) { if (cur.e.equals(e)) { return true; } cur = cur.next; } return false; }
-
Delete linked list element node
public E remove(int index) throws IllegalAccessException { // 索引校验 if (index < 0 || index > size) { throw new IllegalAccessException("Remove failed. Illegal index."); } // 定位到待删除节点的前一节点 Node prev = dummyHead; for (int i = 0; i < index - 1 ; i++) prev = prev.next; // 保存待删除节点 Node retNode = prev.next; // 跨过待删除节点进行连接 prev.next = retNode.next; // 待删除节点next置空 retNode.next = null; size --; return retNode.e; } public E removeFirst() throws IllegalAccessException { return remove(0); } public E removeLast() throws IllegalAccessException { return remove(size - 1); }
-
Through the above method, we can analyze that the average time complexity of the CURD operation of the linked list is O(n), and all the operations of the linked list need to be traversed.
Think about it carefully, what if the operation on the linked list is limited to 头部
it? Thinking carefully, is it true that the complexity is reduced to O(1)? And because the linked list is dynamic, it will not cause a waste of space, so if and only if it is 头部
operated, the advantage is very obvious!
-
Based on
头部操作
, implemented with a linked list栈
, aboutStack接口
, you can view the data structure_2: stackpublic class LinkedListStack<E> implements Stack<E> { private LinkedList<E> list; public LinkedListStack(LinkedList<E> list) { this.list = new LinkedList<>(); } @Override public int getSize() { return list.getSize(); } @Override public boolean isEmpty() { return list.isEmpty(); } @Override public void push(E e) throws IllegalAccessException { list.addFirst(e); } @Override public E pop() throws IllegalAccessException { return list.removeFirst(); } @Override public E peek() throws IllegalAccessException { return list.getFirst(); } }
-
Now that we have achieved, it would take
链表栈
and数组栈
relatively bar, create a test functionprivate static double testStack(Stack<Integer> stack, int opCount) throws IllegalAccessException { long startTime = System.nanoTime(); Random random = new Random(); for (int i = 0; i < opCount; i ++) stack.push(random.nextInt(Integer.MAX_VALUE)); for (int i = 0; i < opCount; i ++) stack.pop(); long endTime = System.nanoTime(); return (endTime - startTime) / 1000000000.0; }
-
Create
链表栈
and数组栈
perform one million push and outbound operations respectively , and compare the time of the two, it seems to be链表栈
better.
-
Continue to increase the amount of data to 10 million times of stacking and popping operations , at this time the performance of the linked list stack is not good.
The reason is roughly:数组栈
the pop and push operations are processed based on the tail of the array;链表栈
the pop and push operations are based on the operation of the head of the linked list, and the insurance operation contains the operation of creating a new node (new Node), so it is time-consuming. -
栈
The structure has been created, so it队列
is also indispensable.数组队列
The construction in the previous article is to operate from the head and tail. Since the complexity of the dequeue operation is O(n), the complexity of the enqueue operation is O(1 ), the queue structure is optimized, so an array implementation is produced循环队列
, and the performance is much higher than ordinary数组队列
. So we analyze链表
this structure:-
Due to the presence of the
head
head pointer, the complexity of the head operation is O(1) [setting of dummyHead] -
So based on this principle, adding a
tail
tail pointer to record the tail (index) of the linked list, can the operation complexity of the tail be reduced?head
The positioning of the pointer depends on the structure setting of the virtual head pointer, and thetail
pointer does not have this setting. If you want to delete the tail element, you need to locate the element before the element to be deleted, and you still need to traverse. -
Based on the above,
链表节点Node的next
setting more conducive to us from链表首部进行出队操作
,链表尾部进行入队操作
. -
Adopt
head
+tail
transform ourLinkedListQueue
/** * @author by Jiangyf * @classname LinkedListQueue * @description 链表队列 * @date 2019/9/28 16:35 */ public class LinkedListQueue<E> implements Queue<E> { /** * 节点内部类 */ private class Node { public E e; public Node next; public Node(E e, Node next) { this.e = e; this.next = next; } public Node(E e) { this(e, null); } public Node() { this(null, null); } @Override public String toString() { return "Node{" + "e=" + e + ", next=" + next + '}'; } } private Node head, tail; private int size; public LinkedListQueue() { this.head = null; this.tail = null; this.size = 0; } @Override public int getSize() { return size; } @Override public boolean isEmpty() { return size == 0; } @Override public void enqueue(E e) { // 入队 从链表尾部进行 if (tail == null) { // 表示链表为空 tail = new Node(e); head = tail; } else { // 不为空,指向新创建的元素,尾指针后移 tail.next = new Node(e); tail = tail.next; } size ++; } @Override public E dequeue() { // 出队 从链表头部进行 if (isEmpty()) { throw new IllegalArgumentException("Queue is empty"); } // 获取待出队元素 Node retNode = head; // 头指针后移 head = head.next; // 待删除元素与链表断开 retNode.next = null; if (head == null) { // 链表中仅有一个元素的情况,头指针移动后变为空链表 tail = null; } size --; return retNode.e; } @Override public E getFront() { if (isEmpty()) { throw new IllegalArgumentException("Queue is empty"); } return head.e; } }
-
Also, with the previous
数组队列
,循环队列
,链表队列
carried out performance tests (the order of 100,000)
when seen circular queue and the queue list is far higher than the performance of the array queue head and tail pointers reason is the dynamic control data structure, and the number of columns listed team Data replication is repeated, so it takes a long time.
-