当前位置:   article > 正文

java数据结构和算法(哈希表)_private dataitem nonitem;

private dataitem nonitem;

什么是哈希表?

哈希表是一种数据结构,提供快速的插入和查找操作。

优点:

  • 插入、查找、删除的时间级为O(1);

  • 数据项占哈希表长的一半,或者三分之二时,哈希表的性能最好。

缺点:

  • 基于数组,数组创建后难于扩展,某些哈希表被基本填满时性能下降的非常严重;
  • 没有一种简单的方法可以以任何一种顺序(如从小到大)遍历整个数据项;

用途:

  • 不需要遍历数据并且可以提前预测数据量的大小,此时哈希表的速度和易用性无与伦比。

哈希化

就是把关键字转化为数组的下标,在哈希表中通过哈希函数来完成。对于某些关键字并不需要哈希函数进行哈希化,如员工编号。

哈希函数:作用是将大的多整数范围转化为数组的下标范围。一般通过取余%来完成。key%arraySize。

冲突:哈希函数不能保证每个每个关键字都映射到数组的空白元素的位置。解决冲突的方法分为:开放地址法和链地址法。

开放地址法

若数据不能直接放在由哈希函数计算出来的数组下标所致的单元时,就要寻找数组的其他位置。该方法会发生聚集,那些哈希化后的落在聚集内的数据项,都要一步一步的移动,并且插入在聚集的最后,因此使聚集越来越大。聚集越大,它增长的越快。

线性探测

在线性探测中,线性的查找空白单元,如52的位置被占用了,就找53、54。。。的位置直到找到空位。

聚集:一串连续的已填充单元叫做填充序列。增加越来越多的数据项时,填充序列变的越来越长,这就叫聚集。

如果把哈希表填的太满,那么在表中每填一个数据都要花费很长的时间。

  1. package cn.xyc.dataStructure.hash;
  2. /**
  3. *
  4. * 描述:哈希表中存储的数据项
  5. *
  6. * <pre>
  7. * HISTORY
  8. * ****************************************************************************
  9. * ID DATE PERSON REASON
  10. * 1 2016年10月2日 80002253 Create
  11. * ****************************************************************************
  12. * </pre>
  13. *
  14. * @author 蒙奇·d·许
  15. * @since 1.0
  16. */
  17. public class DataItem {
  18. private int item;// 数据项key
  19. public DataItem(int item) {
  20. this.item = item;
  21. }
  22. public int getKey() {
  23. return item;
  24. }
  25. }


  1. package cn.xyc.dataStructure.hash;
  2. /**
  3. *
  4. * 描述:使用线性探测法实现哈希表
  5. *
  6. * <pre>
  7. * HISTORY
  8. * ****************************************************************************
  9. * ID DATE PERSON REASON
  10. * 1 2016年10月2日 80002253 Create
  11. * ****************************************************************************
  12. * </pre>
  13. *
  14. * @author 蒙奇·d·许
  15. * @since 1.0
  16. */
  17. public class Hash {
  18. // 保存哈希表中的数据
  19. private DataItem[] hashArray;
  20. // 哈希表的长度
  21. private int arraySize;
  22. // 可以被删除的哈希项
  23. private DataItem nonItem;
  24. /**
  25. * 初始化哈希表
  26. *
  27. * @param size
  28. */
  29. public Hash(int size) {
  30. arraySize = size;
  31. hashArray = new DataItem[arraySize];
  32. nonItem = new DataItem(-1);// 被删除的项为-1
  33. }
  34. // /
  35. /**
  36. * 遍历哈希表
  37. */
  38. public void displayTable() {
  39. System.out.print("Table: ");
  40. for (int i = 0; i < arraySize; i++) {
  41. if (hashArray[i] != null) {
  42. System.out.print(hashArray[i].getKey() + "\t");
  43. } else {
  44. System.out.print("**\t");
  45. }
  46. }
  47. System.out.println("");
  48. }
  49. // /
  50. /**
  51. * 哈希函数
  52. *
  53. * @param key
  54. * @return
  55. */
  56. public int hashFunc(int key) {
  57. return key % arraySize;
  58. }
  59. // //
  60. public void insert(DataItem item) {
  61. int key = item.getKey();
  62. int hashVal = hashFunc(key);
  63. while (hashArray[hashVal] != null && hashArray[hashVal].getKey() != -1) {
  64. ++hashVal;// 直到找到空白元素
  65. hashVal %= arraySize;// 保证不越界
  66. }
  67. // 插入数据
  68. hashArray[hashVal] = item;
  69. }
  70. // //
  71. public DataItem delete(int key) {
  72. int hashVal = hashFunc(key);
  73. while (hashArray[hashVal] != null) {
  74. if (hashArray[hashVal].getKey() == key) {
  75. DataItem temp = hashArray[hashVal];
  76. hashArray[hashVal] = nonItem;
  77. return temp;
  78. }
  79. ++hashVal;
  80. hashVal %= arraySize;
  81. }
  82. return null;
  83. }
  84. //
  85. public DataItem find(int key) {
  86. int hashVal = hashFunc(key);
  87. while (hashArray[hashVal] != null) {
  88. if (hashArray[hashVal].getKey() == key) {
  89. return hashArray[hashVal];
  90. } else {
  91. ++hashVal;
  92. hashVal %= arraySize;
  93. }
  94. }
  95. return null;
  96. }
  97. // //
  98. /**
  99. * 得到一个长度为质数的数组长度
  100. *
  101. * @param min
  102. * @return
  103. */
  104. @SuppressWarnings("unused")
  105. private int getPrime(int min) {
  106. for (int i = 0; true; i++) {
  107. if (isPrime(i)) {
  108. return i;
  109. }
  110. }
  111. }
  112. // ///
  113. /**
  114. * 判断一个数是否为质数
  115. *
  116. * @param n
  117. * @return
  118. */
  119. private boolean isPrime(int n) {
  120. for (int i = 2; i * i < n; i++) {
  121. if (n % i == 0) {
  122. return false;
  123. }
  124. }
  125. return true;
  126. }
  127. }





