1. 程式人生 > >java資料結構和演算法(雜湊表)

java資料結構和演算法(雜湊表)

什麼是雜湊表?

雜湊表是一種資料結構,提供快速的插入和查詢操作。

優點:

  • 插入、查詢、刪除的時間級為O(1);

  • 資料項佔雜湊表長的一半,或者三分之二時,雜湊表的效能最好。

缺點:

  • 基於陣列,陣列建立後難於擴充套件,某些雜湊表被基本填滿時效能下降的非常嚴重;
  • 沒有一種簡單的方法可以以任何一種順序(如從小到大)遍歷整個資料項;

用途:

  • 不需要遍歷資料並且可以提前預測資料量的大小,此時雜湊表的速度和易用性無與倫比。

雜湊化

就是把關鍵字轉化為陣列的下標,在雜湊表中通過雜湊函式來完成。對於某些關鍵字並不需要雜湊函式進行雜湊化,如員工編號。

雜湊函式:作用是將大的多整數範圍轉化為陣列的下標範圍。一般通過取餘%來完成。key%arraySize。

衝突:雜湊函式不能保證每個每個關鍵字都對映到陣列的空白元素的位置。解決衝突的方法分為:開放地址法和鏈地址法。

開放地址法

若資料不能直接放在由雜湊函式計算出來的陣列下標所致的單元時,就要尋找陣列的其他位置。該方法會發生聚集,那些雜湊化後的落在聚集內的資料項,都要一步一步的移動,並且插入在聚集的最後,因此使聚集越來越大。聚集越大,它增長的越快。

線性探測

線上性探測中,線性的查詢空白單元,如52的位置被佔用了,就找53、54。。。的位置直到找到空位。

聚集:一串連續的已填充單元叫做填充序列。增加越來越多的資料項時,填充序列變的越來越長,這就叫聚集。

如果把雜湊表填的太滿,那麼在表中每填一個數據都要花費很長的時間。

package cn.xyc.dataStructure.hash;

/**
 * 
 * 描述:雜湊表中儲存的資料項
 * 
 * <pre>
 * HISTORY
 * ****************************************************************************
 *  ID   DATE           PERSON          REASON
 *  1    2016年10月2日        80002253         Create
 * ****************************************************************************
 * </pre>
 * 
 * @author 蒙奇·d·許
 * @since 1.0
 */
public class DataItem {
	private int item;// 資料項key

	public DataItem(int item) {
		this.item = item;
	}

	public int getKey() {
		return item;
	}
}

package cn.xyc.dataStructure.hash;

/**
 * 
 * 描述:使用線性探測法實現雜湊表
 * 
 * <pre>
 * HISTORY
 * ****************************************************************************
 *  ID   DATE           PERSON          REASON
 *  1    2016年10月2日        80002253         Create
 * ****************************************************************************
 * </pre>
 * 
 * @author 蒙奇·d·許
 * @since 1.0
 */
public class Hash {
	// 儲存雜湊表中的資料
	private DataItem[] hashArray;
	// 雜湊表的長度
	private int arraySize;
	// 可以被刪除的雜湊項
	private DataItem nonItem;

	/**
	 * 初始化雜湊表
	 * 
	 * @param size
	 */
	public Hash(int size) {
		arraySize = size;
		hashArray = new DataItem[arraySize];
		nonItem = new DataItem(-1);// 被刪除的項為-1
	}

	// /////////////////////////////////////////////////////////
	/**
	 * 遍歷雜湊表
	 */
	public void displayTable() {
		System.out.print("Table: ");
		for (int i = 0; i < arraySize; i++) {
			if (hashArray[i] != null) {
				System.out.print(hashArray[i].getKey() + "\t");
			} else {
				System.out.print("**\t");
			}
		}
		System.out.println("");
	}

	// /////////////////////////////////////////////////////
	/**
	 * 雜湊函式
	 * 
	 * @param key
	 * @return
	 */
	public int hashFunc(int key) {
		return key % arraySize;
	}

