Java數據結構與算法——鏈表

聲明:碼字不易,轉載請註明出處,歡迎文章下方討論交流。

前言:Java數據結構與算法專題會不定時更新,歡迎各位讀者監督。本文介紹另外一種數據結構——鏈表,包括鏈表的特色特色、鏈表的建立、刪除、插入和輸出,文末給出java代碼和一道常見的關於鏈表的面試題。java

一、鏈表的概念和特色

鏈表是由若干結點組成,每一個結點至少包括兩部分信息:一個是元素數據,一個是指向下一個(上一個)元素地址的指針。鏈表的存儲在物理上是非連續、非順序的存儲結構,數據元素之間是經過每一個元素的指針來關聯的。node

與數組相比,鏈表獨特的存儲結構克服了數組提早須要設置長度的缺點,在運行時能夠動態的快速的添加和刪除元素;計算機的存儲空間並不是連續的,而鏈表則能夠靈活的使用存儲空間,能更好的對計算機內存進行動態管理。c++

鏈表分爲單鏈表、雙鏈表、和循環鏈表,雙鏈表的每一個結點有兩個指針,分別指向前一個元素和後一個元素,循環鏈表的尾結點指針不是null,而是指向頭結點元素的地址。面試

二、鏈表的操做

鏈表的操做包括了建立、刪除、插入、輸出。
建立就是空間的分配,將頭、尾指針及鏈表結點個數等初始化。
刪除和插入根據被操做元素的位置能夠細分爲頭刪除(插入),尾刪除(插入),中間刪除(插入),如下詳細介紹。算法

建立和輸出比較簡單,不作具體分析,後面直接給出代碼segmentfault

2.1 插入操做

插入分爲頭插入,尾插入,中間插入數組

頭插入
頭插入其實是增長一個新節點,而後把新增長的結點指針指向原來頭指針指向的元素,再把頭指針指向新增的節點。圖片描述數據結構

尾插入
尾插入也是增長一個新節點,該節點指針置爲null,而後把原尾結點指針指向新增長的節點,最後把尾指針指向新增長的節點便可。圖片描述性能

中間插入
中間插入稍複雜,首先增長一個節點,而後新增節點的指針指向插入位置的後一個節點,把插入位置的前一個節點指針指向新插入節點便可。圖片描述測試

2.2 刪除操做

刪除與插入相似,根據被操做元素的位置分爲頭刪除,尾刪除,中間刪除

頭刪除
刪除頭元素時,先將頭指針指向下一個節點,而後把原頭結點的指針置空便可圖片描述

尾刪除
刪除尾元素時,首先找到鏈表倒數第2個元素,而後把尾指針指向這個元素,接着把原倒數第2個元素的指針置空。圖片描述

中間刪除
刪除中間元素相對複雜一些,首先將要刪除的節點的前一個節點指針指向要刪除的節點的下一個節點,而後把要刪除節點的指針置空。圖片描述

三、java代碼實現

鏈表是由一系列節點組成,首先是節點部分

public class Node {
    private int data;  //數據
    private Node next;  //指針
    
    public int getData() {
        return data;
    }
    public void setData(int data) {
        this.data = data;
    }
    public Node getNext() {
        return next;
    }
    public void setNext(Node next) {
        this.next = next;
    }    
}

鏈表節點部分的代碼實現了兩個部分,一個是數據,一個是指向下一個節點的指針(因爲java中摒棄了c++中的指針概念,準確的說應該是引用)
如下是鏈表的代碼實現:

public class Link {
    private int size = 0;
    private Node first;
    private Node last;
    
    /*鏈表初始化 */
    public Link(){}
    
    /**
     * 返回鏈表長度
     * @return 返回鏈表長度
     */
    public int getLength(){
        return size;
    }
    
    /**
     * 獲取指定位置的節點
     * @param index 位置[0~size]
     * @return 
     */
    public Node get(int index){
        Node temp = first;
        for(int i=0;i<index;i++){
            temp = temp.getNext();
        }
        return temp;
    }
    
    /**
     * 鏈表中插入第一個元素時,頭和尾時同一個元素
     * @param element
     */
    private void onetNode(int element){
        first = new Node();
        first.setData(element);
        last = first;
    }
    
    /**
     * 鏈表只剩一個節點時,清除first和last
     */
    private void clear(){
        first = null;
        last = null;
        size = 0;
    }
    
