本文参考了https://blog.csdn.net/pcwl1206/article/details/83582986 。对哈希表知识点进行了梳理。感觉有一定难度。
一、哈希表:
1、基本定义:
散列表(Hash Table,又叫哈希表),是根据关键码值(Key Value)而直接进行访问的数据结构。
2、使用哈希表的查找算法:
- 使用哈希函数:将被查找的键转化为数组的索引
- 处理哈希碰撞冲突(开放寻址法、拉链法)
二、哈希函数
1、基本定义:
hash(key),其中key表示元素的键值,hasn(key)的键表示经过散列函数计算得到的散列值。
2、散列函数设计的基本要求
(1)散列函数计算得到的散列值是一个非负整数;
(2)如果key1 = key2,那么hash(key1) == hash(key2);
(3)如果key1 != key2,那么hash(key1) != hash(key2)。
3、设计函数的准则
(1)散列函数的设计不能太复杂,过于复杂的散列函数,势必会消耗很多计算时间,也会间接的影响到散列表的性能;
(2)散列函数生成的值要尽可能随机且均匀分布,这样才能避免或者最小化散列冲突,而且即便出现冲突,散列到每个槽里的数据也会比较平均,不会再出现某个槽里数据特别多的情况。
三、哈希碰撞
解决散列冲突的方法有:开放寻址法(Open Addressing)和链表法(Chaining),
1、开放寻址法
开放寻址的核心思想是:如果出现了散列冲突,我们就重新探测一个空闲位置,将其插入。这里我们可以使用线性探测方法(Linear Probing)。
当我们往散列表中插入数据时,如果某个数据经过散列函数之后,存储位置已经被占用了,我们就从当前位置开始,依次往后查找,看是否有空闲位置,直到找到为止。
2、链表法
链表法是一种更加常用的散列冲突解决办法,相比较开放寻址法,它要简单很多。在散列表中,每个“桶(bucket)”或者“槽(slot)”会对应成一条链表,所有散列值相同的元素,都放到相同槽位对应的链表中。
class ChainingHashSet<K, V> {
private int num; // 当前散列表中的键值对总数
private int capacity; // 散列表的大小
private SeqSearchST<K, V>[] st; // 链表对象数组
// 构造函数
public ChainingHashSet(int initialCapacity){
capacity = initialCapacity;
st = (SeqSearchST<K, V>[]) new Object[capacity];
for(int i = 0; i < capacity; i++){
st[i] = new SeqSearchST<>();
}
}
// hash()方法
private int hash(K key){
return (key.hashCode() & 0x7fffffff) % capacity;
}
public V get(K key){
return st[hash(key)].get(key);
}
public void put(K key, V value){
st[hash(key)].put(key, value);
}
}
// SeqSearchST基于链表的符号表实现
class SeqSearchST<K, V>{
private Node first;
// 结点类
private class Node{
K key;
V value;
Node next;
// 构造函数
public Node(K key, V val, Node next){
this.key = key;
this.value = val;
this.next = next;
}
}
// get()方法
public V get(K key) {
for(Node node = first; node != null; node = node.next){
if(key.equals(node.key)){
return node.value;
}
}
return null;
}
// put()方法
public void put(K key, V value) {
// 先查找表中是否已经存在相应的key
Node node;
for(node = first; node != null; node = node.next){
if(key.equals(key)){
node.value = value; // 如果key存在,就把当前value插入node.next中
return;
}
}
// 表中不存在相应的key,直接插入表头
first = new Node(key, value, first);
}
}
leetCode
两数之和
package codingTest4;
import java.util.HashMap;
public class twoSum {
public static int[] twoSum(int[] numbers, int target) {
/**
* 一个简单的实现使用两个迭代。
* 第一次迭代中,我们将每个元素的值及其索引添加到表中。
* 第二次迭代中,看哈希表中有无对应的target-该元素的存在
* 并且要注意:target-该元素不能是该元素自身
*
**/
HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
for(int i = 0; i < numbers.length; i++) {
map.put(numbers[i], i);
}
for(int i = 0; i < numbers.length; i++) {
int complement = target - numbers[i];
if(map.containsKey(complement) && map.get(complement) != i) {
return new int[] {i, map.get(complement)};
}
}
return null;
}
// public static int[] twoSum(int[] numbers, int target) {
// int[] res = {0, 0};
// for(int i = 0; i < numbers.length; i++) {
// for(int j = i+1; j < numbers.length; j++) {
// if( numbers[i] + numbers[j] == target) {
// res[0] = i;
// res[1] = j;
// }
// }
// }
// return res;
// }
public static void main(String[] args) {
int[] list = {1, 2, 7, 8, 9};
int[] res = twoSum(list, 3);
for(int r : res) {
System.out.println(r);
}
}
}
快乐数
package codingTest4;
import java.util.HashSet;
/*
* *编写一个算法来判断一个数是不是“快乐数”。
* *一个“快乐数”定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,
* *然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1。
* *如果可以变为 1,那么这个数就是快乐数。
* */
public class HappyNumber {
public static boolean isHappy(int n) {
HashSet<Integer> set = new HashSet<>();
int num = sum(n);
while(!set.contains(num) && num != 1) {
set.add(num);
num = sum(num);
}
if(num == 1) {
return true;
}else
return false;
}
public static int sum(int n) {
int sum =0;
while(n != 0) {
sum += (n%10)*(n%10);
n= n/10;
}
return sum;
}
public static void main(String[] args) {
System.out.println(isHappy(36));
}
}
参考文献
https://blog.csdn.net/pcwl1206/article/details/83582986