數(shù)據(jù)結(jié)構(gòu)(鏈表)的應(yīng)用——單向鏈表和雙向鏈表(LinkedList)

鏈表是線性表的其中之一既鞠,線性表又是我們要學(xué)的數(shù)據(jù)結(jié)構(gòu)的一部分,所以非常有學(xué)習(xí)價值,我們今天專門分析單鏈表和雙鏈表饭弓。

一双饥、單鏈表

  1. 存儲結(jié)構(gòu)
    上圖就是單鏈表的存儲結(jié)構(gòu)原理圖,由圖中我們可以看出每個節(jié)點都由兩部分構(gòu)成弟断,一個是data數(shù)據(jù)域咏花、另一個是指向下一個節(jié)點的next指針域,指針域不存放任何數(shù)據(jù)阀趴。然后一直通過這樣的方式一直指下去昏翰,最后就形成了一條類似鐵鏈的結(jié)構(gòu),所以稱為鏈表刘急。我們看到最后的next指針為null,說明到了最后一個節(jié)點棚菊,最后一個節(jié)點的指針域不指向任何節(jié)點,所以next=null排霉。

  2. 代碼實現(xiàn)

/**
 * Created by bryanrady on 2017/12/7.
 *  單鏈表的實現(xiàn)
 */

public class SinglyLinkedList<E>{

private Node<E> first;      //單鏈表的頭結(jié)點
private Node<E> last;       //單鏈表的最后一個節(jié)點  
private int size;

public SinglyLinkedList(){

}

public SinglyLinkedList(SinglyLinkedList c){
    this();
    addAll(c);
}

static class Node<E>{
    E data;     //數(shù)據(jù)域
    Node<E> next;   //指針域

    public Node(E data){
        this.data = data;
    }
}

/**
 * 返回單鏈表的長度
 * @return
 */
public int size(){
    return size;
}

/**
 * 在單鏈表尾部添加一個元素節(jié)點
 * @param element
 */
public void add(E element){
    Node<E> node = new Node<E>(element);
    Node<E> l = last;
    if(l == null){  //表明之前沒有節(jié)點
        first = node;
    }else{
        l.next = node;
    }
    last = node;
    size++;
}

/**
 * 刪除節(jié)點
 */
public E remove(){
    return removeFirst();
}

/**
 * 刪除第一個節(jié)點
 * @return
 */
private E removeFirst(){
    Node<E> f = first;
    if(f == null){
        throw new NoSuchElementException();
    }
    return unlinkFirst(f);
}

/**
 * 刪除第一個節(jié)點
 * @param node
 * @return
 */
private E unlinkFirst(Node<E> node) {
    E element = node.data;
    Node<E> next = node.next;
    node.data = null;
    node.next = null;
    first = next;
    if(next == null){
        last = null;
    }
    size--;
    return element;
}

/**
 * 在鏈表尾部添加一個集合
 * @param list
 * @return
 */
public boolean addAll(SinglyLinkedList<E> list){
    return  addAll(size,list);
}

/**
 * 返回單鏈表的Object[]數(shù)組
 * @return
 */
public Object[] toArray() {
    Object[] result = new Object[size];
    int i = 0;
    Node<E> e = first;
    while(e!=null){
        result[i++] = e.data;
        e = e.next;
    }
    return result;
}

/**
 * 在指定位置添加集合
 * @param index
 * @param list
 * @return
 */
private boolean addAll(int index,SinglyLinkedList<E> list){
    if(index<0 || index > size){
        throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+size);
    }
    Object[] a = list.toArray();
    int numNew = a.length;
    if(numNew == 0){
        return false;
    }
    for(Object o : a){
        E e = (E)o;
        Node<E> newNode = new Node<>(e);
        if(last==null){
            first = newNode;
        }else{
            last.next = newNode;
        }
        last = newNode;
    }
    size += numNew;
    return true;
}

/**
 * 根據(jù)傳入的Index查找節(jié)點
 * @param index
 * @return
 */
private Node<E> node(int index){
    Node<E> x = first;
    for(int i=0;i<index;i++){
       x = x.next;
    }
    return x;
}

public E get(int index){
    if(index<0||index>=size){
        throw new IndexOutOfBoundsException("Index:"+index);
    }
    return node(index).data;
}

}

