數據結構之單鏈表

鏈表是一種數據結構,和數組同級。鏈表中的數據是以結點來表示的,每個結點構成:元素+指針 。元素就是存儲數據單元,指針就是連接每個結點的地址數據。比如,java中我們經常使用的ArrayList,其實現原理是數組,而LinkedList的實現原理就是鏈表。鏈表在進行循環遍歷時效率不高,但是在插入和刪除是優勢明顯,下面對單鏈表做介紹

一、簡介

以”結點的序列”表示線性表稱爲”線性鏈表(單鏈表)”。也就是說它實際上是由結點(Node)組成的,一個鏈表擁有不定數量的結點其數據在內存中存儲是不連續的,它存儲的數據分散在內存中,每個結點只能也只有它能知道下一個結點的存儲位置。由N各節點(Node)組成單向鏈表,每一個Node記錄本Node的數據及下一個Node的存儲位置。向外暴露的只有一個頭節點(Head),我們對鏈表的所有操作,都是直接或者間接地通過其頭節點來進行的。 如圖所示:

這裏寫圖片描述

上圖中最左側的結點就是頭結點,但是新添加的結點的順序是從左到右的,添加的新結點會被作爲新節點。最先添加的結點對下一個結點的引用可以爲空。

二、鏈表的結點變量

上圖中藍色的部分就是結點的結構。
對象(data):存放結點值的數據域。
引用(next):存放結點的直接後繼的地址(位置)的指針域(鏈域)。

三、鏈表的具體存儲方式

1)用一組任意的存儲單元來存放線性表的結點(這組存儲單元既可以是連續的,也可以是不連續的),如下圖所示。
2)鏈表中的結點邏輯次序和物理次序不一定相同。爲了正確表示結點見的邏輯關係,在存儲每個結點值的同事,還必須存儲指示其後繼結點的地址或位置信息(成爲指針或鏈)。

下圖簡單的描述了單向鏈表存儲情況。存儲是分散的,每一個結點只要記錄下一個結點就把所有數據串了起來,形成了一個單向的鏈表。
這裏寫圖片描述

四、單鏈表的建立


單鏈表的建立有頭插法、尾插法兩種方法:

1、頭插法:在頭結點(頭結點的數據域可以存放數據標題、表長等信息,也可以不存儲任何信息,其指針域存儲第一個結點的首地址)之後插入數據,其特點就是:讀入的數據順序與線性表的邏輯正好相反,如圖所示。
這裏寫圖片描述

注:  頭插法建單鏈表是將鏈表右端看成固定的,鏈表不斷向左延伸而得到的。頭插法最先得到的是尾結點(鏈表的最右邊)

從上圖可以看出頭插法就是從一個空表開始,重複讀入數據,形成新結點,將讀入數據存放到新結點的數據域中,然後將新結點插入到當前鏈表的表頭結點之後

算法實現如下:
Step1.定義一個鏈表的結點

package test;

/**
 * Created by DELL on 2017/11/24.
 * 鏈表結點的實現
 */
public class Node<T> {

    /**
     * 下一個結點
     */
    Node<T> next;
    /**
     * 結點存儲的數據
     */
    T val;

    public Node(T val, Node<T> next) {
        this.next = next;
        this.val = val;
    }
}

Step2.定義一個單鏈表頭插法的操作類

package test;

/**
 * Created by DELL on 2017/11/24.
 * 單鏈表的頭插法操作類
 */
public class LinearList<T> {
    Node<T> head ; //鏈表的頭結點
    Node<T> tail ; //鏈表的尾結點
    static int length;//定義鏈表長度

    public LinearList() {
        //空鏈表
        this.head = null;
        this.tail = null;
    }
    //頭插法 鏈表 指針指向this.head
    void add(T val){
        this.head = new Node<T>(val,this.head);
        if(this.tail == null){
            //此時將尾結點固定,不斷改變的是頭結點
            this.tail = this.head;
        }
        //鏈表長度加1
        length ++ ;
    }

    public static void main(String[] args){
        LinearList<Integer> list = new LinearList<Integer>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        Node<Integer> head = list.head;
        while(head!=null){
            System.out.println(head.val);
            head = head.next;
        }
        System.out.println("鏈表的長度爲:"+length);
    }