	// //////////////////////////////////////////////////////
	public void insert(DataItem item) {
		int key = item.getKey();
		int hashVal = hashFunc(key);

		while (hashArray[hashVal] != null && hashArray[hashVal].getKey() != -1) {
			++hashVal;// 直到找到空白元素
			hashVal %= arraySize;// 保證不越界
		}
		// 插入資料
		hashArray[hashVal] = item;
	}

	// //////////////////////////////////////////////
	public DataItem delete(int key) {
		int hashVal = hashFunc(key);
		while (hashArray[hashVal] != null) {
			if (hashArray[hashVal].getKey() == key) {
				DataItem temp = hashArray[hashVal];
				hashArray[hashVal] = nonItem;
				return temp;
			}
			++hashVal;
			hashVal %= arraySize;
		}
		return null;
	}

	// ////////////////////////////////////////////
	public DataItem find(int key) {
		int hashVal = hashFunc(key);
		while (hashArray[hashVal] != null) {
			if (hashArray[hashVal].getKey() == key) {
				return hashArray[hashVal];
			} else {
				++hashVal;
				hashVal %= arraySize;
			}

		}
		return null;
	}

	// //////////////////////////////////////////////////////
	/**
	 * 得到一個長度為質數的陣列長度
	 * 
	 * @param min
	 * @return
	 */
	@SuppressWarnings("unused")
	private int getPrime(int min) {
		for (int i = 0; true; i++) {
			if (isPrime(i)) {
				return i;
			}
		}
	}

	// ///////////////////////////////////
	/**
	 * 判斷一個數是否為質數
	 * 
	 * @param n
	 * @return
	 */
	private boolean isPrime(int n) {
		for (int i = 2; i * i < n; i++) {
			if (n % i == 0) {
				return false;
			}
		}
		return true;
	}

}



擴充套件陣列

雜湊函式是根據陣列的大小來計算給定資料項的大小的,所以不能簡單的從一個數組向另一個數組拷貝資料。需要按照遍歷老陣列,用insert方法向新陣列中插入每個資料項。

二次探測

二次探測試防止聚集產生的一種嘗試,思想史探測相隔較遠的單元,而不是和原始位置相鄰的單元

步驟是是步數的平方:x+1^2、x+2^2、x+3^2,而不是x+1、x+2。

但是當二次探測的搜尋變長時,好像它變得越來越絕望,因為步長平方級增長,可能會飛出整個空間。造成二次聚集。

再雜湊法

是一種依賴關鍵字的探測序列,而不是每個關鍵字都是一樣的。

二次聚集產生的原因是,二次探測的演算法產生的探測序列步長總是固定的1,4,9,16.

方法是:把關鍵字用不同的雜湊函式再雜湊一遍,用這個結果作為步長,對指定的關鍵字,步長在整個探測中是不變的,不過不同的關鍵字使用不同的步長。

第二個雜湊函式的特點:

  • 和第一個雜湊函式不同。
  • 不能輸出0(否則將沒有步長:每次探測都是原地踏步,演算法將陷入死迴圈)。

專家發現下面形式的雜湊函式工作的非常好:

stepSize=constant-(key%constant);其中constant是質數,且小於陣列容量。如stepSize=5-(key%5);

package cn.xyc.dataStructure.hash;

/**
 * 
 * 描述:使用再雜湊法實現雜湊表 <br/>
 * 要求:表的容量是一個質數,否則再雜湊時可能會取到0導致演算法崩潰。
 * 
 * <pre>
 * HISTORY
 * ****************************************************************************
 *  ID   DATE           PERSON          REASON
 *  1    2016年10月2日        80002253         Create
 * ****************************************************************************
 * </pre>
 * 
 * @author 蒙奇·d·許
 * @since 1.0
 */
public class HashDouble {
	// 儲存雜湊表中的資料
	private DataItem[] hashArray;
	// 雜湊表的長度
	private int arraySize;
	// 可以被刪除的雜湊項
	private DataItem nonItem;

	/**
	 * 初始化雜湊表
	 * 
	 * @param size
	 */
	public HashDouble(int size) {
		arraySize = size;
		hashArray = new DataItem[arraySize];
		nonItem = new DataItem(-1);// 被刪除的項為-1
	}

