首页 > 编程语言 >小白学算法: 哈希 - 数据结构和算法教程

小白学算法: 哈希 - 数据结构和算法教程

时间:2023-10-24 11:07:02浏览次数:37  
标签:11 哈希 数组 算法 小白学 arr1 数据结构 arr2

散列是指使用称为散列函数的数学公式从可变大小的输入生成固定大小的输出的过程。该技术确定数据结构中项目存储的索引或位置。

小白学算法: 哈希 - 数据结构和算法教程_数组

需要Hash数据结构

互联网上的数据每天都在成倍增加,有效存储这些数据始终是一个难题。在日常编程中,这些数据量可能不是那么大,但仍然需要轻松高效地存储、访问和处理。用于此目的的一种非常常见的数据结构是数组数据结构。

现在问题来了,如果数组已经存在,还需要一个新的数据结构吗!答案就在“效率”二字。虽然存储在数组中需要 O(1) 时间,但搜索至少需要 O(log n) 时间。这个时间看起来很小,但是对于大型数据集来说,它可能会导致很多问题,进而使数组数据结构效率低下。 所以现在我们正在寻找一种可以在恒定时间内(即 O(1) 时间)存储数据并在其中进行搜索的数据结构。这就是哈希数据结构发挥作用的方式。随着哈希数据结构的引入,现在可以轻松地在恒定时间内存储数据并在恒定时间内检索数据。

散列的组成部分

哈希主要包含三个组成部分:

  1. 键:键可以是任何字符串或整数,作为哈希函数的输入,该技术确定数据结构中项目存储的索引或位置。 
  2. 哈希函数:哈希函数接收输入键并返回称为哈希表的数组中元素的索引。该索引称为哈希索引
  3. 哈希表:哈希表是一种使用称为哈希函数的特殊函数将键映射到值的数据结构。哈希以关联方式将数据存储在数组中,其中每个数据值都有自己的唯一索引。

小白学算法: 哈希 - 数据结构和算法教程_Hash_02

散列的组成部分

哈希是如何工作的?

假设我们有一组字符串 {“ab”, “cd”, “efg”} 并且我们希望将其存储在表中。 

我们这里的主要目标是在 O(1) 时间内快速搜索或更新表中存储的值,并且我们不关心表中字符串的顺序。因此给定的一组字符串可以充当键,而字符串本身将充当字符串的值,但是如何存储与键对应的值呢? 

  • 步骤1:我们知道哈希函数(这是一些数学公式)用于计算哈希值,该哈希值充当存储该值的数据结构的索引。 
  • 第 2 步:那么,让我们分配 
  • “a”=1,
  • “b”=2,.. 等等,适用于所有字母字符。 
  • 步骤3:因此,字符串中所有字符相加得到的数值为: 
  • “ab” = 1 + 2 = 3, 
  • “CD” = 3 + 4 = 7 , 
  • “efg” = 5 + 6 + 7 = 18  
  • 步骤 4:现在,假设我们有一个大小为 7 的表来存储这些字符串。这里使用的哈希函数是key mod Table size中的字符之和。我们可以通过sum(string) mod 7来计算字符串在数组中的位置。
  • 第5步:所以我们将存储 
  • 3 mod 7 = 3 中的“ab”, 
  • 7 mod 7 = 0 中的“cd”,以及 
  • 18 mod 7 = 4 中的“efg”。

小白学算法: 哈希 - 数据结构和算法教程_数组_03

将键映射到数组的索引

上述技术使我们能够使用简单的哈希函数计算给定字符串的位置,并快速找到存储在该位置的值。因此,散列的想法似乎是在表中存储数据(键,值)对的好方法。

什么是哈希函数?

哈希函数创建键和值之间的映射,这是通过使用称为哈希函数的数学公式来完成的。散列函数的结果称为散列值或散列。哈希值是原始字符串的表示,但通常小于原始字符串。

例如:将数组视为 Map,其中键是索引,值是该索引处的值。因此,对于数组 A,如果我们有索引i,它将被视为键,那么我们只需查看 A[i] 处的值即可找到该值。
只需查找 A[i]。 

哈希函数的应用:

判断一个数组是否是另一个数组的子集

给定两个数组:arr1[0..m-1] 和 arr2[0..n-1]。判断 arr2[] 是否是arr1[] 的子集。两个数组都没有按顺序排列。可以假设两个数组中的元素是不同的。

例子: 

输入:arr1[] = {11, 1, 13, 21, 3, 7}, arr2[] = {11, 3, 7, 1} 
输出:arr2[] 是 arr1[] 的子集