扩展数组
哈希函数是根据数组的大小来计算给定数据项的大小的,所以不能简单的从一个数组向另一个数组拷贝数据。需要按照遍历老数组,用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);

  1. package cn.xyc.dataStructure.hash;
  2. /**
  3. *
  4. * 描述:使用再哈希法实现哈希表 <br/>
  5. * 要求:表的容量是一个质数,否则再哈希时可能会取到0导致算法崩溃。
  6. *
  7. * <pre>
  8. * HISTORY
  9. * ****************************************************************************
  10. * ID DATE PERSON REASON
  11. * 1 2016年10月2日 80002253 Create
  12. * ****************************************************************************
  13. * </pre>
  14. *
  15. * @author 蒙奇·d·许
  16. * @since 1.0
  17. */
  18. public class HashDouble {
  19. // 保存哈希表中的数据
  20. private DataItem[] hashArray;
  21. // 哈希表的长度
  22. private int arraySize;
  23. // 可以被删除的哈希项
  24. private DataItem nonItem;
  25. /**
  26. * 初始化哈希表
  27. *
  28. * @param size
  29. */
  30. public HashDouble(int size) {
  31. arraySize = size;
  32. hashArray = new DataItem[arraySize];
  33. nonItem = new DataItem(-1);// 被删除的项为-1
  34. }
  35. // /
  36. /**
  37. * 遍历哈希表
  38. */
  39. public void displayTable() {
  40. System.out.print("Table: ");
  41. for (int i = 0; i < arraySize; i++) {
  42. if (hashArray[i] != null) {
  43. System.out.print(hashArray[i].getKey() + "\t");
  44. } else {
  45. System.out.print("**\t");
  46. }
  47. }
  48. System.out.println("");
  49. }
  50. // //
  51. public void insert(DataItem item) {
  52. int key = item.getKey();
  53. int hashVal = hashFunc1(key);
  54. int stepSize = hashFunc2(key);
  55. while (hashArray[hashVal] != null && hashArray[hashVal].getKey() != -1) {
  56. hashVal += stepSize;// 直到找到空白元素
  57. hashVal %= arraySize;// 保证不越界
  58. }
  59. // 插入数据
  60. hashArray[hashVal] = item;
  61. }
  62. // //
  63. public DataItem delete(int key) {
  64. int hashVal = hashFunc1(key);
  65. int stepSize = hashFunc2(key);
  66. while (hashArray[hashVal] != null) {
  67. if (hashArray[hashVal].getKey() == key) {
  68. DataItem temp = hashArray[hashVal];
  69. hashArray[hashVal] = nonItem;
  70. return temp;
  71. }
  72. hashVal += stepSize;
  73. hashVal %= arraySize;
  74. }
  75. return null;
  76. }
  77. //
  78. public DataItem find(int key) {
  79. int hashVal = hashFunc1(key);
  80. int stepSize = hashFunc2(key);
  81. while (hashArray[hashVal] != null) {
  82. if (hashArray[hashVal].getKey() == key) {
  83. return hashArray[hashVal];
  84. } else {
  85. hashVal += stepSize;
  86. hashVal %= arraySize;
  87. }
  88. }
  89. return null;
  90. }
  91. // //
  92. /**
  93. * 得到一个长度为质数的数组长度
  94. *
  95. * @param min
  96. * @return
  97. */
  98. @SuppressWarnings("unused")
  99. private int getPrime(int min) {
  100. for (int i = 0; true; i++) {
  101. if (isPrime(i)) {
  102. return i;
  103. }
  104. }
  105. }
  106. // /
  107. /**
  108. * 哈希函数
  109. *
  110. * @param key
  111. * @return
  112. */
  113. public int hashFunc1(int key) {
  114. return key % arraySize;
  115. }
  116. // /
  117. /**
  118. * 再哈希函数:生成步长
  119. *
  120. * @param key
  121. * @return
  122. */
  123. public int hashFunc2(int key) {
  124. // 再哈希的余数必须是一个小于数组长度,不和hF1一样
  125. // 不能为为零
  126. // 是一个质数
  127. return 5 - (key % 5);
  128. }
  129. // ///
  130. /**
  131. * 判断一个数是否为质数
  132. *
  133. * @param n
  134. * @return
  135. */
  136. private boolean isPrime(int n) {
  137. for (int i = 2; i * i < n; i++) {
  138. if (n % i == 0) {
  139. return false;
  140. }
  141. }
  142. return true;
  143. }
  144. }