我這里沒有像其他實現(xiàn)者一樣定義頭結(jié)點是空的數(shù)據(jù)域窍株,但是實現(xiàn)思路和方法基本都是一模一樣的。我是采用的LinkedList代碼的思想來進(jìn)行實現(xiàn)攻柠,后面有時間再添加其他有關(guān)于集合的方法球订。

二、雙向鏈表

雙向鏈表與單鏈表比較就是多了一個指向前驅(qū)節(jié)點的指針瑰钮,這樣來構(gòu)成有序性冒滩。我們常用的LinkedList就是在雙向鏈表的數(shù)據(jù)結(jié)構(gòu)上進(jìn)行實現(xiàn)的,現(xiàn)在我們直接來分析源代碼浪谴。

LinkedList簡介 LinkedList是基于雙向循環(huán)鏈表(從源碼中可以很容易看出)實現(xiàn)的开睡,除了可以當(dāng)作鏈表來操作外,它還可以當(dāng)作棧苟耻,隊列和雙端隊列來使用篇恒。LinkedList同樣是非線程安全的,只在單線程下適合使用凶杖。

  1. 繼承關(guān)系與實現(xiàn)的接口

     public class LinkedList<E>
             extends AbstractSequentialList<E>
             implements List<E>, Deque<E>, Cloneable, java.io.Serializable
    

LinkedList繼承了AbstractSequentialList胁艰,而AbstractSequentialList又是AbstractList<E>的一個子類,這就是集合框架架構(gòu)圖智蝠,這里不詳細(xì)介紹腾么,后面再總結(jié)。

LinkedList實現(xiàn)了Deque(雙端隊列)接口杈湾,即LinkedList可以作為一個雙端隊列來使用解虱。

LinkedList實現(xiàn)了Serializable接口,因此它支持序列化漆撞,能夠通過序列化傳輸殴泰,實現(xiàn)了Cloneable接口于宙,能被克隆。

  1. 數(shù)據(jù)結(jié)構(gòu)艰匙,就是一個雙向鏈表

    private static class Node<E> {
     E item;
     Node<E> next;
     Node<E> prev;
    
     Node(Node<E> prev, E element, Node<E> next) {
         this.item = element;
         this.next = next;    //指向下一個節(jié)點
         this.prev = prev;  //指向上一個節(jié)點
     }
    

    }

3.屬性字段

  transient int size = 0;    //鏈表中的節(jié)點個數(shù)

  transient Node<E> first;    //定義鏈表頭結(jié)點

  transient Node<E> last;    //定義最后一個節(jié)點

4.構(gòu)造函數(shù)

public LinkedList() {
}

public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}
  1. 增刪改查操作
    (1)添加操作
   /**
    * 添加元素
    * @param e
    * @return
    */
   public boolean add(E e){
       linkLast(e);
       return true;
   }

   /**
    * 在指定的位置往集合中添加元素e
    * @param index
    * @param e
    */
   public void add(int index,E e){
       checkPositionIndex(index);
       if(index == size){
           linkLast(e);
       }else{
           linkBefore(e,node(index));
       }
   }

   /**
    * 添加元素作為第一個節(jié)點
    * @param e
    */
   public void addFirst(E e){
       linkFirst(e);
   }

   /**
    * 添加元素作為最后一個節(jié)點
    * @param e
    */
   public void addLast(E e){
       linkLast(e);
   }

   /**
    * 在鏈表頭部添加元素e,并將元素e作為第一個元素
    * @param e
    */
   private void linkFirst(E e){
       Node<E> f = first;
       Node<E> newNode = new Node<>(null,e,f);
       first = newNode;
       if(f==null){
           last = newNode;
       }else{
           f.prev = newNode;
       }
       size++;
   }

   /**
    * 在鏈表尾部添加元素e,并將e作為最后一個元素
    * @param e
    */
   private void linkLast(E e){
       Node<E> l = last;
       Node newNode = new Node(l,e,null);
       if(l == null){
           first = newNode;
       }else{
           l.next = newNode;
           newNode.prev = l;
       }
       last = newNode;
       size++;
   }

   /**
    * 在非空節(jié)點succ之前插入元素e
    * @param e
    * @param succ
    */
   private void linkBefore(E e,Node<E> succ){
       Node<E> pred = succ.prev;
       Node<E> newNode = new Node<>(pred,e,succ);
       if(pred == null){
           first = newNode;
       }else{
           pred.next = newNode;
       }
       succ.prev = newNode;
       size++;
   }


