Android基礎(數據結構)

目錄

一,數組

二,鏈表

三,Map

四,  Set

五,Tree


今晚不想寫公司項目了,頭暈暈的,整理下數據結構吧:

數據結構:簡單說就是指一組數據的存儲結構,算法就是操作數據的方法。

首先,需要明白數據結構的繼承關係,數據結構一切都源於Collection接口和Map接口~

Collection繼承接口Iterable:顧名思義迭代,該接口只是返回了迭代器對象Iterator<T> iterator();接下來就可以通過iterator的hasNext方法對list進行迭代了。

數據結構包括:數組、鏈表、棧、隊列、散列表、二叉樹、堆、調錶、圖、樹;

算法包括:遞歸、排序、查找、搜索、哈希算法、貪心算法、分治算法、回溯算法、動態規劃、字符串匹配算法。

說下繼承關係List--->Collection-->Iterable

接口Iterable:顧名思義迭代,該接口只是返回了迭代器對象Iterator<T> iterator();接下來就可以通過iterator的hasNext方法對list進行迭代了。

一,數組

1,數組分配在一塊連續的數據空間上,元素有序可重複,所以在分配空間時必須要確定它的大小。

2,優點是查詢快,因爲有角標/索引;缺點是增刪慢,因爲空間是固定的,增刪需要複製數組中的元素。

3,用到的最多的是數組容器ArrayList,方法有:add(Object o),addAll(Object o),boolean contains(Object o),size(),remove()可傳item,index等,clear(),indexOf(),lastIndexOf();

4,add方法和remove方法:

先擴容,再賦值。

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 數組賦值操作
    elementData[size++] = e;
    return true;
}


private void ensureCapacityInternal(int minCapacity) {
    //1,先判斷elementData的內容是否爲空,如果是空的,就設置一個最小容量
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //最小容量是默認值(10)和size + 1中的最大值
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    //2,開始擴容
    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // 如果數組的新容量大於當前數據的大小,就需要擴容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

//擴容函數
private void grow(int minCapacity) {
    // 1.5倍擴容
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 複製數組元素
    elementData = Arrays.copyOf(elementData, newCapacity);
}

注意,當在指定位置 i 插入元素時,同樣會擴容,並且位置i之後的元素都要通過複製的方法依次後移。所以增刪效率低。

public void add(int index, E element) {
    rangeCheckForAdd(index);
    ensureCapacityInternal(size + 1);
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}


public E remove(int index) {
    rangeCheck(index);
    modCount++;
    E oldValue = elementData(index);
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; 
    return oldValue;
}

5,get方法

public E get(int index) {
    rangeCheck(index);
    return elementData(index);
}

E elementData(int index) {
    return (E) elementData[index];
}

4,平時會用到的:

數組轉ArrayList:

ArrayList<Integer> arrayList = (ArrayList<Integer>) Arrays.stream(nums).boxed().collect(Collectors.toList());

二,鏈表

1,鏈表是一塊動態的空間,可以隨意的改變長短,初始化時不需要確定大小。由一系列結點(鏈表中每一個元素稱爲結點)組成,每個結點包括兩個部分:一個是存儲數據元素的數據域,另一個是存儲下一個(上一個)結點地址的指針域。

2,增刪快,因爲只需要修改指針,不需要移動複製元素;查詢慢,因爲沒有角標,只能從第一個元素開始查。

3,LinkedList:

數據結構是一個雙向鏈表,元素有序可重複,內部結構有前驅和後繼,不是連續的。

如圖:兩個指針一個指向前一個元素,一個指向後一個元素,單鏈表比這個少了一個指向前一個元素的指針。

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;
    }
}

方法有add(),contains(),remove,push(進棧),pop(出站)

首先看add方法和remove方法:

public boolean add(E e) {
    //連接最後一個
    linkLast(e);
    return true;
}
void linkLast(E e) {
    final Node<E> l = last;
    //創建一個節點,頭指針指向last,後指針爲空
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    //如果last爲空說明鏈表是空的,說明新增的節點是頭結點;
    //如果非空則把之前的last的後指針指向該節點。
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

public E remove(int index) {
    // 檢查index是否爲0或是否越界
    checkElementIndex(index);
    return unlink(node(index));
}

//根據index查到對應的節點
Node<E> node(int index) {
    // size >> 1其實就是size/2
    //如果index小於size/2,則從鏈表的第一個節點開始遍歷到index然後返回節點;
    //如果index大於size/2,則從鏈表最後一個節點開始倒敘遍歷到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;
    }
}

//斷開節點
E unlink(Node<E> x) {
    final E element = x.item;
    final Node<E> next = x.next;
    final 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--;
    modCount++;
    return element;
}


再看下get方法:

public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}

三,Map

參考鏈接:https://blog.csdn.net/zhengwangzw/article/details/104889549

1,hashMap的原理

HashMap 是基於哈希表的 Map 接口的非同步實現。此實現提供所有可選的映射操作,並允許使用 null 值和 null 鍵。此類不保證映射的順序,特別是它不保證該順序恆久不變。HashMap實際上是一個“鏈表散列”的數據結構,即數組和鏈表/紅黑樹的結合體,數組中的每一項又是一個鏈表。當新建一個 HashMap 的時候,就會初始化一個數組。數組默認長度是16,負載因子是0.75,當元素的個數大於數組長度*0.75時,數組會擴容。如果初始化時傳入了初始大小k,那數組的長度就爲 大於k的那個2的整數次方。數組用來確定桶的位置。

爲什麼採用紅黑樹:

因爲鏈表較多時,時間複雜度最壞爲O(lgn),但鏈表爲O(n)。

 

HashMap是基於hashing的原理,使用put(k,v)存儲對象到HashMap中,使用get(k)從HashMap中獲取對象。

2,hashMap的put方法

當put時會先對key調用hashCode()方法,得到hash值後會通過hash&(length-1)得到這個元素在數組中的下標index(桶的位置);

如果發生了hash碰撞(計算得到的hash值相等,也就是該位置已經有元素了),那就繼續判斷key是否相等,相等的話就覆蓋,不相等的話那就放到鏈表最後(Java1.8前的版本是放到最前)。如果拉鍊過長,查詢的性能會下降,所以在Java8中添加了紅黑樹結構,當一個桶的長度超過8時,就將其轉爲紅黑樹鏈表,如果小於6,再重新轉換爲普通鏈表。

當get時,HashMap會使用key對象的hashCode找到在數組中的位置(桶的位置),然後調用keys.equals()方法去找到在鏈表中的正確節點,最終找到值。

3,hash函數怎麼設計的?

hash函數是先拿到key的hashcode,是32位的int值,然後讓hashcode的高16位和低16位進行異或操作,這樣設計可以儘可能降低hash碰撞,越分散越好,再就是位運算比較高效。

static final int hash(Object key){
    int h;
    //key爲null時hash爲0
    return (key == null) ? 0 : (h=key.hashCode()) ^ (h>>>16);
}

4,爲什麼高16位和低16位異或能降低hash碰撞?爲什麼不能直接採用hashCode?

因爲key.hashCode()函數調用的是key鍵值類型自帶的哈希函數,返回int型散列值。int值範圍爲-2147483648~2147483647(2的31次方),只要哈希函數映射的比較均勻鬆散,一般不會出現碰撞的,但是對內存來說,這麼大的數組是放不下的。所以採用了取模運算:

bucketIndex = indexFor(hash, table.length);

static int indexFor(int h, int length) {
    //與運算比%要高效
     return h & (length-1);
}

順便說一下,這也正好解釋了爲什麼HashMap的數組長度要取2的整數冪。因爲這樣(數組長度-1)正好相當於一個“低位掩碼”。“與”操作的結果就是散列值的高位全部歸零,只保留低位值,用來做數組下標訪問。以初始長度16爲例,16-1=15。2進製表示是00000000 00000000 00001111。和某散列值做“與”操作如下,結果就是截取了最低的四位值。