    /**
     * 插入尾節點
     * @param element
     */
    public void addTail(int element){
        if(size==0){   //鏈表爲空時,插入尾節點即第一個節點
            onetNode(element);
        }else{
            Node node = new Node();
            node.setData(element);
            last.setNext(node);
            last = node;   //尾節點設置爲插入的節點
        }
        size++;
    }
    
    /**
     * 鏈表頭插入
     * @param element
     */
    public void addHead(int element){
        if(size==0){
            onetNode(element);
        }else{
            Node node = new Node();
            node.setData(element);
            node.setNext(first);  //新插入元素的指針指向原頭元素
            first = node;  //新插入的元素設爲頭節點
        }
        size++;
    }
    
    /**
     * 插入中間元素,考慮頭尾兩種特殊狀況
     * @param index 位置
     * @param element  值
     */
    public void add(int index,int element){
        if(index < size){  
            if(size==0){  //空鏈表
                onetNode(element);
                size++;
            }else if(index==0){ //插入的位置是頭
                addHead(element);
                size++;
            }else if(size==index+1){  //插入的位置是尾
                addTail(element);
                size++;
            }else{  
                Node temp = get(index);   //獲取插入位置的節點
                Node node = new Node();   //插入新節點
                node.setData(element);
                node.setNext(temp.getNext());  
                temp.setNext(node);
                size++;
            }
        }else{
                throw new IndexOutOfBoundsException("插入位置無效或超出鏈表長度");
        }
    }
    
    /**
     * 刪除頭節點
     */
    public void deleFirst(){
        if(size==0){
            throw new IndexOutOfBoundsException("空鏈表,無元素可刪除");
        }else if(size==1){ //只剩一個節點時,清除first和last
            clear();
        }else{
            Node temp = first;  //爲了將刪除的頭結點置空
            first = first.getNext();
            temp = null;
            size--;
        }
    }
    
    /**
     * 刪除尾節點
     */
    public void deleLast(){
        if(size==0){
            throw new IndexOutOfBoundsException("空鏈表,無元素可刪除");
        }else if(size==1){ //只剩一個節點時,清除first和last
            clear();
        }else{
            Node temp = get(size-1);  //獲取最後元素的前一個節點(前驅)
            temp.setNext(null);
            size--;
        }
    }
    
    /**
     * 刪除中間元素,考慮了頭尾和超界
     * @param index 位置
     */
    public void deleMid(int index){
        if(size==0){
            throw new IndexOutOfBoundsException("空鏈表,無結點可刪");
        }else if(size==1){
            clear();
        }else{
            if(index==0){
                deleFirst();
            }else if(index==size-1){
                deleLast();
            }else if(index>size){
                throw new IndexOutOfBoundsException("刪除位置超界");
            }else{
                Node temp = get(index-1);
                temp.setNext(get(index));
                temp.setNext(null);
                size--;
            }
        }
    }
    
    /**
     * 獲取鏈表
     */
    public void getAll(){
        Node temp = first;
        System.out.println(temp.getData());
        while(temp.getNext()!=null){
            System.out.print(temp.getData()+"-->");
            temp = temp.getNext();
            size--;
        }
    }    
}

四、測試代碼

public class LinkTest {
    public static void main(String[] args) {
        Link link = new Link();
        link.addHead(1); //1
        link.printLink();
        
        link.addHead(5); //5->1
        link.printLink();
        
        link.addTail(9); //5->1->9
        link.printLink();
        
        link.addTail(7); //5->1->9->7
        link.printLink();
        
        link.add(3,8);  //5->1->9->8->7
        link.printLink();        
        System.out.println("鏈表長度:"+link.getLength()); //5
        
        link.deleFirst();  //1->9->8->7
        link.printLink();
        
        link.deleLast();  //1->9->8
        link.printLink();
        
        link.deleMid(1);  //1->8
        link.printLink();
        
        System.out.println("鏈表長度:"+link.getLength()); //2        
    }
}

5.小結

鏈表的操做稍稍複雜,插入和刪除要考慮不少因素(邊界條件、異常),寫完代碼要逐個測試,出現不一致的狀況逐步調試、跟蹤變量。記住插入和刪除的步驟(六張圖),編碼時仔細一點通常不會出現問題。

以上代碼通過測試無誤,歡迎收藏轉發,歡迎下方討論交流^_^
碼字——>畫圖——>編碼——>調試 一趟下來不容易,若是對您有幫助請收藏點贊

更新:個人另外一篇的文章Java數據結構與算法——鏈表面試介紹了鏈表的特色,使用場景、鏈表的性能分析以及一道經典的鏈表面試題——鏈的反轉問題,請移步閱讀參考。

相關文章
相關標籤/搜索