數據結構(一)之線性表

基礎概念

數據結構是相互之間存在一種或多種關係的數據元素的集合。數組

邏輯結構和物理結構 數據結構

關於數據結構,咱們能夠從邏輯結構和物理結構這兩個維度去描述學習

邏輯結構是數據對象中數據元素之間的關係,是從邏輯意義上去描述的數據之間的組織形式。this

邏輯結構有4種:spa

  • 集合結構(數據元素之間僅以集合的方式體現,元素之間沒有別的關係)
  • 線性結構(數據元素之間存在一對一的關係)
  • (數據元素之間爲一對多或多對一的關係)
  • (數據元素之間爲多對多的關係)

物理結構則是邏輯結構在計算機中內存中的存儲形式,分爲兩種:設計

  • 順序存儲結構
  • 鏈式存儲結構

線性表(list)

性表是零個或多個數據元素的的有限序列指針

線性表是線性結構,元素之間存在一對一的關係,線性表可經過順序和鏈式兩種方式來實現。code

順序存儲結構,用一段地址連續的存儲單元依次存儲線性表的數據元素對象

  

鏈式存儲結構,用一組任意的存儲單元來存儲數據元素不要求物理存儲單元的連續性,由一系列結點組成,每一個結點除了要存儲數據外,還需存儲指向後繼結點或前驅結點的存儲地址。blog

順序存儲和鏈式存儲對比

  • 順序存儲結構
    • 優勢
      • 實現比較簡單
      • 查找指定位置的元素效率很快,時間複雜度爲常數階O(1) 
      • 無需額外存儲元素之間的邏輯關係(鏈式存儲因爲存儲空間隨機分配,須要存儲元素之間的邏輯關係)
    • 缺點
      • 須要預先分配存儲空間,若是數據元素數量變化較大,很難肯定存儲容量,並致使空間浪費
      • 若頻繁進行插入刪除操做,則可能須要頻繁移動大量數據元素
  • 鏈式存儲結構
    • 優勢
      • 不須要提早分配存儲空間,元素個數不受限制
      • 對於插入刪除操做,在已找到目標位置前提下,效率很高,僅需處理元素之間的引用關係,時間複雜度爲O(1)
    • 缺點
      • 實現相對複雜
      • 查找效率較低,最壞狀況下須要遍歷整張表
      • 因爲物理存儲位置不固定,須要額外存儲數據元素之間的邏輯關係

鏈式存儲代碼實現

單鏈表

package listdemo;
/**
 * Created by chengxiao on 2016/10/18.
 */
public class MyLinkedList {
    /**
     * 指向頭結點的引用
     */
    private Node first ;
    /**
     * 線性表大小
     */
    private int size;
    /**
     * 結點類
     */
    private static class Node{
        //數據域
        private int data;
        //指向後繼結點的引用
        private Node next;
        Node(int data){
            this.data = data;
        }
    }
    /**
     * 從頭部進行插入
     * 步驟:1.新結點的next鏈指向當前頭結點;2.將first指向新節點
     * 時間複雜度:O(1)
     * @param data
     */
    public void insertFirst(int data){
        Node newNode = new Node(data);
        newNode.next = first;
        first = newNode;
        size++;
    }
    /**
     * 從頭部進行刪除操做
     * 步驟:1.將頭結點的next鏈置空 2.將first引用指向第二個結點
     * 時間複雜度爲:O(1)
     * @return
     */
    public boolean deleteFirst(){
        if(isEmpty()){
            return false;
        }
        Node secondNode = first.next;
        first.next = null;
        first = secondNode;
        size--;
        return true;
    }
    /**
     * 取出第i個結點
     * 步驟:從頭結點進行遍歷,取第i個結點
     * 時間複雜度:O(n),此操做對於利用數組實現的順序存儲結構,僅需常數階O(1)便可完成。
     * @param index
     * @return
     */
    public int get(int index) throws Exception {
        if(!checkIndex(index)){
            throw new Exception("index不合法!");
        }
        Node curr = first;
        for(int i=0;i<index;i++){
            curr = curr.next;
        }
        return curr.data;
    }
    /**
     * 遍歷線性表
     * 時間複雜度:O(n)
     */
    public void displayList(){
        Node currNode = first;
        while (currNode!=null){
            System.out.print(currNode.data+" ");
            currNode = currNode.next;
        }
        System.out.println();
    }