链地址法

在哈希表中每个单元中设置链表。某个数据项的关键字还是像通常一样映射到哈希表的单元,而数据项本身插入到这个单元的链表中。其他同样映射到这个位置的数据项只需要加入到链表中,不需要在原始数组中寻找空位。

优缺点:

有序链表不能加快成功的搜索,但可以减少不成功搜索中的一半的时间。

删除的时间级也减少一半

插入的时间延长,因为数据项不能只插在表头;插入前必须找到有序表中的正确位置。

  1. package cn.xyc.dataStructure.hash;
  2. /**
  3. *
  4. * 描述:链表中的节点
  5. *
  6. * <pre>
  7. * HISTORY
  8. * ****************************************************************************
  9. * ID DATE PERSON REASON
  10. * 1 2016年10月2日 80002253 Create
  11. * ****************************************************************************
  12. * </pre>
  13. *
  14. * @author 蒙奇·D·许
  15. * @since 1.0
  16. */
  17. public class LinkItem {
  18. private int data;// 链表中的数据项
  19. public LinkItem nextLink;// 下一个节点
  20. public LinkItem(int data) {
  21. this.data = data;
  22. }
  23. public int getKey() {
  24. return data;
  25. }
  26. public void dispalyLink() {
  27. System.out.println(data + "\t");
  28. }
  29. }

  1. package cn.xyc.dataStructure.hash;
  2. /**
  3. *
  4. * 描述:从小到大的有序链表
  5. *
  6. * <pre>
  7. * HISTORY
  8. * ****************************************************************************
  9. * ID DATE PERSON REASON
  10. * 1 2016年10月2日 80002253 Create
  11. * ****************************************************************************
  12. * </pre>
  13. *
  14. * @author 蒙奇·d·许
  15. * @since 1.0
  16. */
  17. public class SortLink {
  18. // 链表的第一个元素
  19. private LinkItem first;
  20. //
  21. public SortLink() {
  22. first = null;
  23. }
  24. // /
  25. public void insert(LinkItem theLink) {
  26. int key = theLink.getKey();
  27. LinkItem previousItem = null;
  28. // 从第一个开始
  29. LinkItem currentItem = first;
  30. while (currentItem != null && key > currentItem.getKey()) {
  31. previousItem = currentItem;
  32. currentItem = currentItem.nextLink;
  33. }
  34. if (previousItem == null) {
  35. first = theLink;
  36. } else {
  37. previousItem.nextLink = theLink;
  38. theLink.nextLink = currentItem;
  39. }
  40. }
  41. // /
  42. public void delete(int key) {
  43. LinkItem previous = null;
  44. LinkItem current = first;
  45. while (current != null && current.getKey() != key) {
  46. previous = current;
  47. current = current.nextLink;
  48. }
  49. if (previous == null) {
  50. // 说明是第一个
  51. if (first != null) {
  52. first = first.nextLink;
  53. }
  54. } else {
  55. previous.nextLink = current.nextLink;
  56. }
  57. }
  58. //
  59. public LinkItem find(int key) {
  60. LinkItem currentItem = first;
  61. while (currentItem != null && currentItem.getKey() <= key) {
  62. if (currentItem.getKey() == key) {
  63. return currentItem;
  64. }
  65. currentItem = currentItem.nextLink;
  66. }
  67. return null;
  68. }
  69. ///
  70. public void displayList(){
  71. LinkItem currentItem=first;
  72. while(currentItem!=null){
  73. currentItem.dispalyLink();
  74. currentItem=currentItem.nextLink;
  75. }
  76. System.out.println();
  77. }
  78. }

  1. package cn.xyc.dataStructure.hash;
  2. /**
  3. *
  4. * 描述:使用链地址法实现哈希表
  5. *
  6. * <pre>
  7. * HISTORY
  8. * ****************************************************************************
  9. * ID DATE PERSON REASON
  10. * 1 2016年10月2日 80002253 Create
  11. * ****************************************************************************
  12. * </pre>
  13. *
  14. * @author 蒙奇·d·许
  15. * @since 1.0
  16. */
  17. public class HashLink {
  18. // 保存哈希表中的数据
  19. private SortLink[] hashArray;
  20. // 哈希表的长度
  21. private int arraySize;
  22. // 可以被删除的哈希项
  23. private SortLink nonItem;
  24. /**
  25. * 初始化哈希表
  26. *
  27. * @param size
  28. */
  29. public HashLink(int size) {
  30. arraySize = size;
  31. hashArray = new SortLink[arraySize];
  32. // 填充数组
  33. for (int i = 0; i < hashArray.length; i++) {
  34. hashArray[i] = new SortLink();
  35. }
  36. }
  37. // /
  38. /**
  39. * 遍历哈希表
  40. */
  41. public void displayTable() {
  42. System.out.print("Table: ");
  43. for (int i = 0; i < arraySize; i++) {
  44. if (hashArray[i] != null) {
  45. hashArray[i].displayList();
  46. } else {
  47. System.out.print("**\t");
  48. }
  49. }
  50. System.out.println("");
  51. }
  52. // /
  53. /**
  54. * 哈希函数
  55. *
  56. * @param key
  57. * @return
  58. */
  59. public int hashFunc(int key) {
  60. return key % arraySize;
  61. }
  62. // //
  63. public void insert(LinkItem item) {
  64. int key = item.getKey();
  65. int hashVal = hashFunc(key);
  66. // 插入数据
  67. hashArray[hashVal].insert(item);
  68. }
  69. // //
  70. public void delete(int key) {
  71. int hashVal = hashFunc(key);
  72. hashArray[hashVal].delete(key);
  73. }
  74. //
  75. public LinkItem find(int key) {
  76. int hashVal = hashFunc(key);
  77. LinkItem item = hashArray[hashVal].find(key);
  78. return item;
  79. }
  80. // //
  81. /**
  82. * 得到一个长度为质数的数组长度
  83. *
  84. * @param min
  85. * @return
  86. */
  87. @SuppressWarnings("unused")
  88. private int getPrime(int min) {
  89. for (int i = 0; true; i++) {
  90. if (isPrime(i)) {
  91. return i;
  92. }
  93. }
  94. }
  95. // ///
  96. /**
  97. * 判断一个数是否为质数
  98. *
  99. * @param n
  100. * @return
  101. */
  102. private boolean isPrime(int n) {
  103. for (int i = 2; i * i < n; i++) {
  104. if (n % i == 0) {
  105. return false;
  106. }
  107. }
  108. return true;
  109. }
  110. }






另一种方法类似于链接地址法,它是在哈希表的每个单元中使用数组,而不是链表。这样的数组称为桶。

缺点:桶的容量太小会溢出,太大浪费空间。



开放地址法和链地址法的比较:

项数未知:使用链地址法。







声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/775702
推荐阅读
相关标签
  

闽ICP备14008679号