小碼哥《戀上數據結構與算法》筆記(二):鏈表

個人Github地址java

小碼哥《戀上數據結構與算法》筆記node

極客時間《iOS開發高手課》筆記git

iOS大廠面試高頻算法題總結github

iOS面試資料彙總面試

參考:小碼哥數據結構與算法(二): 鏈表算法

數據結構和算法動態可視化數組

1、鏈表(LinkedList)

  • 鏈表是一種鏈式存儲的線性表, 全部元素的內存地址不必定是連續的。

2、鏈表(LinkedList)接口設計

  • 建立LinkedList類,用來管理鏈表數據,其中的size屬性記錄存儲數據的數量,first屬性引用鏈表的第0個元素。
  • 建立私有類Node,其中的element屬性用於存儲元素,next屬性用於指向鏈表中的下一個節點。

public class LinkedList<E> {
    private int size;
    private Node<E> first;
    
    // 元素的數量
    int size(); 
    // 是否爲空
    boolean isEmpty();
    // 是否包含某個元素
    boolean contains(E element); 
    // 添加元素到最後面
    void add(E element); 
    // 返回index位置對應的元素
    E get(int index); 
    // 設置index位置的元素
    E set(int index, E element); 
    // 往index位置添加元素
    void add(int index, E element); 
    // 刪除index位置對應的元素 
    E remove(int index); 
    // 查看元素的位置
    int indexOf(E element); 
    // 清除全部元素
    void clear();
    
    // 私有類, 鏈表中的節點
    private class Node<E> {
        E element;
        Node<E> next;
        // 構造方法
        public Node(E element, Node<E> next) {
            this.element = element;
            this.next = next;
        }
    }
}
複製代碼

3、鏈表的實現

一、構造方法

  • 鏈表的建立與動態數組不一樣,動態數組在構造時須要傳入一個空間屬性,來決定這個數組的容量。但鏈表元素是在添加時才建立的,內存地址不必定是連續的。因此鏈表不須要在單獨設計構造方法,使用默認構造方法便可。

二、添加元素

  • 添加數據時,須要建立一個節點存儲數據,並將該節點拼接到最後節點的後面,而後size加1
  • 須要區分當前鏈表沒有數據,新節點拼接到first當前鏈表有數據,新節點拼接到最後的節點
public void add(E element) {
    // 當first等於null時, 說明此事沒有節點, 因此first引用新節點
    if (first == null) {
    	first = new Node<E>(element, null);
    }
    // 當fitst不等於null時, 說明鏈表中有節點, 此時獲取最後一個節點, 並將該節點的next指向新節點
    else {
        Node<E> node = node(size - 1);
        node.next = new Node<E>(element, null);
    }
    size++;
}
複製代碼

三、插入元素

  • 插入鏈表,首先須要建立新節點,而後經過變動插入位置前一個元素next指針指向,插入指定位置便可。
  • 須要區分插入到0的位置,使用first指向新節點插入到非0位置,找到前一個節點進行處理兩種狀況。
3.一、數組越界
  • 插入元素的位置必須不能小於0, 也不能大於等於size,因此咱們在插入元素以前須要先進行索引檢查。
protected void outOfBounds(int index) {
    throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
}
	
protected void rangeCheck(int index) {
    if (index < 0 || index >= size) {
        outOfBounds(index);
    }
}
複製代碼
  • 插入元素代碼以下:
public void add(int index, E element) {
    // 檢查索引是否越界
    rangeCheckForSize(index);
    // 當插入到0的位置時
    if (index == 0) {
        // 將first指向新節點, 新節點的next指向first以前指向的節點
        first = new Node<E>(element, first.next);
    }else {
        // 找到指定位置前面的節點
        Node<E> prev = node(index - 1);
        // 將前面節點的next指向新節點, 新節點的next指向prev以前指向的節點
        prev.next = new Node<>(element, prev.next);
    }
    size++;
}
複製代碼
  • 添加元素也能夠簡寫:
public void add(E element) {
    // 元素添加到size位置, 即添加到最後面
    add(size, element);
}
複製代碼

