鏈表與LinkedList

真的膩害

鏈表

概念

說到鏈表,coder們都不會陌生,在日常開發(fā)中或多或少都會用到它。
它是鏈?zhǔn)酱鎯Φ木€性表,簡稱鏈表。鏈表由多個鏈表元素組成,這些元素稱為節(jié)點(diǎn)。結(jié)點(diǎn)之間通過邏輯連接,形成鏈?zhǔn)酱鎯Y(jié)構(gòu)。存儲結(jié)點(diǎn)的內(nèi)存單元,可以是連續(xù)的也可以是不連續(xù)的。邏輯連接與物理存儲次序沒有關(guān)系。
還是多說說概念上的東西,來說說鏈表的分類,從內(nèi)存角度出發(fā):鏈表可以分為靜態(tài)鏈表動態(tài)鏈表從鏈表存儲方式的角度出發(fā):鏈表可以分為單鏈表雙鏈表以及循環(huán)鏈表

  • 靜態(tài)鏈表:把線性表的元素放在數(shù)組中,不管物理上是不是連續(xù)存放的,它們之間的邏輯關(guān)系來連接,數(shù)組單位存放鏈表結(jié)點(diǎn),結(jié)點(diǎn)的鏈域指向下一個元素的位置,也就是下一個元素所在的數(shù)組單元的下標(biāo)。既然需要數(shù)組來實(shí)現(xiàn),那么數(shù)組的長度是不能預(yù)支的。
  • 動態(tài)鏈表:克服了靜態(tài)鏈表的缺點(diǎn)。它動態(tài)地為節(jié)點(diǎn)分配存儲單元。當(dāng)有節(jié)點(diǎn)插入時,系統(tǒng)動態(tài)地為結(jié)點(diǎn)分配空間。在結(jié)點(diǎn)刪除時,應(yīng)該及時釋放相應(yīng)存儲單元,以防止內(nèi)存泄露。

  • 單鏈表: 單鏈表是一種順序存儲的結(jié)構(gòu)。
    有一個頭結(jié)點(diǎn),沒有值域,只有連域,專門存放第一個結(jié)點(diǎn)的地址。
    有一個尾結(jié)點(diǎn),有值域,也有鏈域,鏈域始終為NULL.
    所以,在單鏈表中為找第i個結(jié)點(diǎn)或數(shù)據(jù)元素,必須先找到第i-1結(jié)點(diǎn)或數(shù)據(jù)元素,而且必須知道頭結(jié)點(diǎn),否則整個鏈表無法訪問。
單鏈表示意圖
  • 雙鏈表
    雙鏈表也是基于單鏈表的,單鏈表是單向的。而雙鏈表則在單鏈表的基礎(chǔ)上添加了一個鏈域。通過兩個鏈域,分別指向結(jié)點(diǎn)的前結(jié)點(diǎn)和后結(jié)點(diǎn)。這樣的話可以通過雙鏈表的任何結(jié)點(diǎn)訪問到它的前結(jié)點(diǎn)和后結(jié)點(diǎn)。但是雙鏈表還是不夠靈活,在實(shí)際編程中比較常用的是循環(huán)雙鏈表,但是循環(huán)雙鏈表比較麻煩。
雙向鏈表示意圖
  • 循環(huán)鏈接表
    循環(huán)鏈表由單鏈表演化而來。單鏈表的最后一個結(jié)點(diǎn)的鏈域指向NULL,而循環(huán)鏈表的建立,不要專門的頭結(jié)點(diǎn),讓最后一個結(jié)點(diǎn)的鏈域指向鏈表結(jié)點(diǎn)。它與單鏈表的區(qū)別:
    區(qū)別一:鏈表的建立。單鏈表需要創(chuàng)建一個頭結(jié)點(diǎn),專門存放第一個結(jié)點(diǎn)的地址。單鏈表的鏈域指向NULL。而循環(huán)鏈表的建立,不要專門的頭結(jié)點(diǎn),讓最后一個結(jié)點(diǎn)的鏈域指向鏈表的頭結(jié)點(diǎn)。
    區(qū)別二:鏈表表尾的判斷。單鏈表判斷結(jié)點(diǎn)是否為表尾結(jié)點(diǎn),只需判斷結(jié)點(diǎn)的鏈域值是否是NULL。如果是,則為尾結(jié)點(diǎn);否則不是。而循環(huán)鏈表盤判斷是否為尾結(jié)點(diǎn),則是判斷該節(jié)點(diǎn)的鏈域是不是指向鏈表的頭結(jié)點(diǎn)。