看下圖:

第一行取得了key的hashcode;

第二行進行高16位和低16位的異或運算(相同爲0,不同爲1),這樣前16位肯定都爲1,這樣就是爲了混合原始哈希碼的高位和低位,以此來加大低位的隨機性。而且混合後的低位摻雜了高位的部分特徵,這樣高位的信息也被變相保留下來;

第三行進行(n-1)和hash的與運算(都爲1才爲1,其餘情況爲0),這裏n表示默認初始長度16.

5,Hashtable 與 HashMap 的簡單比較

HashTable 基於 Dictionary 類,而 HashMap 是基於 AbstractMap。Dictionary 是任何可將鍵映射到相應值的類的抽象父類,而 AbstractMap 是基於 Map 接口的實現,它以最大限度地減少實現此接口所需的工作。

HashMap 的 key 和 value 都允許爲 null,而 Hashtable 的 key 和 value 都不允許爲 null。HashMap 遇到 key 爲 null 的時候,調用 putForNullKey 方法進行處理,而對 value 沒有處理;Hashtable遇到 null,直接返回 NullPointerException。

Hashtable 方法是同步,而HashMap則不是。Hashtable 中的幾乎所有的 public 的方法都是 synchronized 的,而有些方法也是在內部通過 synchronized 代碼塊來實現。所以有人一般都建議如果是涉及到多線程同步時採用 HashTable,沒有涉及就採用 HashMap。

除了HashTable,還可以解決線程不安全的方法有:在 Collections 類中存在一個靜態方法:synchronizedMap(),該方法創建了一個線程安全的 Map 對象,並把它作爲一個封裝的對象來返回。也可以使用ConcurrentHashMap,ConcurrentHashMap成員變量使用volatile 修飾,免除了指令重排序,同時保證內存可見性,另外使用CAS操作和synchronized結合實現賦值操作,多線程操作只會鎖住當前操作索引的節點,如線程A只鎖住A節點所在的鏈表,線程B智鎖珠B節點所在的鏈表,互不干擾,這也叫分段鎖。

6,HashMap有序嗎?

因爲hash值是隨機插入的,所以無序!但LinkedHashMap是有序的!TreeMap也是有序的!

LinkedHashMap內部維護了一個單鏈表,有頭尾節點,同時LinkedHashMap節點Entry內部除了繼承HashMap的Node屬性,還有before 和 after用於標識前置節點和後置節點。可以實現按插入的順序或訪問順序排序。

TreeMap是按照Key的自然順序或者Comprator的順序進行排序,內部是通過紅黑樹來實現。所以要麼key所屬的類實現Comparable接口,或者自定義一個實現了Comparator接口的比較器,傳給TreeMap用戶key的比較。

四, Set

Set:無序(存入和取出的順序不一定相同),不能重複,非線程安全的。直接繼承Collection

HashSet :

數據結構是哈希表,存儲的元素時哈希值,常數階。

HashSet內部使用HashMap的key存儲元素,以此來保證元素不重複;

HashSet是無序的,因爲HashMap的key是無序的;

HashSet中允許有一個null元素,因爲HashMap允許key爲null;

若保證不添加相同元素,需重寫對象(如Person類)的兩個方法:hasncode(),equals()。

方法有:add,remove(Object o),contain(Object o),iteartor(),size(),isEmpty(),clear(),注意,沒有get方法!!

五,Tree

樹:樹的數據元素間具有層次關係的非線性的結構。

前序遍歷:中左右;後序遍歷:左右中;

二叉樹:

BinaryNode(T data , BinaryNode<T> left , BinaryNode<T> right )

TreeSet :

數據結構是樹 對數階log(n) 。

若保證不添加相同元素,元素的類(如Person類)需實現Comparable接口,並重寫compareTo方法。

Map:key-value  鍵值對,key不能重複,value可以重複,通過key可以獲得value

HashMap:數據結構是hashTable。

HashTree:樹結構,按照key值排序,但key的類需實現Comparable接口

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章