输入:arr1[] = {1, 2, 3, 4, 5, 6}, arr2[] = {1, 2, 4} 
输出:arr2[] 是 arr1[] 的子集

输入arr1[] = {10, 5, 2, 23, 19}, arr2[] = {19, 5, 3} 

输出:arr2[] 不是 arr1[] 的子集 


解法:暴力解法

使用两个循环:外层循环一一选取 arr2[] 的所有元素。内循环线性搜索外循环选取的元素。如果找到所有元素则返回 1,否则返回 0。

下面是上述方法的实现:

#Python 3程序,用于查找一个数组是否是另一个数组的子集

#如果arr2 []是arr1 []的子集,则返回1

def isSubset(arr1, arr2, m, n):
	i = 0
	j = 0
	for i in range(n):
		for j in range(m):
			if(arr2[i] == arr1[j]):
				break

		# 如果上面的内部循环根本没有被中断,那么arr2[i]在arr1[]中不存在。
		if (j == m):
			return 0

	# 如果我们到达这里,那么所有 arr2[] 中的元素都存在 在 arr1[] 中
	return 1


if __name__ == "__main__":

	arr1 = [11, 1, 13, 21, 3, 7]
	arr2 = [11, 3, 7, 1]

	m = len(arr1)
	n = len(arr2)

	if(isSubset(arr1, arr2, m, n)):
		print("arr2[] is subset of arr1[] ")
	else:
		print("arr2[] is not a subset of arr1[]")

输出

arr2[] 是 arr1[] 的子集

复杂度分析

时间复杂度: O(m*n)
辅助空间: O(1)

使用排序和二分查找

这个想法是对给定的数组 arr1[] 进行排序,然后对 arr2[] 中的每个元素在排序的 arr1[] 中进行二分搜索。如果未找到该元素则返回 0。如果所有元素都存在则返回 1。

步骤:

给定数组arr1[] = { 11, 1, 13, 21, 3, 7 }arr2[] = { 11, 3, 7, 1 }

步骤 1:我们对数组 arr1[] 进行排序,得到 arr1[] = { 1, 3, 7, 11, 13, 21}。

步骤 2:我们将使用二分查找在 arr1[] 中查找 arr2[] 中的每个元素。

  • arr2[] = { 11 , 3, 7, 1 }, 11 出现在 arr1[] = { 1, 3, 7, 11 , 13, 21}中
  • arr2[] = { 11, 3 , 7, 1 }, 3 出现在 arr1[] = { 1, 3 , 7, 11, 13, 21}中
  • arr2[] = { 11, 3, 7 , 1 }, 7 出现在 arr1[] = { 1, 3, 7 , 11, 13, 21}中
  • arr2[] = { 11, 3, 7, 1 }, 1 出现在 arr1[] = { 1 , 3, 7, 11, 13, 21}中

当找到所有元素后,我们可以得出 arr2[] 是 arr1[] 的子集。

算法:

该算法非常简单。 

  • 对第一个数组 arr1[] 进行排序。
  • 在已排序的 arr1[] 中查找 arr2[] 的元素。
  • 如果我们遇到 arr2[] 中存在但 arr1[] 中不存在的特定值,则代码将终止,arr2[] 永远不可能是 arr1[] 的子集。
  • 否则 arr2[] 是 arr1[] 的子集。

下面是上述方法的代码实现:

def isSubset(arr1, arr2, m, n):
	i = 0

	quickSort(arr1, 0, m-1)
	for i in range(n):
		if (binarySearch(arr1, 0, m - 1, arr2[i]) == -1):
			return 0

	# 如果我们到达这里,那么所有元素
	# arr2[] 中的所有元素都存在于 arr1[] 中
	return 1


def binarySearch(arr, low, high, x):
	if(high >= low):
		mid = (low + high)//2

		if((mid == 0 or x > arr[mid-1]) and (arr[mid] == x)):
			return mid
		elif(x > arr[mid]):
			return binarySearch(arr, (mid + 1), high, x)
		else:
			return binarySearch(arr, low, (mid - 1), x)

	return -1


def partition(A, si, ei):
	x = A[ei]
	i = (si - 1)

	for j in range(si, ei):
		if(A[j] <= x):
			i += 1
			A[i], A[j] = A[j], A[i]
	A[i + 1], A[ei] = A[ei], A[i + 1]
	return (i + 1)

# 实现快速排序
# A[] --> 待排序数组
# si --> 起始索引
# ei --> 结束索引

def quickSort(A, si, ei):
	# 分区索引
	if(si < ei):
		pi = partition(A, si, ei)
		quickSort(A, si, pi - 1)
		quickSort(A, pi + 1, ei)