鏈表的實(shí)現(xiàn)

單鏈表(Single-Linked List)

public class SingleLinkedList<E> {
    transient int size = 0;
    private Node<E> first;

    private class Node<E> {
        E item;
        Node<E> next;
    }
}

插入節(jié)點(diǎn)

由于我們SIngleLinkedList類中維護(hù)了一個指向FirstNode的引用,所以在表頭插入節(jié)點(diǎn)是很容易的。

 public void insert(E item) {
        Node<E> oldFirst = first;
        first = new Node<>();
        first.item = item;
        first.next = oldFirst;
        size++;
    }

刪除節(jié)點(diǎn)

 /**
     * 從頭結(jié)點(diǎn)開始刪除
     * @return
     */
    public E delete() {
        if (first != null) {
            E item=first.item;
            //讓頭結(jié)點(diǎn)變成原來頭結(jié)點(diǎn)的下一個結(jié)點(diǎn)
            first=first.next;
            return item;
        } else {
            throw new NullPointerException("This SingleLinkedList is empty!");
        }
    }

雙向鏈表(DoubleLinkedList)

雙向鏈表相比與單鏈表的優(yōu)勢在于它同時支持高效的正向及反向遍歷,并且可以方便的在鏈表尾部刪除結(jié)點(diǎn)(單鏈表可以方便的在尾部插入結(jié)點(diǎn),但不支持高效的表尾刪除操作)。

public class DoubleLinkedList<E> {
    transient int size = 0;
    transient Node<E> first;
    transient Node<E> last;
    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;
            this.prev = prev;
        }
    }
}

添加節(jié)點(diǎn)

 public void addFirst(E e) {
        Node<E> f = first;
        Node<E> newNode = new Node<E>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
    }

    public void addLast(E e) {
        Node<E> l = last;
        Node<E> newNode = new Node<E>(l, e, null);
        last = newNode;
        if(l==null){
            first=newNode;
        }else{
            l.next=newNode;
        }
        size++;
    }

在指定位置之前添加節(jié)點(diǎn):

  public void add(int index, E element) {
        if (index < 0 || index > size) {
            throw new IndexOutOfBoundsException("index is illegal");
        }
        if (index == size) {
            addLast(element);
        }
        addBefore(element, node(index));
    }

    private void addBefore(E element, Node<E> node) {
        Node<E> prev = node.prev;
        Node<E> newNode = new Node<E>(prev, element, node);
        node.prev = newNode;
        if (prev == null)
            first = newNode;
        else
            prev.next = newNode;
        size++;
    }

    private Node<E> node(int index) {
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;

        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i++)
                x = x.prev;
            return x;
        }
    }

刪除操作

 private E deleteFirst(Node<E> f) {//刪除第一個
        E element = f.item;
        Node<E> next = f.next;
        f.item = null;
        f.next = null;
        first = next;
        if (next == null) {
            last = null;
        } else {
            next.prev = null;
        }
        size--;
        return element;
    }

    private E deleteLast(Node<E> node) {//刪除最后一個
        E element = node.item;
        Node<E> prev = node.prev;
        node.item = null;
        node.prev = null;
        if (prev == null)
            first = null;
        else
            prev.next = null;
        size--;
        return element;
    }

    E delete(Node<E> x) {//刪除指定的Node
        E element = x.item;
        Node<E> next = x.next;
        Node<E> prev = x.prev;
        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }
        if (next == null) {
            last = prev;
        } else {
            next.prev=prev;
            x.next=null;
        }
        x.item=null;
        size--;
        return element;
    }

它們使用的地方

 public boolean remove(Object o) {
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    delete(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    delete(x);
                    return true;
                }
            }
        }
        return false;
    }

直接看代碼就知道所謂鏈表到底是怎么一回事了。

鏈表的特性

1 優(yōu)點(diǎn)

鏈表的主要優(yōu)勢有兩點(diǎn):1,插入的時間復(fù)雜度為o(1);2,可以動態(tài)的改變大小。

