java 单向链表与双向链表的实现

链表

单向链表

单向链表概念

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

这是一种物理结构,不是树那样的逻辑结构。链表和顺序表两种物理结构,说明的是数据元素在内存中是如何存储的。顺序表是连续顺序存储,例如数组。而链表是非连续非顺序的。

基本API的java代码实现

代码如下,实现基本的增查删改的功能。

public class SinglyLinkedList<T> {
    private class Node{
        private T value;
        private Node next;
        public Node(T value){
            this.value=value;
            this.next=null;
        }//constructor
    }

    private Node head;
    private Node tail;
    public SinglyLinkedList(){
        this.head=null;
        this.tail=null;
    }
    //empty list?
    public boolean Empty(){
        return this.head==null;
    };

    //add to front
    //两种情况:链表为空和正常
    public void PushFront(T key){
        Node node=new Node(key);
        node.next=this.head;
        this.head=node;
        if(this.tail==null) this.tail=this.head;//原本无元素的话,需要将尾节点设置为刚插入的新节点
    }
    //return front item
    public T TopFront(){
        if(Empty()) throw new IndexOutOfBoundsException("linked list is empty");
        return head.value;
    }
    //remove front item
    //两种情况:只有一个元素和正常
    public void PopFront(){
        if(Empty()) return;
        this.head=this.head.next;
        if(this.head==null) this.tail=null;//只有一个元素,pop后变为空链表
    }
    //add to back
    public void PushBack(T key){
        Node node=new Node(key);
        if(this.tail==null){
            this.head=this.tail=node;
        }
        else{
            this.tail.next=node;
            this.tail=node;
        }
    }
    //return back item
    public T TopBack(){
        if(Empty()) throw new IndexOutOfBoundsException("linked list is empty");
        return tail.value;
    }
    //remove back item
    //三种情况:只有一个元素和两个元素和正常
    public void PopBack(){
        if(Empty()) return;
        if(this.head==this.tail) this.head=this.tail=null;//只有一个元素,无法遍历到倒数第二个
        else {
            Node node = this.head;
            while (node.next.next != null) node = node.next;//遍历到倒数第二个元素
            this.tail = node;
            node.next=null;
        }
    }
    //is key in list?
    public boolean Find(T key){
        Node node=this.head;
        while(node!=null){
            if(node.value==key) return true;
            node=node.next;
        }
        return false;
    }
    //remove key from list
    public void Erase(T key){
        Node node=this.head;
        while(node!=null){
            if(node.value==key){
                if(this.head==this.tail) this.head=this.tail=null;//只有一个元素
                else if(node==this.head) this.head=this.head.next;//删除的是头元素
                else{
                    Node temp=this.head;
                    while(temp.next!=node) temp=temp.next;//遍历到node的前一个节点
                    temp.next=node.next;
                    if(node==this.tail) this.tail=temp;//如果删除的是尾节点,要改变尾节点
                }
            }
            node=node.next;
        }
    }
    //adds keys before node
    public void AddBefore(Node node,T key){
        Node New=new Node(key);
        if(node==this.head){
            New.next=this.head;
            this.head=New;
        }//插在头节点前不用遍历
        else{
            Node temp=this.head;
            while(temp!=null){
                if(temp.next==node){
                    temp.next=New;
                    New.next=node;
                }
                temp=temp.next;
            }
        }
    }
    //adds key after node
    public void AddAfter(Node node,T key){
        Node New=new Node(key);
        New.next=node.next;
        node.next=New;
        if(node==this.tail) this.tail=New;//如果插在尾节点后要变更尾节点
    }
    //print the value of the whole linked list
    public void Print(){
        Node node=this.head;
        while(node!=null) {
            System.out.print(node.value+" ");
            node=node.next;
        }
        System.out.println();
    }

    public Node getHead(){
        return head;
    }

    public Node getNode(){
        return head.next.next.next;
    }

    public static void main(String[] args){
        SinglyLinkedList<Integer> list= new SinglyLinkedList<>();
        for(int i=5;i>0;i--) list.PushFront(i);//test PushFront
        for(int i=6;i<11;i++) list.PushBack(i);//test PushBackT
        list.Print();
        System.out.println(list.TopFront()+" "+list.TopBack());//test TopFront and TopBack
        list.PopFront();
        list.PopBack();
        list.Print();
        list.AddBefore(list.getHead(),11);
        list.AddBefore(list.getNode(),12);
        list.Print();
        list.AddAfter(list.getHead(),13);
        list.AddAfter(list.getNode(),14);
        list.Print();
        System.out.println(list.Find(22)+" "+list.Find(9));
        list.Erase(11);
        list.Print();
    }
}

test

各个API的时间复杂度O(n)

接下来分析时间复杂度

Singly-Linked List no tail with tail
PushFront(key) O(1)
TopFront() O(1)
PopFront() O(1)
PushBack(key) O(n) O(1)
TopBack() O(n) O(1)
PopBack() O(n)
Find(key) O(n)
Erase(key) O(n)
Empty() O(1)
AddBefore(Node,Key) O(n)
AddAfter(Node,Key) O(1)

很显然,每当我们的操作是从后往前进行时,复杂度就会很高。因为我们需要从头开始遍历.例如PopBack,我们需要从头开始遍历。像二分搜索,更是难以实现。如果有一个和next相对的previous指针,事情会简单很多。这就是双向链表的好处。

双向链表

双向链表概念

功能和单向链表一样,就是每次操作时要多对prev节点操作。每次插入和删除操作时,要处理四个节点的引用,而不是两个。