arr1 = [11, 1, 13, 21, 3, 7]
arr2 = [11, 3, 7, 1]

m = len(arr1)
n = len(arr2)

if(isSubset(arr1, arr2, m, n)):
	print("arr2[] is subset of arr1[] ")
else:
	print("arr2[] is not a subset of arr1[] ")

标签:11,哈希,数组,算法,小白学,arr1,数据结构,arr2
From: https://blog.51cto.com/demo007x/8001563

相关文章

  • 小白学数据结构和算法: 哈希数据结构实现原理
    使用哈希函数计算哈希值的复杂度时间复杂度:O(n)空间复杂度:O(1)哈希问题如果我们考虑上面的例子,我们使用的哈希函数是字母的总和,但是如果我们仔细检查哈希函数,那么问题可以很容易地可视化,对于不同的字符串,哈希函数开始生成相同的哈希值。 例如:{“ab”,“ba”}具有相同的哈希值,字符串......
  • 小白学算法-数据结构和算法教程: 使用开放寻址线性探测实现自己的哈希表
    Java中使用链接实现哈希表所有数据结构都有其自身的特点,例如,当需要快速搜索元素(在log(n)中)时,会使用BST。当需要在恒定时间内获取最小或最大元素时,使用堆或优先级队列。类似地,哈希表用于在恒定时间内获取、添加和删除元素。在继续实施方面之前,任何人都必须清楚哈希表的工作原理。因此......
  • 【基础算法】- 贪心
    贪心定义贪心算法适用于最优子结构问题。意思是问题在分解成子问题来解决时,子问题的最优解能递推到最终问题的最优解。常见的符合这种性质的问题如:「我们将XXX按照某某顺序排序,然后按某种顺序(例如从小到大)选择。」「我们每次都取XXX中最大/小的东西,并更新XXX。」但比......
  • 子序列相关算法
    1、最长公共子序列最长公共子序列(LongestCommonSubsequence,LCS)是动态规划中的经典问题,顾名思义,即求两个序列最长的公共子序列(可以不连续)。 1#include<iostream>2#include<string>3usingnamespacestd;//使用动态规划算法;分为两种情况4chara[102],b[102];......
  • md5算法实现
    前言md5算法是我们经常会用到的一个hash函数,虽然已经被证明是不安全的了,但其应用依然十分广泛.哈希函数具有如下特点:将任意长度的字符串映射为固定长度源数据微小的改动会导致结果差异巨大不可逆暴力破解困难你有没有好奇过,哈希函数是如何做到这些的呢?本文就拿m......
  • diff算法
    什么是Diff算法?Diff算法是Vue.js的一个核心特性,它是一种用于比较虚拟DOM树的差异,并最小化DOM操作的数量。当Vue.js检测到数据更改时,它会生成一个新的虚拟DOM树,并将其与旧虚拟DOM树进行比较。Diff算法会查找差异,并仅对需要更改的部分进行DOM操作。这种算法可以帮助我们在前端开发中......
  • 算法笔记(3)模拟退火
    原发表于个人博客=模拟退火的引入假如我们有一个函数,要求它的极大值,怎么求呢?如果这个函数满足单调性,可以用二分的方法。如果这是一个单谷(或单峰)函数,可以用三分法。那要是多峰函数怎么半呢?这时就可以用随机化算法。一种朴素的方法是:每次在当前找到的最优方案\(x\)附近寻找一......
  • 算法笔记(4)莫队算法入门
    原发表于我的博客前言本来想学完回滚莫队、树上莫队、二离莫队之后一起写一个博客,但是一直学不会/kk,只好把已会的普通莫队和带修莫队写了(以后会补上的)普通莫队莫队——优雅的暴力莫队算法的引入例题:给定一个数列和若干询问,每次询问询问一段区间内不同种类数字的个数。暴力......
  • 算法笔记(5)贪心算法
    原发表于我的博客贪心算法贪心与其说是一种算法,不如说一种思想。贪心思想,顾名思义,就是总是做出当前最好的选择,这种方式可能在全局上不是最好的结果,但是在一些题目中就可以直接用。最简单的例子就是“货比三家”,在生活中,我们买东西时都会挑性价比最优的,这就是一种贪心。贪心算......
  • 算法笔记(6)数列分块
    原发表于我的博客前言分块不能说是一种数据结构,它是一种思想,无论是数列分块,块状链表,还是数论分块,莫队算法,都应用了分块的思想。本文主要介绍狭义上的分块,即数列分块。数列分块的引入数列分块可以说是暴力,一种优美的暴力,它的基本思路是,把数列分成若干块(一般取\(\sqrtn\)),分块......