Estructura de datos_4: lista vinculada

LinkedList LinkedList

Escribe al principio

  • Antes de largo artículo menciona: 动态数组,, , 队列subyacente a todos se basan en 静态数组, a través de resize()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 ++;
      }
    
  • indexAgregue 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 el headnodo original se mejora para dummyHead.next = headagregar lógica al nodo adaptativo.
    • Modifica el código
      • Agregue 虚拟头结点dummyHeadsin almacenar ningún contenido

          	private 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ón

        public void addFirst(E e) throws IllegalAccessException {
                  
                  
              add(0, e);
        }
        
  • Obtener indexel elemento de nodo en la posición especificada

     public 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 , sobre Stack接口, puede ver la estructura de datos_2: pila

     public 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 prueba

    private 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.
    Inserte la descripción de la imagen aquí

  • 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. Inserte la descripción de la imagen aquí
    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 headpuntero 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 tailpuntero de cola para registrar la cola (índice) de la lista vinculada, ¿se puede reducir la complejidad de la operación de la cola? headEl posicionamiento del puntero depende de la configuración de la estructura del puntero de cabeza virtual, y el tailpuntero 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的nextla configuración más propicio a nosotros de 链表首部进行出队操作, 链表尾部进行入队操作.

    • Adoptar head+ tailtransformar 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)
      Inserte la descripción de la imagen aquí
      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.


Finalmente, el código anterior se cargó en el almacén personal y los amigos que lo necesiten pueden descargarlo y verlo.

Supongo que te gusta

Origin blog.csdn.net/Nerver_77/article/details/101619454
Recomendado
Clasificación