顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O( ),搜索的效率取决于搜索过程中 元素的比较次数。 理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。 如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。 当向该结构中:
插入元素 根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放 搜索元素 对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功
该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(HashTable)(或者称散列表) 例如:数据集合{1,7,6,4,5,9}; 哈希函数设置为:hash(key) = key % capacity; capacity为存储元素底层空间总的大小。

用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快 问题:按照上述哈希方式,向集合中插入元素44,会出现什么问题?
对于两个数据元素的关键字 和 (i != j),有 != ,但有:Hash( ) == Hash( ),即:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。 把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”
首先,我们需要明确一点,由于我们哈希表底层数组的容量往往是小于实际要存储的关键字的数量的,这就导致一个问题,冲突的发生是必然的,但我们能做的应该是尽量的降低冲突率
引起哈希冲突的一个原因可能是:哈希函数设计不够合理。 哈希函数设计原则: 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间 哈希函数计算出来的地址能均匀分布在整个空间中 哈希函数应该比较简单
常见哈希函数
class Solution {
public int firstUniqChar(String s) {
int[] count = new int[26];
for(int i = 0;i < s.length();i++){
char ch = s.charAt(i);
count[ch-97]++;
}
for(int i = 0;i < s.length();i++){
char ch = s.charAt(i);
if(count[ch - 97] == 1){
return i;
}
}
return -1;
}
}

所以当冲突率达到一个无法忍受的程度时,我们需要通过降低负载因子来变相的降低冲突率。 已知哈希表中已有的关键字个数是不可变的,那我们能调整的就只有哈希表中的数组的大小。
闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。那如何寻找下一个空位置呢?

插入 通过哈希函数获取待插入元素在哈希表中的位置 如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素
采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。比如删除元素4,如果直接删除掉,44查找起来可能会受影响。因此线性探测采用标记的伪删除法来删除一个元素。
开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。

