【数据结构】哈希表

本文参考了https://blog.csdn.net/pcwl1206/article/details/83582986 。对哈希表知识点进行了梳理。感觉有一定难度。

一、哈希表:

1、基本定义:

        散列表(Hash  Table,又叫哈希表),是根据关键码值(Key  Value)而直接进行访问的数据结构。

2、使用哈希表的查找算法:

  1. 使用哈希函数:将被查找的键转化为数组的索引
  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 

字符串

https://blog.csdn.net/volcano1995/article/details/88355084

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
是一种基于哈函数进行快速查找的数据结构,它通过把关键码值映射到中一个位置来访问记录,以加快查找的速度。哈的设计思路如下: 1. 哈函数的设计:哈函数是哈的核心,它将关键字映射到哈中的位置。一个好的哈函数应该具有以下特点: - 映射范围广:哈函数应该将关键字均匀地映射到哈中的位置,避免出现大量的哈冲突。 - 计算速度快:哈函数的计算速度应该尽可能快,以提高哈的访问速度。 - 低冲突率:哈函数应该尽可能地避免哈冲突,以提高哈的访问效率。 2. 哈冲突的解决:由于哈函数的映射范围是有限的,所以不同的关键字可能会映射到同一个位置,这就是哈冲突。哈冲突的解决方法有以下两种: - 链地址法:将哈中的每个位置都连接一个链,当发生哈冲突时,将新的关键字插入到链的末尾。 - 开放地址法:当发生哈冲突时,通过某种算法找到哈中的下一个空位置,将新的关键字插入到该位置。 3. 哈的增删查改操作:哈的增删查改操作都需要先通过哈函数找到关键字在哈中的位置,然后再进行相应的操作。具体操作如下: - 插入操作:将新的关键字插入到哈中的对应位置,如果发生哈冲突,则按照链地址法或开放地址法进行解决。 - 删除操作:将关键字从哈中对应位置删除,如果该位置上有链,则需要遍历链找到对应的关键字进行删除。 - 查找操作:通过哈函数找到关键字在哈中的位置,如果该位置上有链,则需要遍历链找到对应的关键字进行查找。 - 修改操作:通过哈函数找到关键字在哈中的位置,如果该位置上有链,则需要遍历链找到对应的关键字进行修改。 下面是一个使用链地址法实现的哈的Python代码示例: ```python class ListNode: def __init__(self, key=None, value=None): self.key = key self.value = value self.next = None class MyHashMap: def __init__(self): self.size = 1000 self.table = [None] * self.size def _hash(self, key): return key % self.size def put(self, key, value): index = self._hash(key) if not self.table[index]: self.table[index] = ListNode(key, value) else: node = self.table[index] while node: if node.key == key: node.value = value return if not node.next: break node = node.next node.next = ListNode(key, value) def get(self, key): index = self._hash(key) node = self.table[index] while node: if node.key == key: return node.value node = node.next return -1 def remove(self, key): index = self._hash(key) node = prev = self.table[index] if not node: return if node.key == key: self.table[index] = node.next else: node = node.next while node: if node.key == key: prev.next = node.next break node, prev = node.next, prev.next ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值