    /**
     * 鏈表是否爲空
     * @return
     */
    public boolean isEmpty(){
        return first == null;
    }

    /**
     * index是否合法
     * @param index
     * @return
     */
    private boolean checkIndex(int index){
        return index >= 0 && index < size;
    }
    /**
     * 鏈表大小
     * @return
     */
    public int size() {
        return size;
    }
    public static  void main(String []args) throws Exception {

        MyLinkedList myLinkedList = new MyLinkedList();
        //從頭部插入
        myLinkedList.insertFirst(1);
        myLinkedList.insertFirst(2);
        myLinkedList.insertFirst(3);
        myLinkedList.insertFirst(4);
        //遍歷線性表中元素
        myLinkedList.displayList();
        //獲取第二個元素
        System.out.println(myLinkedList.get(2));
        //刪除結點
        myLinkedList.deleteFirst();
        myLinkedList.displayList();
    }
}

輸出結果

 4 3 2 1 2 3 2 1 

雙端鏈表 

  上面羅列了線性表中的幾種基本操做,考慮下,若是要提供一個在鏈表尾部進行插入的操做insertLast,那麼因爲單鏈表只保留了指向頭結點的應用first,須要從頭結點不斷經過其next鏈找後繼結點來遍歷,時間複雜度爲O(n)。其實,咱們能夠在保留頭結點引用的時候,也保留一個尾結點的引用。這樣,在從尾部進行插入時就方便多了

  雙端鏈表同時保存對頭結點和對尾結點的引用

    /**
     * 指向頭結點的引用
     */
    private Node first ;
    /**
     * 指向尾結點的引用
     */
    private Node rear;

從尾部進行插入

  /**
     * 雙端鏈表,從尾部進行插入
     * 步驟:將當前尾結點的next鏈指向新節點便可
     * 時間複雜度:O(1)
     * @param data
     */
    public void insertLast(int data){
        Node newNode = new Node(data);
        if(isEmpty()){
            first = newNode;
            rear = newNode;
            size++;
            return;
        }
        rear.next = newNode;
        rear = newNode;
        size++;
    }

作其餘操做的時候也需注意保持對尾結點的引用,此處再也不贅述。

雙向鏈表

 再考慮下,若是咱們要提供一個刪除尾結點的操做,步驟很簡單:在刪除尾結點的過程當中須要將其前驅結點(即倒數第二個結點)的next鏈引用置爲空,但因爲咱們的鏈表是單鏈表,一條道走到黑,要找倒數第二個結點得從頭開始遍歷,這種狀況下,咱們就能夠考慮使用雙向鏈表。

  雙向鏈表的的每個結點,包含兩個指針域,一個指向它的前驅結點,一個指向它的後繼結點。

          

  /**
     * 刪除尾結點
     * 主要步驟:1.將rear指向倒數第二個結點 2.處理相關結點的引用鏈
     * 時間複雜度:O(1)
     * @return
     */
    public void deleteLast() throws Exception {
        if(isEmpty()){
            throw new Exception("鏈表爲空");
        }
        Node secondLast = rear.prev;
        rear.prev = null;
        rear = secondLast;
        if(rear == null){
            first = null;
        }else{
            rear.next = null;
        }
        size--;
    }

其餘操做同理,在過程當中須要同時保持對結點的前驅結點和後繼結點的引用,刪除操做時,須要注意解除廢棄結點的各類引用,便於GC。

總結

  本文對數據結構的一些基本概念,邏輯結構和物理結構,線性表等概念進行了基本的闡述。同時,介紹了線性表的順序存儲結構和鏈式存儲結構,對線性表的鏈式存儲結構(單鏈表,雙端鏈表,雙向鏈表),使用Java語言作了基本實現。數據結構的重要性毋庸置疑,它是軟件設計的基石,因爲本身非科班出身,雖曾自學過一段時間,也不夠系統,最近但願能從新系統地梳理下,本篇就當本身數據結構再學習的開篇吧,共勉。

相關文章
相關標籤/搜索