/**
    * 將集合c追加到LinkedList中
    * @param c
    * @return
    */
   public boolean addAll(Collection<? extends E> c){
       return addAll(size,c);
   }

   /**
    * 將集合c添加到LinkedList的指定位置
    * @param index
    * @param c
    * @return
    */
   public boolean addAll(int index,Collection<? extends E> c){
       checkPositionIndex(index);
       Object[] a = c.toArray();
       int numNew = a.length;
       if(numNew==0){
           return false;
       }
       Node<E> pred,succ;  //設(shè)置當(dāng)前要插入節(jié)點的前后節(jié)點
       if(index==size){
           succ = null;
           pred = last;
       }else{
           succ = node(index);
           pred = succ.prev;
       }

       /****插入開始***/
       for(Object o : a){
           E e = (E)o;
           Node<E> newNode = new Node<>(pred,e,null);
           if(pred==null){
               first = newNode;
           }else{
               pred.next = newNode;
           }
           //此時把下一個要插入的節(jié)點的上一個節(jié)點變成剛剛插入的節(jié)點
           pred = newNode;
       }
       /****插入完成***/

       //設(shè)置前后節(jié)點的關(guān)系
       if(succ == null){
           last = pred;
       }else{
           pred.next = succ;
           succ.prev = pred;
       }
       size += numNew;
       return true;
   }
      

(2)刪除操作

