LinkedList LinkedList
Escribe al principio
- Antes de largo artículo menciona:
动态数组
,,栈
,队列
subyacente a todos se basan en静态数组
, a través deresize()
la operación de expansión dinámica. - Sin embargo
链表
, es真正的动态数据结构
y lo mismo es cierto最简单的动态数据结构
. 链表
Este tipo de estructura de datos puede ayudarnos a comprender los conceptos de computadora,指针(引用)
etc.递归
Nodo
-
Los datos se almacenan en
节点
él y se producen tantos nodos para ensamblar como se necesiten, pero se pierde la capacidad de acceso aleatorio, lo cual es adecuado para el caso en el que el índice no tiene semántica .class node { E e; Node next; }
La estructura de datos de la lista vinculada crea LinkedList, para garantizar la seguridad de la información del nodo, se utiliza el método de clase interna para construir
/**
* @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;
}
}
Agregar método de operación
-
Agregar elementos del encabezado de la lista vinculada
public void addFirst(E e) { head = new Node(e, head); size ++; }
-
index
Agregue elementos desde la posición media de la lista vinculada , tenga en cuenta:先连后断
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 ++; } }
-
Agregue un elemento al final de la lista vinculada
public void addLast(E e) throws IllegalAccessException { add(size, e); }
-
Configure la lista vinculada para
虚拟头结点(dummyHead)
resolver la inconsistencia lógica entre agregar desde el encabezado y agregar desde otras posiciones虚拟头结点
Se establece como un mecanismo interno de la lista vinculada y elhead
nodo original se mejora paradummyHead.next = head
agregar lógica al nodo adaptativo.- Modifica el código
-
Agregue
虚拟头结点dummyHead
sin almacenar ningún contenidoprivate Node dummyHead; public LinkedList() { dummyHead = new Node(null, null); size = 0; }
-
add(index, e)
Método de modificación// 从链表中间添加元素 先连后断 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 ++; }
-
addFirst(e)
Método de modificaciónpublic void addFirst(E e) throws IllegalAccessException { add(0, e); }
-
-
Obtener
index
el elemento de nodo en la posición especificadapublic 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; }
-
Obtener el nodo principal y el nodo final
public E getFirst() throws IllegalAccessException { return get(0); } public E getLast() throws IllegalAccessException { return get(size - 1); }
-
Actualizar el elemento de ubicación especificado
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; }
-
Encuentre si hay un elemento en la lista vinculada
public boolean contains(E e) { Node cur = dummyHead.next; while(cur != null) { if (cur.e.equals(e)) { return true; } cur = cur.next; } return false; }
-
Eliminar nodo de elemento de lista vinculado
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); }
-
A través del método anterior, podemos analizar que la complejidad de tiempo promedio de la operación CURD de la lista vinculada es O (n), y todas las operaciones de la lista vinculada deben atravesarse.
Piénselo detenidamente, ¿qué pasa si la operación en la lista vinculada se limita a 头部
ella? Pensando con cuidado, ¿es cierto que la complejidad se reduce a O (1)? Y debido a que la lista vinculada es dinámica, no causará una pérdida de espacio, por lo que si y solo si se 头部
opera, ¡la ventaja es muy obvia!
-
Basado en
头部操作
, implementado con una lista vinculada栈
, sobreStack接口
, puede ver la estructura de datos_2: pilapublic 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(); } }
-
Ahora que lo hemos logrado, tomaría
链表栈
y数组栈
relativamente bar, crear una función de pruebaprivate 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; }
-
Cree
链表栈
y数组栈
realice un millón de operaciones push y outbound respectivamente , y compare el tiempo de los dos, parece ser链表栈
mejor.
-
Continúe aumentando la cantidad de datos a 10 millones de veces de operaciones de apilamiento y extracción , en este momento el rendimiento de la pila de listas vinculadas no es bueno.
La razón es aproximada:数组栈
las operaciones pop y push se procesan en función de la cola de la matriz;链表栈
las operaciones pop y push se basan en la operación del encabezado de la lista vinculada, y la operación de aseguramiento contiene la operación de crear un nuevo nodo (nuevo nodo), por lo que lleva mucho tiempo. -
栈
La estructura ha sido creada, por lo队列
que también es indispensable.数组队列
La construcción en el artículo anterior es para operar desde la cabeza y la cola. Dado que la complejidad de la operación de sacar de cola es O (n), la complejidad de la operación de poner en cola es O (1 ), la estructura de la cola está optimizada, por lo que se produce una implementación de matriz循环队列
y el rendimiento es mucho mayor de lo normal数组队列
. Entonces analizamos链表
esta estructura:-
Debido a la presencia del
head
puntero de la cabeza, la complejidad de la operación de la cabeza es O (1) [ajuste de dummyHead] -
Entonces, según este principio, agregando un
tail
puntero de cola para registrar la cola (índice) de la lista vinculada, ¿se puede reducir la complejidad de la operación de la cola?head
El posicionamiento del puntero depende de la configuración de la estructura del puntero de cabeza virtual, y eltail
puntero no tiene esta configuración. Si desea eliminar el elemento de cola, debe ubicar el elemento antes del elemento que se eliminará, y aún así necesidad de atravesar. -
Con base en lo anterior,
链表节点Node的next
la configuración más propicio a nosotros de链表首部进行出队操作
,链表尾部进行入队操作
. -
Adoptar
head
+tail
transformar nuestroLinkedListQueue
/** * @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; } }
-
También, con la anterior
数组队列
,循环队列
,链表队列
llevado a cabo pruebas de rendimiento (el orden de 100.000)
cuando se ven cola circular y la lista de la cola es mucho mayor que el rendimiento de la cola de matriz de cabeza y cola punteros razón es la estructura de datos de control dinámico, y el número de columnas enumeradas equipo La replicación de datos se repite, por lo que lleva mucho tiempo.
-