四、刪除元素

  • 首先找到刪除節點(delete_node)的前一個節點(pre_node),而後經過變動(pre_node)節點next指針指向刪除節點(delete_node)的下一個節點便可,而後size1
  • 須要判斷是否刪除的第0個元素,若是是,則使用first指向第1個節點

public E remove(int index) {
    // 檢查索引是否越界
    rangeCheck(index);
    // 記錄須要刪除的節點
    Node<E> old = first;
    // 當刪除第0個元素時, 將first的next指向索引爲`1`的節點便可
    if (index == 0) {
        first = first.next;
    }else {
        // 找到前一個元素
        Node<E> prev = node(index - 1);
        // 記錄須要刪除的節點
        old = prev.next;
        // 將prev的next指向須要刪除節點的後一個節點
        prev.next = old.next;
    }
    // size-1
    size--;
    // 返回刪除的元素
    return old.element;
}
複製代碼

五、清空元素

  • first指向null,釋放鏈表全部node,同時size置爲0便可。
public void clear() {
    first = null;
    size = 0;
}
複製代碼

六、修改元素

  • 首先經過遍歷鏈表元素,找到該節點。
private Node<E> node(int index) {
    //越界判斷
    rangeCheck(index);
		
    Node<E> node = first;
        for (int i = 0; i < index; i++) {
            node = node.next;
        }
    return node;
}
複製代碼
  • 而後修改node節點的element便可。
public E set(int index, E element) {
    // 找到對應節點, node方法中已經判斷了索引是否越界
    Node<E> node = node(index);
    // 記錄舊元素
    E old = node.element;
    // 覆蓋元素
    node.element = element;
    // 返回舊元素
    return old;
}
複製代碼

七、查找元素

  • 找到對應的節點, 取出元素便可。
public E get(int index) {
    // node方法中已經判斷了索引是否越界
    return node(index).element;
}
複製代碼

八、查找元素索引

  • 查找指定元素的索引,須要遍歷全部節點,找到節點對應的元素與執行元素相等便可。
  • 若是須要支持節點elementnull,則須要分兩種狀況處理。
private static final int ELEMENT_ON_FOUND = -1;
public int indexOf(E element) {
    // 取出頭結點
    Node<E> node = first;
    // 當element爲null時的處理
    if (element == null) {
        // 遍歷節點, 找到存儲爲null的節點, 返回索引
        for (int i = 0; i < size; i++) {
            if (node.element == null) return i;
            node = node.next;
        }
    }else {
        for (int i = 0; i < size; i++) {
            // 遍歷節點, 找到存儲的元素與指定元素相等的節點, 返回索引
            if (element.equals(node.element)) return i;
            node = node.next;
        }
    }
    // 沒有找到元素對應的節點, 返回ELEMENT_ON_FOUND
    return ELEMENT_ON_FOUND;
 }
複製代碼

九、獲取鏈表存儲元素的個數

  • 獲取鏈表存儲元素的個數, 就是size的值。
public int size() {
    return size;
}
複製代碼

十、鏈表是否爲空

  • 鏈表是否爲空, 只須要判斷size是否等於0便可。
public boolean isEmpty() {
    return size == 0;
}
複製代碼

十一、判斷元素是否存在

  • 判斷元素是否存在, 只須要判斷元素的索引是否爲ELEMENT_ON_FOUND便可。
public boolean contains(E element) {
    return indexOf(element) != ELEMENT_ON_FOUND;
}
複製代碼

十二、打印鏈表中存儲的數據

public String toString() {
    StringBuilder string = new StringBuilder();
    string.append("size = ").append(size).append(", [");
    Node<E> node = first;
    for (int i = 0; i < size; i++) {
        if (i != 0) {
            string.append(",");
        }
        string.append(node.element);
        node = node.next;
    }
    string.append("]");
    return string.toString();
}
複製代碼

到此爲止,咱們成功的實現了鏈表。markdown

4、鏈表的複雜度

5、leetcode算法題

一、刪除鏈表中的節點

二、反轉鏈表

三、環形鏈表

四、移除鏈表元素

五、刪除排序鏈表中的重複元素

六、鏈表的中間結點

相關文章
相關標籤/搜索