/**
     * 刪除元素限煞,默認(rèn)刪除第一個,并返回
     * @return
     */
    public E remove(){
        return removeFirst();
    }

    /**
     * 刪除集合中指定位置的元素
     * @param index
     * @return
     */
    public E remove(int index){
        checkElementIndex(index);
        return unlink(node(index));
    }

    /**
     * 在集合中刪除指定的對象
     * @param o
     * @return
     */
    public boolean remove(Object o){
        if(o == null){
            for(Node<E> x=first;x!=null;x=x.next){
                if(x.element == null){
                    unlink(x);
                    return true;
                }
            }
        }else{
            for(Node<E> x=first;x!=null;x=x.next){
                if(x.element.equals(o)){
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 刪除鏈表的第一個元素并返回
     * @return
     */
    public E removeFirst(){
        Node<E> f = first;
        if(f == null){
            throw new NoSuchElementException();
        }
        return unlinkFirst(f);
    }

    /**
     * 刪除鏈表的最后一個元素抹恳,并返回
     * @return
     */
    public E removeLast(){
        Node<E> l = last;
        if(l == null){
            throw new NoSuchElementException();
        }
        return unlinkLast(l);
    }

    /**
     * 清空鏈表     循環(huán)遍歷全部置為null
     */
    public void clear(){
        for(Node<E> x=first;x!=null;x=x.next){
            x.element =null;
            x.prev = null;
            x.next = null;
            x = null;
        }
        first = last = null;
        size = 0;
    }

    /**
     * 刪除一個非空節(jié)點x员凝,并返回
     * @param x
     * @return
     */
    private E unlink(Node<E> x){
        E element = x.element;
        Node<E> pred = x.prev;
        Node<E> succ = x.next;
        if(pred == null){
            first = succ;
        }else{
            pred.next = succ;
            x.prev = null;
        }
        if(succ == null){
            last = pred;
        }else{
            succ.prev = pred;
            x.next = null;
        }
        x.element = null;
        size--;
        return element;
    }

    /**
     * 刪除不為空的第一個節(jié)點
     * @param f
     */
    private E unlinkFirst(Node<E> f){
        E element = f.element;
        Node<E> succ = f.next;
        f.element = null;     //help gc
        f.next = null;
        first = succ;
        if(succ == null){
            last = null;
        }else{
            succ.prev = null;
        }
        size--;
        return element;
    }

    /**
     * 刪除不為空的最后一個節(jié)點
     * @param l
     * @return
     */
    private E unlinkLast(Node<E> l){
        E element = l.element;
        Node<E> pred = l.prev;
        l.element = null;
        l.prev = null;
        last = pred;
        if(pred == null){
            first = null;
        }else{
            pred.next = null;
        }
        size--;
        return element;
    }

(3)更新操作

/**
     * 修改在鏈表中指定位置的元素
     * @param index
     * @param e
     * @return
     */
    public E set(int index,E e){
        checkElementIndex(index);
        Node<E> oldNode = node(index);
        E oldVal = oldNode.element;
        oldNode.element = e;
        return oldVal;
    }

(4)查詢操作

/**
     * 根據(jù)index獲取集合中的指定的元素
     * @param index
     * @return
     */
    public E get(int index){
        checkElementIndex(index);
        return node(index).element;
    }

    /**
     * 獲取第一個節(jié)點上的元素
     * @return
     */
    public E getFirst(){
        Node<E> f = first;
        if(f == null){
            throw new NoSuchElementException();
        }
        return f.element;
    }

    /**
     * 獲取最后一個節(jié)點的元素
     * @return
     */
    public E getLast(){
        Node<E> l = last;
        if(l == null){
            throw new NoSuchElementException();
        }
        return l.element;
    }

    /**
     * 根據(jù)index位置獲取節(jié)點
     *  利用雙向鏈表的特點提高查找效率
     * @param index
     * @return
     */
    private Node<E> node(int index){
        if(index < (size>>1)){  //說明要查找的節(jié)點在前半部分
            Node<E> x = first;
            for(int i=0;i<index;i++){
                x = x.next;
            }
            return x;
        }else{  //說明要查找的節(jié)點在前半部分
            Node<E> x = last;
            for(int i=size-1;i>index;i--){
                x = x.prev;
            }
            return x;
        }
    }

6.集合的一些常規(guī)方法

/**
     * 返回LinkedList的大小
     * @return
     */
    public int size(){
        return size;
    }

    /**
     * 判斷集合是否為空
     * @return
     */
    public boolean isEmpty(){
        return size == 0;
    }
/**
     * 判斷鏈表集合中是否包含此對象
     * @param o
     * @return
     */
    public boolean contains(Object o){
        return indexOf(o) >= 0;
    }

    /**
     * 獲取對象o在鏈表集合中的索引位置
     * @param o
     * @return
     */
    public int indexOf(Object o){
        int index = 0;
        if(o == null){
            for(Node<E> x=first;x!=null;x=x.next){
                if(x.element == null){
                    return index;
                }
                index++;
            }
        }else{
            for(Node<E> x=first;x!=null;x=x.next){
                if(x.element.equals(o)){
                   return index;
                }
                index++;
            }
        }
        return -1;
    }

    /**
     * 返回此對象在鏈表中最后出現(xiàn)的位置
     * @param o
     * @return
     */
    public int lastIndexOf(Object o){
        int index = size-1;
        if(o == null){
            for(Node<E> x=last;x!=null;x=x.prev){
                if(x.element == null){
                    return index;
                }
                index--;
            }
        }else{
            for(Node<E> x=last;x!=null;x=x.prev){
                if(x.element.equals(o)){
                    return index;
                }
                index--;
            }
        }
        return -1;
    }

    /**
     *  添加元素的時候檢查索引位置
     * @param index
     */
    private void checkPositionIndex(int index){
        if(index<0 || index>size) {
            throw new IndexOutOfBoundsException("Index:" + index + ",Size:" + size);
        }
    }

    /**
     * 在刪除和獲取元素時判斷檢查下標(biāo)位置
     * @param index
     */
    private void checkElementIndex(int index){
        if(index<0 || index>=size) {
            throw new IndexOutOfBoundsException("Index:" + index + ",Size:" + size);
        }
    }

    /**
     * 返回LinkedList的Object數(shù)組
     * @return
     */
    public Object[] toArray(){
        Object[] o = new Object[size];
        int i=0;
        for(Node<E> x=first;x!=null;x=x.next){
            o[i++] = x.element;
        }
        return o;
    }

7.LinkedList作為雙端隊列的方法

/***    LinkedList實現(xiàn)了Deque<E>接口,Deque<E>接口對Queue<E>接口進(jìn)行了擴(kuò)展</>
     * </>  LinkedList作為隊列使用
     *      一般用LinkedList作為隊列使用較多奋献,使用棧的時候最好還是使用Stack<E>這個類
     * </>*****/

    /**
     * 往隊列尾部添加元素
     * @param e
     * @return
     */
    public boolean offer(E e){
        return add(e);
    }

    /**
     * 從隊列頭部取元素,并刪除
     * @return
     */
    public E poll(){
        Node<E> f = first;
        if(f == null){
            return null;
        }
        return unlinkFirst(f);
    }

    /**
     * 從隊列頭部讀取元素健霹,但是不刪除
     * @return
     */
    public E peek(){
        Node<E> f = first;
        if(f == null){
            return null;
        }
        return f.element;
    }

    public boolean offerFrist(E e){
        addFirst(e);
        return true;
    }

    public boolean offerLast(E e){
        addLast(e);
        return true;
    }

    public E pollFirst(){
        Node<E> f = first;
        if(f == null){
            return null;
        }
        return unlinkFirst(f);
    }

    public E pollLast(){
        Node<E> f = first;
        if(f == null){
            return null;
        }
        return unlinkLast(f);
    }

    public E peekFirst(){
        Node<E> f = first;
        if(f == null){
            return null;
        }
        return f.element;
    }

    public E peekLast(){
        Node<E> l = last;
        if(l == null){
            return null;
        }
        return l.element;
    }
  1. LinkedList作為棧的使用方法
/***    LinkedList還可以當(dāng)成是棧來使用  *****/

    /**
     * 往棧頂添加元素,就是在鏈表前面添加元素作為第一個節(jié)點
     * @param e
     */
    public void push(E e){
        addFirst(e);
    }

    /**
     * 往棧頂取元素并刪除,即刪除鏈表前面刪除元素瓶蚂,這樣在就滿足了棧的先進(jìn)后出的原則
     *  還有個不刪除的就是和peek()方法一樣
     * @return
     */
    public E pop(){
        return removeFirst();
    }

看了這么多LinkedList的方法糖埋,現(xiàn)在我們來總結(jié)一下ArrayList和LinkedList的區(qū)別(也是順序表和鏈表的區(qū)別):

(1)ArrayList是基于動態(tài)數(shù)組實現(xiàn)的,LinkedList是基于雙向鏈表實現(xiàn)的窃这。

(2)ArrayList支持隨機(jī)訪問(通過下標(biāo))瞳别,LinkedList不支持。

(3)ArrayList的查詢和尾部插入元素效率較高杭攻,中間插入和刪除元素效率較低祟敛,因為有大量的數(shù)組復(fù)制操作。
LinkedList的插入和刪除效率較高兆解,只需要把節(jié)點指針改變一下就行馆铁,但是查詢效率較低,即使有雙向鏈表的特性可以從兩個方向查找锅睛,但還是需要使用蠻力法的方式進(jìn)行遍歷埠巨,所以效率較低。

(4)ArrayList會造成一定的空間浪費现拒,因為隨時需要探測數(shù)組容量然后擴(kuò)容辣垒;LinkedList通過節(jié)點方式進(jìn)行存放數(shù)據(jù),不存在空間浪費印蔬。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末勋桶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子扛点,更是在濱河造成了極大的恐慌哥遮,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件陵究,死亡現(xiàn)場離奇詭異眠饮,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)铜邮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門仪召,熙熙樓的掌柜王于貴愁眉苦臉地迎上來寨蹋,“玉大人,你說我怎么就攤上這事扔茅∫丫桑” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵召娜,是天一觀的道長运褪。 經(jīng)常有香客問我,道長玖瘸,這世上最難降的妖魔是什么秸讹? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮雅倒,結(jié)果婚禮上璃诀,老公的妹妹穿的比我還像新娘。我一直安慰自己蔑匣,他們只是感情好劣欢,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著裁良,像睡著了一般凿将。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上趴久,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天嘱丢,我揣著相機(jī)與錄音表蝙,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛惠桃,可吹牛的內(nèi)容都是我干的续誉。 我是一名探鬼主播烤芦,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼愕提,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了华匾?” 一聲冷哼從身側(cè)響起映琳,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蜘拉,沒想到半個月后萨西,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡旭旭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年谎脯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片持寄。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡源梭,死狀恐怖娱俺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情废麻,我是刑警寧澤荠卷,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站烛愧,受9級特大地震影響油宜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜屑彻,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一验庙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧社牲,春花似錦、人聲如沸悴了。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽湃交。三九已至熟空,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間搞莺,已是汗流浹背息罗。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留才沧,地道東北人迈喉。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像温圆,于是被迫代替她去往敵國和親挨摸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354