单向链表插入时,我们只需考虑改变插入节点的前一个节点的next(如果不是插入在头节点前),和插入节点的next。而双向链表插入时我们需要考虑插入节点的前一个节点的next,插入节点的prev和next,插入节点的后一个节点的prev((如果不是插入在尾节点后)。所以需要特别仔细。

基本API的代码实现

public class DoublyLinkedList<T> {
    private class Node{
        private T value;
        private Node next;
        private Node prev;
        public Node(T value){
            this.value=value;
            this.next=null;
            this.prev=null;
        }
    }

    private Node head;
    private Node tail;
    public DoublyLinkedList(){
        this.head=null;
        this.tail=null;
    }
    //empty list?
    public boolean Empty(){
        return this.head==null;
    }
    //add to front
    //两种情况:链表为空和正常
    public void PushFront(T key){
        Node node=new Node(key);//node.prev=null;
        node.next=this.head;
        this.head=node;
        if(this.tail==null) this.tail=this.head;//原本无元素的话,需要将尾节点设置为刚插入的新节点
        else this.head.next.prev=node;//原本有元素的话设置原head的prev节点
    }
    //return front item
    public T TopFront(){
        if(Empty()) throw new IndexOutOfBoundsException("linked list is empty");
        return head.value;
    }
    //remove front item
    //两种情况:只有一个元素和正常
    public void PopFront(){
        if(Empty()) return;
        if(this.head!=this.tail) this.head.next.prev=null;//两个元素及以上要设置prev节点
        this.head=this.head.next;
        if(this.head==null) this.tail=null;//只有一个元素,pop后变为空链表
    }
    //add to back
    public void PushBack(T key){
        Node node=new Node(key);
        if(this.tail==null){
            this.head=this.tail=node;
        }
        else{
            this.tail.next=node;
            node.prev=this.tail;
            this.tail=node;
        }
    }
    //return back item
    public T TopBack(){
        if(Empty()) throw new IndexOutOfBoundsException("linked list is empty");
        return tail.value;
    }
    //remove back item
    //拥有prev节点后无须从头遍历
    public void PopBack(){
        if(Empty()) return;
        if(this.head==this.tail) this.head=this.tail=null;
        else{
            this.tail=this.tail.prev;
            this.tail.next=null;
        }
    }
    //is key in list?
    public boolean Find(T key){
        Node node=this.head;
        while(node!=null){
            if(node.value==key) return true;
            node=node.next;
        }
        return false;
    }
    //remove key from list
    public void Erase(T key){
        Node node=this.head;
        while(node!=null){
            if(node.value==key){
                if(this.head==this.tail) this.head=this.tail=null;//只有一个元素
                else if(node==this.head) {
                    this.head=this.head.next;//删除的是头元素
                    this.head.prev=null;
                }
                else{
                    node.prev.next=node.next;
                    if(node==this.tail) this.tail=node.prev;//如果删除的是尾节点,要改变尾节点
                    else node.next.prev=node.prev; //非尾节点要改变被删节点的下一个节点的prev
                }
            }
            node=node.next;
        }
    }
    //adds keys before node
    //拥有prev节点后不用遍历
    public void AddBefore(Node node, T key){
        Node New=new Node(key);
        if(node==this.head){
            New.next=this.head;
            this.head.prev=New;
            this.head=New;
        }
        else{
            node.prev.next=New;//改变被插入节点前一个节点的next节点
            New.prev=node.prev;//设置插入节点的prev节点
            New.next=node;
            node.prev=New;
        }
    }
    //adds key after node
    public void AddAfter(Node node, T key){
        Node New=new Node(key);
        New.next=node.next;
        New.prev=node;
        node.next=New;
        if(node==this.tail) this.tail=New;//如果插在尾节点后要变更尾节点
        else node.next.prev=New;//插入的不在尾节点后,就需要要改变插入位置后一个节点的prev节点
    }
    //print the value of the whole linked list
    public void Print(){
        Node node=this.head;
        while(node!=null) {
            System.out.print(node.value+" ");
            node=node.next;
        }
        System.out.println();
    }

    public Node getHead(){
        return head;
    }

    public Node getNode(){
        return head.next.next.next;
    }

    public static void main(String[] args){
        DoublyLinkedList<Integer> list= new DoublyLinkedList<>();
        for(int i=5;i>0;i--) list.PushFront(i);//test PushFront
        for(int i=6;i<11;i++) list.PushBack(i);//test PushBackT
        list.Print();
        System.out.println(list.TopFront()+" "+list.TopBack());//test TopFront and TopBack
        list.PopFront();
        list.PopBack();
        list.Print();
        list.AddBefore(list.getHead(),11);
        list.AddBefore(list.getNode(),12);
        list.Print();
        list.AddAfter(list.getHead(),13);
        list.AddAfter(list.getNode(),14);
        list.Print();
        System.out.println(list.Find(22)+" "+list.Find(9));
        list.Erase(11);
        list.Erase(9);
        list.Erase(13);
        list.Print();
    }
}

test

时间复杂度O(n)

Doubly-Linked List no tail with tail
PushFront(key) O(1)
TopFront() O(1)
PopFront() O(1)
PushBack(key) O(n) O(1)
TopBack() O(n) O(1)
PopBack() O(n) O(1)
Find(key) O(n)
Erase(key) O(n)
Empty() O(1)
AddBefore(Node,Key) O(n) O(1)
AddAfter(Node,Key) O(1)

很明显,有了prev节点后,我们的PopBack和AddBefore时间复杂度都降低了,因为省去了遍历的过程。当然 ,由于多了两个链节点的引用,链节点占用的空间也会变大。所以使用双向链表就是用空间换时间。

猜你喜欢

转载自www.cnblogs.com/dreamyt/p/linked-list.html
今日推荐