我們來比較下散列的3種沖突解決方式,建立3個類,分別代表3種不同的沖突解決方式:
- MyHash_MAD_多槽位
- MyHash_MAD_獨立鏈
- MyHash_MAD_線性探測法
然后在主程序中分別插入10000條記錄,比較各自所需要的時間。
先介紹下:
- MAD:
- multiply-add-divide method,乘法 - 加法 - 除法(取模),如下這個公式是散列核心公式
- (a*collisionIndex+b)%M, M要求是素數,a, b的取值要合理
- 沖突解決方式:
- 多槽位
- 當計算出的Index位置處已經被占用后,還是會在這個index的地方增加一個元素
- 主數組的每個元素是個列表(比如每個元素都能放5個子元素),因此每個位置都能放多個元素
- 獨立鏈
- 基本同上
- 區別:主數組的每個元素對應的是一個鏈表
- 線性探測法
- 主數組只有一層,也就是每個數組元素只有一個空位,沒有子列表
- 如果計算出的Index位置處已經被占用,就自動往后面查找,直到找到一個沒有被占用的位置,把元素放這個空位中
- 多槽位
code:
class Program { static void Main(string[] args) { Timing t = new Timing(); Random rnd = new Random(DateTime.Now.Second); MyHash_MAD_多槽位 hash = new MyHash_MAD_多槽位(); t.Start(); for (var i = 0; i < 10000; i++) { string key = string.Format("{0}-{1}", rnd.Next(0, 9999), rnd.Next(0, 9999)); hash[key] = i; } t.Stop(); t.Display("MyHash_MAD_多槽位: "); hash.DisplayEmptyRatio(); Console.WriteLine(); rnd = new Random(DateTime.Now.Second); MyHash_MAD_獨立鏈 hash2 = new MyHash_MAD_獨立鏈(); t.Start(); for (var i = 0; i < 10000; i++) { string key = string.Format("{0}-{1}", rnd.Next(0, 9999), rnd.Next(0, 9999)); hash2[key] = i; } t.Stop(); t.Display("MyHash_MAD_獨立鏈: "); hash2.DisplayEmptyRatio(); Console.WriteLine(); rnd = new Random(DateTime.Now.Second); MyHash_MAD_線性探測法 hash3 = new MyHash_MAD_線性探測法(); t.Start(); for (var i = 0; i < 10000; i++) { string key = string.Format("{0}-{1}", rnd.Next(0, 9999), rnd.Next(0, 9999)); hash3[key] = i; } t.Stop(); t.Display("MyHash_MAD_線性探測法: "); hash3.DisplayEmptyRatio(); Console.WriteLine("done."); Console.ReadKey(); } } class MyHash_MAD_多槽位 { private const int defaultSize = 10001; private List<List<Tuple<string, object>>> lstArray = new List<List<Tuple<string, object>>>(defaultSize); public MyHash_MAD_多槽位() { int i = lstArray.Capacity; while(i>0) { lstArray.Add(new List<Tuple<string,object>>()); i--; } } public object this[string key] { get { EnsureNotNull(key); List<Tuple<string, object>> lst; Tuple<string, object> obj = FindByKey(key, out lst); if (obj == null) throw new Exception("Key不存在"); return obj.Item2; } set { EnsureNotNull(key); List<Tuple<string, object>> lst; Tuple<string, object> obj = FindByKey(key, out lst); if (obj!=null) lst.Remove(obj); lst.Add(new Tuple<string, object>(key, value)); } } private Tuple<string, object> FindByKey(string key, out List<Tuple<string, object>> lst) { int hashIndex = MapString2Int(key); lst = lstArray[hashIndex]; Tuple<string, object> obj = null; for (var i = 0; i < lst.Count; i++) { if (lst[i].Item1 == key) { obj = lst[i]; break; } } return obj; } private static void EnsureNotNull(string key) { if (key == null || key.Trim().Length == 0) throw new Exception("Key不能為空"); } private int MapString2Int(string key) { int hashIndex=0; char[] keyAry = key.ToCharArray(); foreach (var c in keyAry) hashIndex += (int)c; hashIndex = (31 * hashIndex + 2) % lstArray.Capacity; return hashIndex; } public void DisplayFlags() { foreach (var item in lstArray) Console.Write(string.Format("{0}, ", item.Count)); } public void DisplayEmptyRatio() { float emptyCount = 0; foreach (var item in lstArray) if (item.Count == 0) emptyCount++; string msg = string.Format("空值個數:{1}/{2}\n有值個數:{3}\n空值比例:{0}%", emptyCount / lstArray.Capacity * 100, emptyCount, lstArray.Capacity, lstArray.Capacity-emptyCount); Console.WriteLine(msg); } } class MyHash_MAD_獨立鏈 { class HashNode { public string Key { get; set; } public object Value { get; set; } } private const int defaultSize = 10001; //private List<LinkedList<HashNode>> lstArray = new List<LinkedList<HashNode>>(defaultSize); private LinkedList<HashNode>[] lstArray = new LinkedList<HashNode>[defaultSize]; public MyHash_MAD_獨立鏈() { int i = defaultSize; while (i > 0) { lstArray[i - 1] = new LinkedList<HashNode>(); i--; } } public object this[string key] { get { EnsureNotNull(key); LinkedList<HashNode> lst; HashNode obj = FindByKey(key, out lst); if (obj == null) throw new Exception("Key不存在"); return obj.Value; } set { EnsureNotNull(key); LinkedList<HashNode> lst; HashNode obj = FindByKey(key, out lst); if (obj != null) lst.Remove(obj); lst.AddLast(new HashNode() { Key=key, Value=value}); } } private HashNode FindByKey(string key, out LinkedList<HashNode> lst) { int hashIndex = MapString2Int(key); lst = lstArray[hashIndex]; HashNode obj = lst.FirstOrDefault(t => t.Key == key); if (obj != null && !string.IsNullOrEmpty(obj.Key)) return obj; return null; } private static void EnsureNotNull(string key) { if (key == null || key.Trim().Length == 0) throw new Exception("Key不能為空"); } private int MapString2Int(string key) { int hashIndex = 0; char[] keyAry = key.ToCharArray(); foreach (var c in keyAry) hashIndex += (int)c; hashIndex = (31 * hashIndex + 2) % defaultSize; return hashIndex; } public void DisplayFlags() { foreach (var item in lstArray) Console.Write(string.Format("{0}, ", item.Count)); } public void DisplayEmptyRatio() { float emptyCount = 0; foreach (var item in lstArray) if (item.Count == 0) emptyCount++; string msg = string.Format("空值個數:{1}/{2}\n有值個數:{3}\n空值比例:{0}%", emptyCount / defaultSize * 100, emptyCount, defaultSize, defaultSize - emptyCount); Console.WriteLine(msg); } } class MyHash_MAD_線性探測法 { private const int defaultSize = 10001; private List<List<Tuple<string, object>>> lstArray = new List<List<Tuple<string, object>>>(defaultSize); public MyHash_MAD_線性探測法() { int i = lstArray.Capacity; while (i > 0) { lstArray.Add(new List<Tuple<string, object>>()); i--; } } public object this[string key] { get { EnsureNotNull(key); Tuple<string, object> obj = FindElement(key); if (obj == null) throw new Exception("Key不存在"); return obj.Item2; } set { EnsureNotNull(key); List<Tuple<string, object>> lst; Tuple<string, object> obj = FindNextEmptyPlace(key, out lst); if (obj != null) lst.Remove(obj); lst.Add(new Tuple<string, object>(key, value)); } } private Tuple<string, object> FindElement(string key) { int hashIndex = MapString2Int(key); while (true) { hashIndex = hashIndex % lstArray.Capacity; if (lstArray[hashIndex].Count > 0) { List<Tuple<string, object>> lst = lstArray[hashIndex]; if (lst[0].Item1 == key) { return lst[0]; break; } } hashIndex++; } } private Tuple<string, object> FindNextEmptyPlace(string key, out List<Tuple<string, object>> lst) { int hashIndex = MapString2Int(key); while (true) { hashIndex = hashIndex % lstArray.Capacity; if (lstArray[hashIndex].Count == 0) break; hashIndex++; } lst = lstArray[hashIndex]; Tuple<string, object> obj = null; for (var i = 0; i < lst.Count; i++) { if (lst[i].Item1 == key) { obj = lst[i]; break; } } return obj; } private static void EnsureNotNull(string key) { if (key == null || key.Trim().Length == 0) throw new Exception("Key不能為空"); } private int MapString2Int(string key) { int hashIndex = 0; char[] keyAry = key.ToCharArray(); foreach (var c in keyAry) hashIndex += (int)c; hashIndex = (31 * hashIndex + 2) % lstArray.Capacity; return hashIndex; } public void DisplayFlags() { foreach (var item in lstArray) Console.Write(string.Format("{0}, ", item.Count)); } public void DisplayEmptyRatio() { float emptyCount = 0; foreach (var item in lstArray) if (item.Count == 0) emptyCount++; string msg = string.Format("空值個數:{1}/{2}\n有值個數:{3}\n空值比例:{0}%", emptyCount / lstArray.Capacity * 100, emptyCount, lstArray.Capacity, lstArray.Capacity - emptyCount); Console.WriteLine(msg); } }
線性探測法的空間占用率最高,幾乎沒有空位,但是耗費的時間最多,主要是查找I/O效率低。
多槽位性能最好。