	// /////////////////////////////////////////////////////////
	/**
	 * 遍歷雜湊表
	 */
	public void displayTable() {
		System.out.print("Table: ");
		for (int i = 0; i < arraySize; i++) {
			if (hashArray[i] != null) {
				System.out.print(hashArray[i].getKey() + "\t");
			} else {
				System.out.print("**\t");
			}
		}
		System.out.println("");
	}

	// //////////////////////////////////////////////////////
	public void insert(DataItem item) {
		int key = item.getKey();
		int hashVal = hashFunc1(key);
		int stepSize = hashFunc2(key);
		while (hashArray[hashVal] != null && hashArray[hashVal].getKey() != -1) {
			hashVal += stepSize;// 直到找到空白元素
			hashVal %= arraySize;// 保證不越界
		}
		// 插入資料
		hashArray[hashVal] = item;
	}

	// //////////////////////////////////////////////
	public DataItem delete(int key) {
		int hashVal = hashFunc1(key);
		int stepSize = hashFunc2(key);
		while (hashArray[hashVal] != null) {
			if (hashArray[hashVal].getKey() == key) {
				DataItem temp = hashArray[hashVal];
				hashArray[hashVal] = nonItem;
				return temp;
			}
			hashVal += stepSize;
			hashVal %= arraySize;
		}
		return null;
	}

	// ////////////////////////////////////////////
	public DataItem find(int key) {
		int hashVal = hashFunc1(key);
		int stepSize = hashFunc2(key);
		while (hashArray[hashVal] != null) {
			if (hashArray[hashVal].getKey() == key) {
				return hashArray[hashVal];
			} else {
				hashVal += stepSize;
				hashVal %= arraySize;
			}
		}
		return null;
	}

	// //////////////////////////////////////////////////////
	/**
	 * 得到一個長度為質數的陣列長度
	 * 
	 * @param min
	 * @return
	 */
	@SuppressWarnings("unused")
	private int getPrime(int min) {
		for (int i = 0; true; i++) {
			if (isPrime(i)) {
				return i;
			}
		}
	}

	// /////////////////////////////////////////////////////
	/**
	 * 雜湊函式
	 * 
	 * @param key
	 * @return
	 */
	public int hashFunc1(int key) {
		return key % arraySize;
	}

	// /////////////////////////////////////////////////////
	/**
	 * 再雜湊函式:生成步長
	 * 
	 * @param key
	 * @return
	 */
	public int hashFunc2(int key) {
		// 再雜湊的餘數必須是一個小於陣列長度,不和hF1一樣
		// 不能為為零
		// 是一個質數
		return 5 - (key % 5);
	}

	// ///////////////////////////////////
	/**
	 * 判斷一個數是否為質數
	 * 
	 * @param n
	 * @return
	 */
	private boolean isPrime(int n) {
		for (int i = 2; i * i < n; i++) {
			if (n % i == 0) {
				return false;
			}
		}
		return true;
	}

}


鏈地址法

在雜湊表中每個單元中設定連結串列。某個資料項的關鍵字還是像通常一樣對映到雜湊表的單元,而資料項本身插入到這個單元的連結串列中。其他同樣對映到這個位置的資料項只需要加入到連結串列中,不需要在原始陣列中尋找空位。

優缺點:

有序連結串列不能加快成功的搜尋,但可以減少不成功搜尋中的一半的時間。

刪除的時間級也減少一半

插入的時間延長,因為資料項不能只插在表頭;插入前必須找到有序表中的正確位置。

package cn.xyc.dataStructure.hash;

/**
 * 
 * 描述:連結串列中的節點
 * 
 * <pre>
 * HISTORY
 * ****************************************************************************
 *  ID   DATE           PERSON          REASON
 *  1    2016年10月2日        80002253         Create
 * ****************************************************************************
 * </pre>
 * 
 * @author 蒙奇·D·許
 * @since 1.0
 */
public class LinkItem {
	private int data;// 連結串列中的資料項
	public LinkItem nextLink;// 下一個節點

	public LinkItem(int data) {
		this.data = data;
	}

	public int getKey() {
		return data;
	}

	public void dispalyLink() {
		System.out.println(data + "\t");
	}
}

package cn.xyc.dataStructure.hash;