2 缺點(diǎn)

由于其鏈?zhǔn)酱鎯Φ奶匦裕湵聿痪邆淞己玫目臻g局部性,也就是說,鏈表是一種緩存不友好的數(shù)據(jù)結(jié)構(gòu)。

LinkedList

java 中LinkedList中的源碼中添加刪除方法如我在上面寫的雙鏈表中的添加刪除方法基本一致。
LinkedList是基于雙向循環(huán)鏈表實(shí)現(xiàn)的(last.next=first,first.prev=last)。
LinkedList是非線程安全的,只在單線程下適合使用。
LinkedList實(shí)現(xiàn)了Serializable接口,因此它支持序列化,能夠通過序列化傳輸,實(shí)現(xiàn)了Cloneable接口,能被克隆。
除了可以當(dāng)作鏈表來操作外,它還可以當(dāng)作棧,隊(duì)列和雙端隊(duì)列來使用。

 public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

public boolean offer(E e) {
        return add(e);
    }

public void push(E e) {
        addFirst(e);
    }
public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }
 public E pop() {
        return removeFirst();
    }
....

從源碼可以看出LinkedList不僅有鏈表的特征方法還是棧和隊(duì)列的特性方法。
對于LinkedList還有幾點(diǎn)要說的。
* 在查找和刪除某元素時,源碼中都劃分為該元素為null和不為null兩種情況來處理,LinkedList中允許元素為null。
* LinkedList是基于鏈表實(shí)現(xiàn)的,因此不存在容量不足的問題,所以這里沒有擴(kuò)容的方法。
* 注意源碼中的Entry entry(int index)方法。該方法返回雙向鏈表中指定位置處的節(jié)點(diǎn),而鏈表中是沒有下標(biāo)索引的,要指定位置出的元素,就要遍歷該鏈表,從源碼的實(shí)現(xiàn)中,我們看到這里有一個加速動作。源碼中先將index與長度size的一半比較,如果indexsize/2,就只從位置size往前遍歷到位置index處。這樣可以減少一部分不必要的遍歷,從而提高一定的效率(實(shí)際上效率還是很低)。
* LinkedList是基于鏈表實(shí)現(xiàn)的,因此插入刪除效率高,查找效率低(雖然有一個加速動作)。
* 要注意源碼中還實(shí)現(xiàn)了棧和隊(duì)列的操作方法,因此也可以作為棧、隊(duì)列和雙端隊(duì)列來使用。
## 一道鏈表的測試題
這道題來自[劍指offer](https://www.nowcoder.com/ta/coding-interviews?page=)上的一道題:
在一個排序的鏈表中,存在重復(fù)的結(jié)點(diǎn),請刪除該鏈表中重復(fù)的結(jié)點(diǎn),重復(fù)的結(jié)點(diǎn)不保留,返回鏈表頭指針。 例如,鏈表1->2->3->3->4->4->5 處理后為 1->2->5

public class ListNode {
int val;
ListNode next = null;

ListNode(int val) {
    this.val = val;
}

}

public class Solution {

public ListNode deleteDuplication(ListNode pHead)
{
int maxNum = 0;
//int minNum = pHead.val;
//int length = 0;
for (ListNode x = pHead; x != null; x = x.next) {
// length++;
if (x.val > maxNum) {
maxNum = x.val;
}
}
int[] nums = new int[maxNum + 1];
for (ListNode x = pHead; x != null; x = x.next) {
nums[x.val]++;
}
ListNode first = null;
ListNode pFirst=null;
for (int index = 0; index <= maxNum; index++) {
if (nums[index] > 1) {
nums[index] = 0;
}
if (nums[index] > 0 && index > ( first==null?-1:first.val)) {
if (first != null) {
ListNode oldFirst = first;
first = new ListNode(index);
// first.val=index;
oldFirst.next = first;
} else {
first = new ListNode(index);
pFirst = first;
}

        }
    }
    return pFirst;
}

}

這是我寫出的一種答案,可能有待精簡和優(yōu)化,時間關(guān)系就在這里提供一種解法。等有時間加上注釋。![o(′^`)o](http://upload-images.jianshu.io/upload_images/3548660-40a109b5aaa98652.gif?imageMogr2/auto-orient/strip%7CimageView2/2/w/1440/q/50)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容