Data structure_4: linked list

LinkedList LinkedList

Write at the beginning

  • Before long article mentioned: 动态数组, , , 队列underlying all rely on 静态数组, through resize()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 ++;
      }
    
  • indexAdd 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 original headnode is improved to dummyHead.next = headadd logic to the adaptive node.
    • Modify the code
      • Add 虚拟头结点dummyHeadwithout storing any content

          	private 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)method

        public void addFirst(E e) throws IllegalAccessException {
                  
                  
              add(0, e);
        }
        
  • Get indexthe node element at the specified position

     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;
     }
    
  • 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 , about Stack接口, you can view the data structure_2: stack

     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();
           }
       }
    
  • Now that we have achieved, it would take 链表栈and 数组栈relatively bar, create a test function

    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;
    }
    
  • Create 链表栈and 数组栈perform one million push and outbound operations respectively , and compare the time of the two, it seems to be 链表栈better.
    Insert picture description here

  • 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. Insert picture description here
    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 headhead pointer, the complexity of the head operation is O(1) [setting of dummyHead]

    • So based on this principle, adding a tailtail pointer to record the tail (index) of the linked list, can the operation complexity of the tail be reduced? headThe positioning of the pointer depends on the structure setting of the virtual head pointer, and the tailpointer 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的nextsetting more conducive to us from 链表首部进行出队操作, 链表尾部进行入队操作.

    • Adopt head+ tailtransform 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)
      Insert picture description here
      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.


Finally, the above code has been uploaded to the personal warehouse, and friends who need it can download and view

Guess you like

Origin blog.csdn.net/Nerver_77/article/details/101619454