Data structure - list (2)

Doubly linked list

Detailed above a singly linked list in this section explain the main principle for doubly linked list, as well as the advantages and disadvantages of each operation.

 

For single doubly linked list, it supports two directions, each node has more than one successor node pointer pointing to the back of the next, but also a precursor prev pointer pointing to the preceding node, in conjunction with a map to see:

As can be seen from the figure, two-way linked list requires additional space to store the address of the next node and the predecessor node. So store the same data, one-way linked list doubly linked list than take up more space. Although two pointers more waste of space, but can support two-way traverse, this also brings flexibility doubly-linked list operations. Summarize the advantages and disadvantages of two-way linked list:

advantage:

  • For a given linked list node can be accessed from two directions. However, in a one-way linked list, only get a pointer node precursor nodes before you can delete the node.
  • In the doubly linked list, each node has a pointer to a predecessor node, you can go back directly to the predecessor node.

Disadvantages:

  • Each node need to add an extra pointer, and therefore requires more space overhead
  • Insertion and deletion of nodes is more time-consuming (More pointers operation)

Doubly linked list of actions:

  First, we initialize a doubly linked list:

package com.xb.other;

/**
 * Author Mr. Guo
 * Create 2019/8/11 - 12:22
 */
public class DLLNode {
    private int data;
    private DLLNode next;
    private DLLNode preivous;

    public DLLNode(int data) {
        this.data = data;
    }

    public int getData() {
        return data;
    }

    public DLLNode getNext() {
        return next;
    }

    public DLLNode getPreivous() {
        return preivous;
    }

    public void setData(int data) {
        this.data = data;
    }

    public void setNext(DLLNode next) {
        this.next = next;
    }

    public void setPreivous(DLLNode preivous) {
        this.preivous = preivous;
    }
}

  insert:

    • The head insertion node list

      In this case, before the new node is inserted into the header node. Predecessor and successor pointers modifying pointer values ​​requires two steps:

    1. 1, the new successor node pointer updated to point to the header of the current node, the new node pointer assignment precursor to NULL.
    2.  


      2, the original header precursor node pointer is updated to the new node, then the node as a new header.

        

    • At the end of the linked list insertion node

       In this case, the need to traverse the list to the last, and then insert the new node.

    1. 1, the new successor node pointer points to NULL, the predecessor node pointer to the end of the table.

    1. 2, the tail pointer table original successor node to the new node. 
    2.    
    • In the middle of a linked list insertion node  

       In this case, it is necessary to know the position of the node traversal table, and then insert the new node.

       1、新结点的后继指针指向需要插入新结点的位置结点的后继指针。然后令新结点的前驱指针指向位置结点。

        

       2、位置结点的后继指针指向新结点前驱指针,位置结点的后继结点的前驱指针指向新结点的后继指针  

       

   代码实现:

 /**
     * 获取双向链表的长度
     */
    public int getLength(DLLNode headNode) {
        int length = 0;
        DLLNode currentNode = headNode;
        while (currentNode != null) {
            length++;
            currentNode = currentNode.getNext();
        }
        return length;
    }

    /**
     * 链表插入操作
     */
    public DLLNode DLLInsert(DLLNode headNode, DLLNode nodeToInsert, int position) {
        // 如果链表为空,则直接插入
        if (headNode == null) {
            return nodeToInsert;
        }
        //先获取链表长度
        int size = getLength(headNode);
        // 如果插入位置超出了链表范围,则直接返回头结点,插入范围在[1,size+1]
        if (position > size + 1 || position < 1) {
            System.out.println("Posititon of nodeToInsert is invalid." + "The valid inputs  are1 to " + (size + 1));
            return headNode;
        }
        // 如果是在头结点插入
        if (position == 1) {
            nodeToInsert.setNext(headNode);
            headNode.setPreivous(nodeToInsert);
            return nodeToInsert;
        } else {  // 如果在链表的中间或尾部插入
            DLLNode previousNode = headNode;
            int count = 1;
            while (count < position - 1) {
                previousNode = previousNode.next;
                count++;
            }
            DLLNode currentNode = previousNode.getNext();
            nodeToInsert.setNext(currentNode);
            if (currentNode != null) {
                currentNode.setPreivous(nodeToInsert);
            }
            previousNode.setNext(currentNode);
            currentNode.setPreivous(previousNode);
        }
        return headNode;
    }

  复杂度分析:

      时间复杂度为O(n)。在最坏情况下,需要在链表的尾部插入结点。

      空间复杂度为O(1)。用于创建临时变量。

  删除:

    • 删除链表的表头结点
    •  

      这种情况下,需要从双向链表中删除第一个结点,需要通过两步来实现:

      1、创建一个临时结点,让它与表头指向同一个结点

       

      2、修改表头结点指针,使其指向下一个结点(headnode=headnode.next),将表头结点的前驱指针更改为NULL(head.setpreivous(NULL)),然后移除临时结点。

      

    • 删除链表的表尾结点

      该种情况下处理,比删除双向链表的第一个结点要复杂一些,因为要找到尾节点的前驱结点,需要通过三步来实现:

      1、遍历链表,在遍历的时候,还要保持前驱(前一次经过的)结点的地址。当遍历到表尾时,有两个指针分别指向表尾结点的tail指针和指向表尾结点的前驱结点指针。

      

      2、更新表尾的前驱结点的next指针为NULL

       

      3、移除表尾结点

       

    • 删除链表的中间结点 

      这种情况下,删除的结点总是位于两个结点之间,因此表头和表尾的值不需要更新。该删除操作可以分为两步来完成:

      1、遍历链表,遍历链表的时候保存前驱(前一次经过的)结点。一旦找到要删除的结点。更改前驱结点的next指针使其指向被删除结点的下一个结点,更该被删除结点的后继结点的previous指针指向被删除结点的前驱结点。

      

      2、移除被删除的当前结点

      

  代码实现

/**
     * 链表的删除操作
     */
    public DLLNode DLLDelete(DLLNode headNode, int position) {
        //获取链表的长度
        int size = getLength(headNode);
        //如果删除的位置不在链表的范围内,则直接返回
        if (position > size || position < 1) {
            System.out.println("Posititon of node to delete is invalid." + "The valid inputs  are1 to " + size);
            return headNode;
        }
        if (position == 1) {
            DLLNode currentNode = headNode.getNext();
            headNode = null;
            currentNode.setPreivous(null);
            return currentNode;
        } else {
            DLLNode previousNode = headNode;
            int count = 1;
            while (count < position -1) {
                previousNode = preivous.next;
                count++;
            }
            //要删除的当前结点
            DLLNode currentNode = previousNode.getNext();
            DLLNode laterNext = currentNode.getNext();
            previousNode.setNext(laterNext);
            if (laterNext != null){
                //如果被删除的结点不是NULL结点,那么设置其前驱结点指针指向被删除结点的前驱指针
                laterNext.setPreivous(previousNode);
                currentNode = null;
            }
        }
        return headNode;
    }

  时间复杂度分析:

    时间复杂度为:O(n),在最差的情况下,可能需要删除链表尾部结点

    空间复杂度为:O(1),仅用于创建一个临时变量。

Guess you like

Origin www.cnblogs.com/Gxiaobai/p/11343479.html