/**
 * 
 * 描述:從小到大的有序連結串列
 * 
 * <pre>
 * HISTORY
 * ****************************************************************************
 *  ID   DATE           PERSON          REASON
 *  1    2016年10月2日        80002253         Create
 * ****************************************************************************
 * </pre>
 * 
 * @author 蒙奇·d·許
 * @since 1.0
 */
public class SortLink {
	// 連結串列的第一個元素
	private LinkItem first;

	// ////////////////////////////////
	public SortLink() {
		first = null;
	}

	// /////////////////////////////////////
	public void insert(LinkItem theLink) {
		int key = theLink.getKey();
		LinkItem previousItem = null;
		// 從第一個開始
		LinkItem currentItem = first;
		while (currentItem != null && key > currentItem.getKey()) {
			previousItem = currentItem;
			currentItem = currentItem.nextLink;
		}
		if (previousItem == null) {
			first = theLink;
		} else {
			previousItem.nextLink = theLink;
			theLink.nextLink = currentItem;
		}
	}

	// /////////////////////////////////////////
	public void delete(int key) {
		LinkItem previous = null;
		LinkItem current = first;
		while (current != null && current.getKey() != key) {
			previous = current;
			current = current.nextLink;
		}
		if (previous == null) {
			// 說明是第一個
			if (first != null) {
				first = first.nextLink;
			}
		} else {
			previous.nextLink = current.nextLink;
		}
	}

	// ////////////////////////////////////////////////
	public LinkItem find(int key) {
		LinkItem currentItem = first;
		while (currentItem != null && currentItem.getKey() <= key) {
			if (currentItem.getKey() == key) {
				return currentItem;
			}
			currentItem = currentItem.nextLink;
		}
		return null;
	}
	///////////////////////////////////////////////
	public void displayList(){
		LinkItem currentItem=first;
		while(currentItem!=null){
			currentItem.dispalyLink();
			currentItem=currentItem.nextLink;
		}
		System.out.println();
	}

}

package cn.xyc.dataStructure.hash;

/**
 * 
 * 描述:使用鏈地址法實現雜湊表
 * 
 * <pre>
 * HISTORY
 * ****************************************************************************
 *  ID   DATE           PERSON          REASON
 *  1    2016年10月2日        80002253         Create
 * ****************************************************************************
 * </pre>
 * 
 * @author 蒙奇·d·許
 * @since 1.0
 */
public class HashLink {
	// 儲存雜湊表中的資料
	private SortLink[] hashArray;
	// 雜湊表的長度
	private int arraySize;
	// 可以被刪除的雜湊項
	private SortLink nonItem;

	/**
	 * 初始化雜湊表
	 * 
	 * @param size
	 */
	public HashLink(int size) {
		arraySize = size;
		hashArray = new SortLink[arraySize];
		// 填充陣列
		for (int i = 0; i < hashArray.length; i++) {
			hashArray[i] = new SortLink();
		}
	}

	// /////////////////////////////////////////////////////////
	/**
	 * 遍歷雜湊表
	 */
	public void displayTable() {
		System.out.print("Table: ");
		for (int i = 0; i < arraySize; i++) {
			if (hashArray[i] != null) {
				hashArray[i].displayList();
			} else {
				System.out.print("**\t");
			}
		}
		System.out.println("");
	}

	// /////////////////////////////////////////////////////
	/**
	 * 雜湊函式
	 * 
	 * @param key
	 * @return
	 */
	public int hashFunc(int key) {
		return key % arraySize;
	}

	// //////////////////////////////////////////////////////
	public void insert(LinkItem item) {
		int key = item.getKey();
		int hashVal = hashFunc(key);
		// 插入資料
		hashArray[hashVal].insert(item);
	}

	// //////////////////////////////////////////////
	public void delete(int key) {
		int hashVal = hashFunc(key);
		hashArray[hashVal].delete(key);
	}

	// ////////////////////////////////////////////
	public LinkItem find(int key) {
		int hashVal = hashFunc(key);
		LinkItem item = hashArray[hashVal].find(key);
		return item;
	}

	// //////////////////////////////////////////////////////
	/**
	 * 得到一個長度為質數的陣列長度
	 * 
	 * @param min
	 * @return
	 */
	@SuppressWarnings("unused")
	private int getPrime(int min) {
		for (int i = 0; true; i++) {
			if (isPrime(i)) {
				return i;
			}
		}
	}

