概要
線性表是一種線性結構,它是具有相同類型的n(n≥0)個數據元素組成的有限序列。本章先介紹線性表的幾個基本組成部分:數組、單向鏈表、雙向鏈表;隨后給出雙向鏈表的C、C++和Java三種語言的實現。內容包括:
出處:http://www.cnblogs.com/skywang12345/p/3561803.html
數組
數組有上界和下界,數組的元素在上下界內是連續的。
數組的特點是:數據是連續的;隨機訪問速度快。
數組中稍微復雜一點的是多維數組和動態數組。對於C語言而言,多維數組本質上也是通過一維數組實現的。至於動態數組,是指數組的容量能動態增長的數組;對於C語言而言,若要提供動態數組,需要手動實現;而對於C++而言,STL提供了Vector;對於Java而言,Collection集合中提供了ArrayList和Vector。
單向鏈表
單向鏈表(單鏈表)是鏈表的一種,它由節點組成,每個節點都包含下一個節點的指針。
表頭為空,表頭的后繼節點是"節點10"(數據為10的節點),"節點10"的后繼節點是"節點20"(數據為10的節點),...
單鏈表刪除節點
刪除"節點30"
刪除之前:"節點20" 的后繼節點為"節點30",而"節點30" 的后繼節點為"節點40"。
刪除之后:"節點20" 的后繼節點為"節點40"。
單鏈表添加節點
在"節點10"與"節點20"之間添加"節點15"
添加之前:"節點10" 的后繼節點為"節點20"。
添加之后:"節點10" 的后繼節點為"節點15",而"節點15" 的后繼節點為"節點20"。
單鏈表的特點是:節點的鏈接方向是單向的;相對於數組來說,單鏈表的的隨機訪問速度較慢,但是單鏈表刪除/添加數據的效率很高。
雙向鏈表
雙向鏈表(雙鏈表)是鏈表的一種。和單鏈表一樣,雙鏈表也是由節點組成,它的每個數據結點中都有兩個指針,分別指向直接后繼和直接前驅。所以,從雙向鏈表中的任意一個結點開始,都可以很方便地訪問它的前驅結點和后繼結點。一般我們都構造雙向循環鏈表。
表頭為空,表頭的后繼節點為"節點10"(數據為10的節點);"節點10"的后繼節點是"節點20"(數據為10的節點),"節點20"的前繼節點是"節點10";"節點20"的后繼節點是"節點30","節點30"的前繼節點是"節點20";...;末尾節點的后繼節點是表頭。
雙鏈表刪除節點
刪除"節點30"
刪除之前:"節點20"的后繼節點為"節點30","節點30" 的前繼節點為"節點20"。"節點30"的后繼節點為"節點40","節點40" 的前繼節點為"節點30"。
刪除之后:"節點20"的后繼節點為"節點40","節點40" 的前繼節點為"節點20"。
雙鏈表添加節點
在"節點10"與"節點20"之間添加"節點15"
添加之前:"節點10"的后繼節點為"節點20","節點20" 的前繼節點為"節點10"。
添加之后:"節點10"的后繼節點為"節點15","節點15" 的前繼節點為"節點10"。"節點15"的后繼節點為"節點20","節點20" 的前繼節點為"節點15"。
下面介紹雙鏈表的實現,介紹Java實現
雙鏈表類(DoubleLink.java)