    /**
     * 讀入的數據順序與線性表的邏輯正好相反
     * 運行結果
     * 5
     * 4
     * 3
     * 2
     * 1
     * 鏈表的長度爲:5
     * 
     */

}

2.尾插法:尾插法就是將鏈表的左端固定,鏈表不斷向右延伸也就是說將每次插入的新結點放在鏈表的尾部,如圖所示:

這裏寫圖片描述

注:尾插法建立鏈表時,頭指針固定不動, 故必須設立一個搜索指針,向鏈表右邊延伸,則整個算法應設立三個鏈表指針,即頭指針、搜索指針、申請單元指針。尾插法最先得到的是頭結點

從上圖可以看出,尾插法就是從一個空表開始,重複讀入數據,生成新結點,將讀入數據存放在新結點的數據域中,然後將新結點插入到當前鏈表的表尾上,直到讀入結束標誌爲止。

算法實現
Step1.定義結點類

package test;

/**
 * Created by DELL on 2017/11/24.
 * 鏈表結點的實現
 */
public class Node<T> {

    /**
     * 下一個結點
     */
    Node<T> next;
    /**
     * 結點存儲的數據
     */
    T val;

    public Node(T val, Node<T> next) {
        this.next = next;
        this.val = val;
    }
}

Step2.定義一個單鏈表的尾插法的操作類

package test;

/**
 * Created by DELL on 2017/11/25.
 */
public class TailLinerList<T> {
    Node<T> head ; //鏈表的頭結點
    Node<T> tail; //鏈表的尾結點
    static int length;
    public TailLinerList() {
        //空鏈表
        this.tail = null;
        this.head = null;
    }
    public void tialInsert(T val){
        //判斷是否有尾結點,如果沒有尾結點則說明此時是一個空的鏈表
        if(this.tail == null){
            //定義一個結點
            this.tail = new Node<T>(val,null);
            //由於是插入第一個元素此時頭結點和尾結點是同一個
            //此時將頭結點固定,不斷該改變的是尾結點
            this.head = this.tail;
        }else{
            //從插入第二個元素開始進入這部分
            //此時尾結點的next應該指向新結點
            this.tail.next = new Node<T>(val,null);
            //把鏈表的尾結點變成新插入進來的結點
            this.tail = this.tail.next;
        }
        //鏈表長度加1
        length++;
    }
    public static void  main(String[] args){
        TailLinerList<Integer> tailLinerList = new TailLinerList<Integer>();
        tailLinerList.tialInsert(6);
        tailLinerList.tialInsert(7);
        tailLinerList.tialInsert(8);
        tailLinerList.tialInsert(9);
        tailLinerList.tialInsert(10);
        Node<Integer> headList = tailLinerList.head;
        while(headList !=null ){
            System.out.println(headList.val);
            headList = headList.next;
        }
        System.out.println("鏈表的長度爲:"+length);
    }
    /**
     * 讀入的數據順序與線性表的邏輯相同
     * 運行結果
     * 6
     * 7
     * 8
     * 9
     * 10
     * 鏈表的長度爲:5
     */

}

綜上所述:因爲單鏈表的特殊結構,即只能從頭向尾遍歷,所以向頭插時所用的語句會比向尾插少幾個,向尾插時還要多一個指針指向尾結點,而用頭插法時就不用,但用頭插法時最先輸入的數據會排在鏈表的最後,輸出時即變成了輸入時的逆序輸出,看起來不如用尾插法那樣和輸入的順序一樣的形勢更舒服些 。

五、鏈表中動態存儲分配常用的標準函數
1)malloc(size)
在內存動態存儲區申請一個長度爲size字節的連續空間,但是需設立一申請單元指針,但malloc()函數得到的指針並不是指向結構體(是由一系列具有相同類型或不同類型的數據構成的數據集合)的指針,需使用強制類型轉換,將其轉換成結構體型指針。

2)calloc(n,size)
在內存的動態存儲區申請n個長度爲size字節的連續空間,函數返回值爲分配空間的首地址。若此函數未被成功執行,函數返回值爲0。

3)free(p)
釋放由指針p所指向的存儲單元,而存儲單元的大小是最近一次調用malloc()或calloc()函數時所申請的存儲空間。

發佈了46 篇原創文章 · 獲贊 49 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章