	// ///////////////////////////////////
	/**
	 * 判斷一個數是否為質數
	 * 
	 * @param n
	 * @return
	 */
	private boolean isPrime(int n) {
		for (int i = 2; i * i < n; i++) {
			if (n % i == 0) {
				return false;
			}
		}
		return true;
	}

}

另一種方法類似於連結地址法,它是在雜湊表的每個單元中使用陣列,而不是連結串列。這樣的陣列稱為桶。

缺點:桶的容量太小會溢位,太大浪費空間。

開放地址法和鏈地址法的比較:

項數未知:使用鏈地址法。


相關推薦

Java資料結構演算法--

Hash表也稱散列表,直譯為雜湊表,hash表是一種根據關鍵字值(key-value)而直接進行訪問的資料結構。它基於陣列,通過把關鍵字對映到陣列的某個下標來加快查詢速度,這種對映轉換作用的函式我們稱之為雜湊函式。 每種雜湊表都有自己的雜湊函式,雜湊函式是自己定義的,沒有統一的標準,下面我們

java資料結構演算法()

什麼是雜湊表? 雜湊表是一種資料結構,提供快速的插入和查詢操作。 優點: 插入、查詢、刪除的時間級為O(1); 資料項佔雜湊表長的一半,或者三分之二時,雜湊表的效能最好。缺點: 基於陣列,陣列建立後難於擴充套件,某些雜湊表被基本填滿時效能下降的非常嚴重;沒有一種簡單的方

資料結構演算法 的特點

#雜湊表1.雜湊表的查詢效率主要取決於構造雜湊表時選取的雜湊函式和處理衝突的方法。2.在各種查詢方法中,平均査找長度與結點個數n無關的查詢方法是雜湊表查詢法。3.雜湊函式取值是否均勻是評價雜湊函式好壞的標準。4.雜湊儲存方法只能儲存資料元素的值,不能儲存資料元素之間的關係。5

Java資料結構演算法:HashMap,函式

1. HashMap概述 HashMap是基於雜湊表的Map介面的非同步實現(Hashtable跟HashMap很像,唯一的區別是Hashtalbe中的方法是執行緒安全的,也就是同步的)。此實現提供所有可選的對映操作,並允許使用null值和null鍵。此類不保

java資料結構演算法09(

  樹的結構說得差不多了,現在我們來說說一種資料結構叫做雜湊表(hash table),雜湊表有是幹什麼用的呢?我們知道樹的操作的時間複雜度通常為O(logN),那有沒有更快的資料結構?當然有,那就是雜湊表;   1.雜湊表簡介   雜湊表(hash table)是一種資料結構,提供很快速的插

查詢演算法 淺談演算法資料結構: 七 二叉查詢樹 淺談演算法資料結構: 十一

閱讀目錄 1. 順序查詢 2. 二分查詢 3. 插值查詢 4. 斐波那契查詢 5. 樹表查詢 6. 分塊查詢 7. 雜湊查詢   查詢是在大量的資訊中尋找一個特定的資訊元素,在計算機應用中,查詢是常用的基本運算,例如編譯程式中符號表的查詢。本文

淺談演算法資料結構: 十一

在前面的系列文章中,依次介紹了基於無序列表的順序查詢,基於有序陣列的二分查詢,平衡查詢樹,以及紅黑樹,下圖是他們在平均以及最差情況下的時間複雜度: 可以看到在時間複雜度上,紅黑樹在平均情況下插入,查詢以及刪除上都達到了lgN的時間複雜度。 那麼有沒有查詢效率更高的資料結構呢,答案就是本文接下來要介紹了

Java資料結構演算法(一)線性結構之單鏈

Java資料結構和演算法(一)線性結構之單鏈表 prev current next -------------- -------------- -------------- | value | next | ->

Java資料結構演算法(一):簡介

  本系列部落格我們將學習資料結構和演算法,為什麼要學習資料結構和演算法,這裡我舉個簡單的例子。   程式設計好比是一輛汽車,而資料結構和演算法是汽車內部的變速箱。一個開車的人不懂變速箱的原理也是能開車的,同理一個不懂資料結構和演算法的人也能程式設計。但是如果一個開車的人懂變速箱的原理,比如降低速

java資料結構演算法

java資料結構與演算法 寫給讀者的話: 本人是一個剛剛畢業的程式設計師,大學期間資料結構學的比較紮實,來工作後發現雖然概念都知道,但是應用不是很熟練,所以打算重新擼幾遍資料結構,正好在寫java,這裡就用java描述資料結構了;然後有幾個要點: 1)實踐永遠是檢驗真理的唯一