/** * Java 實現的雙向鏈表。 * 注:java自帶的集合包中有實現雙向鏈表,路徑是:java.util.LinkedList * * @author skywang * @date 2013/11/07 */ public class DoubleLink<T> { // 表頭 private DNode<T> mHead; // 節點個數 private int mCount; // 雙向鏈表“節點”對應的結構體 private class DNode<T> { public DNode prev; public DNode next; public T value; public DNode(T value, DNode prev, DNode next) { this.value = value; this.prev = prev; this.next = next; } } // 構造函數 public DoubleLink() { // 創建“表頭”。注意:表頭沒有存儲數據! mHead = new DNode<T>(null, null, null); mHead.prev = mHead.next = mHead; // 初始化“節點個數”為0 mCount = 0; } // 返回節點數目 public int size() { return mCount; } // 返回鏈表是否為空 public boolean isEmpty() { return mCount==0; } // 獲取第index位置的節點 private DNode<T> getNode(int index) { if (index<0 || index>=mCount) throw new IndexOutOfBoundsException(); // 正向查找 if (index <= mCount/2) { DNode<T> node = mHead.next; for (int i=0; i<index; i++) node = node.next; return node; } // 反向查找 DNode<T> rnode = mHead.prev; int rindex = mCount - index -1; for (int j=0; j<rindex; j++) rnode = rnode.prev; return rnode; } // 獲取第index位置的節點的值 public T get(int index) { return getNode(index).value; } // 獲取第1個節點的值 public T getFirst() { return getNode(0).value; } // 獲取最后一個節點的值 public T getLast() { return getNode(mCount-1).value; } // 將節點插入到第index位置之前 public void insert(int index, T t) { if (index==0) { DNode<T> node = new DNode<T>(t, mHead, mHead.next); mHead.next.prev = node; mHead.next = node; mCount++; return ; } DNode<T> inode = getNode(index); DNode<T> tnode = new DNode<T>(t, inode.prev, inode); inode.prev.next = tnode; inode.next = tnode; mCount++; return ; } // 將節點插入第一個節點處。 public void insertFirst(T t) { insert(0, t); } // 將節點追加到鏈表的末尾 public void appendLast(T t) { DNode<T> node = new DNode<T>(t, mHead.prev, mHead); mHead.prev.next = node; mHead.prev = node; mCount++; } // 刪除index位置的節點 public void del(int index) { DNode<T> inode = getNode(index); inode.prev.next = inode.next; inode.next.prev = inode.prev; inode = null; mCount--; } // 刪除第一個節點 public void deleteFirst() { del(0); } // 刪除最后一個節點 public void deleteLast() { del(mCount-1); } }
ArrayList、Vector、LinkedList 區別與聯系:
看圖:
如上圖所示:
ArrayList是實現了基於動態數組的數據結構,LinkedList基於雙線鏈表的數據結構。
ArrayList可以隨機定位對於新增和刪除操作add和remove,LinedList比較占優勢
具有Collection接口必備的iterator()方法外,List還提供一個listIterator()方法ListIterator多了一些add()之類的方法,允許添加,刪除,設定元素,還能向前或向后遍歷。
Vector與ArrayList唯一的區別是,Vector是線程安全的,即它的大部分方法都包含有關鍵字synchronized,因此,若對於單一線程的應用來說,最好使用ArrayList代替Vector,因為這樣效率會快很多(類似的情況有StringBuffer線程安全的與StringBuilder線程不安全的);而在多線程程序中,為了保證數據的同步和一致性,可以使用Vector代替ArrayList實現同樣的功能。
主要區別:
1、ArrayList、Vector、LinkedList類都是java.util包中,均為可伸縮數組。
2、ArrayList和Vector底層都是數組實現的,所以,索引/查詢數據快,刪除、插入數據慢。
ArrayList采用異步的方式,性能好,屬於非線程安全的操作類。(JDK1.2)
Vector采用同步的方式,性能較低,屬於線程安全的操作類。(JDK1.0)
3、LinkedList底層是鏈表實現,所以,索引慢,刪除、插入快,屬於非線程安全的操作類。
java定義數組需要聲明長度,然后arraylist基於數組,等同於一個動態數組的實現,但是查詢比較慢,所以可以自己編寫一個動態數組來實現,代碼如下:

package com.newer.tw.com; /** * 自定義長度可變數組 * * @author Administrator * */ public class MyList { // 定義一個初始長度為0的數組,用來緩存數據 private String[] src = new String[0]; // 增加 public void add(String s) { //定義新數組,長度是原數組長度+1 String[] dest = new String[src.length+1]; //將原數組的數據拷貝到新數組 System.arraycopy(src, 0, dest, 0, src.length); //將新元素放到dest數組的末尾 dest[src.length]=s; //將src指向dest src=dest; } // 修改指定位置的元素 public void modify(int index, String s) { src[index]=s; } // 刪除指定位置的元素 public void delete(int index) { String[] dest = new String[src.length-1]; //將原數組的數據拷貝到新數組 System.arraycopy(src, 0, dest, 0, index); System.arraycopy(src, index+1, dest, index, src.length-1-index); src=dest; } // 獲得指定位置的元素 public String get(int index) { return src[index]; } // 在指定位置插入指定元素 public void insert(int index, String s) { //定義新數組,長度是原數組長度+1 String[] dest = new String[src.length+1]; //將原數組的數據拷貝到新數組 System.arraycopy(src, 0, dest, 0, index); dest[index]=s; System.arraycopy(src, index, dest, index+1, src.length-index); src=dest; } // 獲得元素個數 public int size() { return src.length; } public void print() { for(int i=0;i<size();i++) System.out.println(src[i]); } public static void main(String[] args) { MyList m=new MyList(); m.add("15"); m.add("16"); m.add("17"); m.add("18"); m.add("19"); System.out.println("插入之前:"); m.print(); m.insert(2,"22"); System.out.println("插入之后:"); m.print(); } }
Hashmap 原理
參考: https://blog.csdn.net/qa962839575/article/details/44889553
在java編程語言中,最基本的結構就是兩種,一個是數組,另外一個是模擬指針(引用),所有的數據結構都可以用這兩個基本結構來構造的,hashmap也不例外。Hashmap實際上是一個數組和鏈表的結合體(在數據結構中,一般稱之為“鏈表散列“),請看下圖
從圖中我們可以看到一個hashmap就是一個數組結構,當新建一個hashmap的時候,就會初始化一個數組。
/** * The table, resized as necessary. Length MUST Always be a power of two. * FIXME 這里需要注意這句話,至於原因后面會講到 */ transient Entry[] table; static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; final int hash; Entry<K,V> next; .......... }
Entry就是數組中的元素,它持有一個指向下一個元素的引用,這就構成了鏈表。
當我們往hashmap中put元素的時候,先根據key的hash值得到這個元素在數組中的位置(即下標),然后就可以把這個元素放到對應的位置中了。如果這個元素所在的位子上已經存放有其他元素了,那么在同一個位子上的元素將以鏈表的形式存放,新加入的放在鏈頭,最先加入的放在鏈尾。從hashmap中get元素時,首先計算key的hashcode,找到數組中對應位置的某一元素,然后通過key的equals方法在對應位置的鏈表中找到需要的元素。從這里我們可以想象得到,如果每個位置上的鏈表只有一個元素,那么hashmap的get效率將是最高的,
static int indexFor(int h, int length) { return h & (length-1); }
首先算得key得hashcode值,然后跟數組的長度-1做一次“與”運算(&)。看上去很簡單,其實比較有玄機。比如數組的長度是2的4次方,那么hashcode就會和2的4次方-1做“與”運算。很多人都有這個疑問,為什么hashmap的數組初始化大小都是2的次方大小時,hashmap的效率最高
當數組長度為2的n次冪的時候,不同的key算得得index相同的幾率較小,那么數據在數組上分布就比較均勻,也就是說碰撞的幾率小,相對的,查詢的時候就不用遍歷某個位置上的鏈表,這樣查詢效率也就較高了。
說到這里,我們再回頭看一下hashmap中默認的數組大小是多少,查看源代碼可以得知是16,為什么是16,而不是15,也不是20呢,看到上面annegu的解釋之后我們就清楚了吧,顯然是因為16是2的整數次冪的原因,在小數據量的情況下16比15和20更能減少key之間的碰撞,而加快查詢的效率。
初始容量為16,初始負載因子loadFactor為0.75 ,當hashmap中元素個數超過16*0.75=12的時候,就把數組的大小擴展為2*16=32,即擴大一倍;所以它帶有動態的意義
初始容量與負載因子
參考: https://www.cnblogs.com/haifeng1990/p/6262417.html
HashMap有兩個參數影響性能,初始容量和負載因子