从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素。 开散列,可以认为是把一个在大集合中的搜索问题转化为在小集合中做搜索了。
public class HashBucket {
private static class Node{
public int key;
public int val;
public Node next;
public Node(int key, int val) {
this.key = key;
this.val = val;
}
}
private Node[] array = new Node[10];
private int usedSize;
private static final double MAX_LOAD_FACTOR = 0.75;
public void push(int key,int val) {
int index = key % array.length;
Node cur = array[index];
while(cur != null){
if(cur.key == key){
cur.val = val;
return;
}
cur = cur.next;
}
Node node = new Node(key,val);
node.next = array[index];
array[index] = node;
usedSize++;
if(doLoadFactor() >= MAX_LOAD_FACTOR){
resize();
}
}
private void resize() {
Node[] newArray = new Node[array.length*2];
for (int i = 0; i < array.length; i++) {
Node cur = array[i];
while(cur != null){
int index = cur.key% newArray.length;
Node curN = cur.next;
cur.next = newArray[index];
newArray[index] = cur;
cur = curN;
}
}
array = newArray;
}
private double doLoadFactor() {
return usedSize*0.1 / array.length;
}
public int getVal(int key) {
int index = key%array.length;
Node cur = array[index];
while(cur != null){
if(cur.key == key){
return cur.val;
}
cur = cur.next;
}
return -1;
}
}然而,我们设置了key值为int整形,所以非常好比较。那如果遇到的是没法直接比较的对象该怎么做呢?请看下面的代码:
public class HashBucket2<K,V> {
private static class Node<K,V>{
public K key;
public V val;
public Node<K,V> next;
public Node(K key,V val){
this.key = key;
this.val = val;
}
}
public Node<K,V>[] array = (Node<K,V>[])new Node[10];
private int usedSize;
private static final double MAX_LOAD_FACTOR = 0.75;
public void push(K key,V val) {
int hashCode = key.hashCode();
int index = hashCode % array.length;
Node<K,V> cur = array[index];
while(cur != null){
if(cur.key.equals(key)){
cur.val = val;
return;
}
cur = cur.next;
}
Node<K,V> node = new Node<>(key,val);
node.next = array[index];
array[index] = node;
usedSize++;
if(doLoadFactor() >= MAX_LOAD_FACTOR){
resize();
}
}
private void resize() {
Node<K,V>[] newArray = (Node<K, V>[]) new Node[array.length*2];
for (int i = 0; i < array.length; i++) {
Node<K,V> cur = array[i];
while(cur != null){
int hashCode = cur.key.hashCode();
int index = hashCode % newArray.length;
Node<K, V> curN = cur.next;
cur.next = newArray[index];
newArray[index] = cur;
cur = curN;
}
}
array = newArray;
}
private double doLoadFactor() {
return usedSize*0.1 / array.length;
}
public V getVal(K key) {
int hashCode = key.hashCode();
int index = hashCode%array.length;
Node<K,V> cur = array[index];
while(cur != null){
if(cur.key.equals(key)){
return cur.val;
}
cur = cur.next;
}
return null;
}
}复杂链表的复制 https://leetcode.cn/problems/copy-list-with-random-pointer/
public Node copyRandomList(Node head) {
Node cur = head;
Map<Node,Node> map = new HashMap<>();
while(cur != null){
Node node = new Node(cur.val);
map.put(cur,node);
cur = cur.next;
}
cur = head;
while(cur != null){
map.get(cur).next = map.get(cur.next);
map.get(cur).random = map.get(cur.random);
cur = cur.next;
}
return map.get(head);
}前k个高频单词 https://leetcode.cn/problems/top-k-frequent-words/description/
class Solution {
public List<String> topKFrequent(String[] words, int k) {
Map<String,Integer> map = new HashMap<>();
for(String s : words){
if(!map.containsKey(s)){
map.put(s,1);
}else{
int val = map.get(s);
map.put(s,val+1);
}
}
PriorityQueue<Map.Entry<String,Integer>> minHeap = new PriorityQueue<>(new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
if(o1.getValue().compareTo(o2.getValue()) == 0){
return o2.getKey().compareTo(o1.getKey());
}
return o1.getValue().compareTo(o2.getValue());
}
});
for(Map.Entry<String,Integer> entry: map.entrySet()){
if(minHeap.size() < k){
minHeap.offer(entry);
}else{
Map.Entry<String,Integer> peek = minHeap.peek();
if(peek.getValue() < entry.getValue()){
minHeap.poll();
minHeap.offer(entry);
} else if (peek.getValue().equals(entry.getValue())) {
if(entry.getKey().compareTo(peek.getKey()) < 0){
minHeap.poll();
minHeap.offer(entry);
}
}
}
}
ArrayList<String> array = new ArrayList<>();
for (int i = 0; i < k ; i++) {
array.add(minHeap.poll().getKey());
}
Collections.reverse(array);
return array;
}
}坏键盘打字 https://www.nowcoder.com/questionTerminal/f88dafac00c8431fa363cd85a37c2d5e
import java.util.*;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String a = null; // 在循环外声明变量
String b = null;
while (in.hasNextLine()) {
a = in.nextLine(); // 循环内赋值
b = in.nextLine();
}
func(a, b);
}
public static void func(String s1,String s2){
s1 = s1.toUpperCase();
s2 = s2.toUpperCase();
HashSet<Character> set = new HashSet<>();
for(int i = 0; i< s2.length();i++){
set.add(s2.charAt(i));
}
HashSet<Character> flg = new HashSet<>();
for(int i = 0;i< s1.length();i++){
if(!set.contains(s1.charAt(i))&&!flg.contains(s1.charAt(i))){
flg.add(s1.charAt(i));
System.out.print(s1.charAt(i));
}
}
}
}宝石与石头 https://leetcode.cn/problems/jewels-and-stones/
class Solution {
public int numJewelsInStones(String jewels, String stones) {
HashSet<Character> set = new HashSet<>();
for(int i = 0;i < jewels.length();i++){
set.add(jewels.charAt(i));
}
int count = 0;
for(int i = 0;i< stones.length();i++){
if(set.contains(stones.charAt(i)) == true){
count++;
}
}
return count;
}
}只出现一次的数字 https://leetcode.cn/problems/single-number/
class Solution {
public int singleNumber(int[] nums) {
int ret = 0;
for(int num : nums){
ret = ret ^ num;
}
return ret;
}
}