java資料結構演算法程式設計作業系列篇-陣列

/** * 程式設計作業 2.1 向highArray.java程式(清單2.3)的HighArray類新增一個名為getMax()的方法,它返回 陣列中最大關鍵字的值,當陣列為空時返回-1。向main()中新增一些程式碼來使用這個方法。 可以假設所有關鍵字都是正數。 2.2 修改程式設計作業

Java資料結構演算法)最短路徑---Dijkstra+Floyd

參考博文 Floyd演算法和Dijkstra演算法都不能針對帶有負權邊的圖,否則一直走負權邊,沒有最小,只有更小!! Floyd演算法 import java.util.Scanner; //Floyd演算法 class Graph{ public int[][] ad

Java資料結構演算法)拓撲排序

參考博文 拓撲排序 public class Main { public static void main(String[] args){ System.out.println("請輸入一個圖的鄰接矩陣(8X8):"); int[][] map = new int[

Java資料結構演算法)最小生成樹---Kruskal演算法(並查集)

該文章利用prime演算法求得連通圖的最小生成樹對應的邊權最小和,prime演算法是從頂點的角度思考和解決問題。本文介紹的Kruskal演算法將從邊的角度考慮並解決問題,利用了並查集方便地解決了最小生成樹的問題。 本文參考博文 //並查集 class UnionSameSet{

Java資料結構演算法)最小生成樹---prime演算法

參考博文 public class Main { public static void main(String[] args){ int inf = 1000000;//無窮大 //圖,可以這樣認為:圖的任意兩個頂點之間都有邊,兩頂點無法到達的,可以認為他們之間的邊權是

Java資料結構演算法)圖的DFSBFS

DFS+BFS import java.util.*; //以無向圖為例,實現圖的深度優先搜尋和廣度優先搜尋 class Graph{ public int[][] adjacencyMatrix;//鄰接矩陣,1代表有邊,0代表沒有邊 public int arcNumb

Java資料結構演算法)堆---優先佇列、堆排序

堆主要用於實現優先佇列。 利用有序陣列可以實現優先佇列(從小到大或從大到小的陣列),刪除的時間複雜度是O(1),但是插入的時間複雜度是O(N)。用堆實現優先佇列,插入和刪除的時間複雜度都是O(logN)。 簡介 堆是一種完全二叉樹,且每個節點的值都大於等於子節點值(大根堆)。

Java資料結構演算法 - 堆

堆的介紹 Q: 什麼是堆? A: 這裡的“堆”是指一種特殊的二叉樹,不要和Java、C/C++等程式語言裡的“堆”混淆,後者指的是程式設計師用new能得到的計算機記憶體的可用部分 A: 堆是有如下特點的二叉樹: 1) 是一棵完全二叉樹 2) 通常由陣列實現。前面介

Java資料結構演算法(七)——連結串列

目錄   前面部落格我們在講解陣列中,知道陣列作為資料儲存結構有一定的缺陷。在無序陣列中,搜尋效能差,在有序陣列中,插入效率又很低,而且這兩種陣列的刪除效率都很低,並且陣列在建立後,其大小是固定了,設定的過大會造成記憶體的浪費,過小又不能滿足資料量的儲存。  

Java資料結構演算法(一)樹

Java資料結構和演算法(一)樹 前面講到的連結串列、棧和佇列都是一對一的線性結構,這節講一對多的線性結構 - 樹。「一對多」就是指一個元素只能有一個前驅,但可以有多個後繼。 一、樹 度(Degree) :節點擁有的子樹數。樹的度是樹中各個節點度的最大值。 節點 :度為 0 的節點稱為葉節