双重哈希表处理数字出现次数问题

前言————

上回——也就是哈希表解决数字出现次数问题-CSDN博客——我们讲到如何处理给定范围n~m内0~9数字出现次数的问题,因为n~m范围内数值是连续且满足前一项总是<后一项的,所以记录计算相对比较容易。那,如果是一串打乱的无序的数字呢?

题目分析

题目:

 

给定若干数字存储于arr数组中,找出该数组只出现n次的元素打印出来。

 

实际上,解决0~9数字出现次数问题就是利用双重哈希表来解决的,也就是需要两个哈希表进行数据处理。但经过思考发现,数组arr【10】中下标依次为0~9的有序递增序列,我们可以利用下标来充当哈希表存储关键字,0~9各数字即为对应下标0~9,只需要将出现次数存储进数组即可。

本来我们是需要——

哈希表hash1——存储0~9

哈希表hash2——存储0~9出现的次数

其中hash2下标对应hash1,即为次数对应相关数字。

 

但是我们可以把hash1优化掉,也就是变成——

哈希表hash2【hash1】——存储0~9出现的次数

 

对于一般的数字出现问题,都可以用双重哈希表解决。

也就是:

hash1——存储相关数字

hash2——存储相关数字出现次数

 

我们只需要将给定的要处理的数组arr利用循环遍历一遍,将其中出现的数字在hash1中再循环遍历一遍,若不重复则记录这个数字就存储在hash1中并在hash2中记录次数为1,重复则在hash2中对应下标++即可完成检索记录。

	int arr[10];
	int hash1[10] = { 0 };
	int hash2[10] = { 0 };
	int i = 0;
	int j = 0;
	int k = 0;
	//存入元素至arr1
	for (i = 0; i < 10; i++)
	{
		scanf("%d", &arr[i]);
	}

 首先将要使用的参数元素定义:

arr——存储要检索的元素集合

hash1——存储arr出现的数字

hash2——存储记录次数

i,j,k为相应变量

利用for循环将数据存入arr中

	for (i = 0; i < 10; i++)
	{
		int mid = 0;
		for (j = 0; j <= k; j++)
		{
			//记录第二次及往后出现次数,如果出现重复则双重哈希表记录++,break跳出循环
			if (hash1[j] == arr[i])
			{
                mid++;
				hash2[j]++;
				break;
			}
			//跳过哈希表1中已存储的重复元素,如果是新元素,进入else存储
			if (arr[i] != hash1[j] && j != k)
			{
				continue;
			}
			//元素存入哈希表1,并记录第一次出现次数
			else
			{
				hash1[j] = arr[i];
				hash2[j]++;
			}

		}
        //k用作记录arr中出现的不重复元素个数
        if(mid)
            continue;
        else
		    k++;
	}

这段代码是整个程序的关键

代码逻辑是这样的——

检索arr中每个元素,如果该元素第一次出现,将其值存入hash1中的顺序位置,并在hash2中相应位置置1,此时k++;如果该元素出现第二次(次数n>=2),此时无需在hash1中重复记录,找到其在hash1中的位置,并在hash2中相同下标的地址元素进行++处理。

 

讲解一下。

首先是for循环嵌套一层for循环,第一层for循环到10截止,因为arr中只有10个元素;第二层for循环到k截止,   其中k表示数组arr中出现元素的个数

比如给定arr{1,2,2,4,3,1,6,7},则里面共有1,2,3,4,6,7种元素,则k=6。

在第一层for循环中,我们需要定义一个判定变量mid,用来判断何时k需要+1,何时不需要+1。因为k决定了hash1和hash2中检索到哪里,k等于多少,hash1和hash2中就存了多少位元素,arr中出现的元素就有多少个。

也就是说:

mid决定k是否需要进行+1。

mid=1——k无需+1.

mid=0——k需要+1.

在第二层for循环中,如果前序已经在hash1及hash2存入数据了,此时只需判断这次检索的arr元素在已经存入hash1中的相同元素位于哪一位置,在这个下标位置的hash2中进行+1即可。同时,将mid赋值为1,因为mid=1所以k无需+1。

如果满足条件,还需要break跳出第二层循环,此外,mid只要在第一层循环判断就行。

要实现上述功能仅需if判断即可,如下:

			if (hash1[j] == arr[i])
			{
                mid++;
				hash2[j]++;
				break;
			}

当hash1中所有元素与当前检索的arr元素不匹配时,我们需要将这个新的未出现过的元素存入hash1中并在hash2中同样位置执行++。

为了实现这个功能我们需要定位hash1中的空位。当然,为了美观肯定是按顺序存储,摆在我们面前的存储问题有两种解决方法:

1.将hash1所有元素向后推一位,将新元素存入下标为0的空间中。

2.找到hash1中按顺序存放的第一个空位,将新元素存入该空位中。

显然第二种方法更简便。而且发现,在上述的代码中,k这个变量刚好是用来记录arr中出现元素的个数的,同样可以表示为hash1中已存储的元素个数,这样的话hash1【k】就刚好是空位

当第二层for循环j = k时,此时将arr【i】元素存入hash1【k】中,并使得hash2【k】++。但由于是嵌套在for循环中,我们可以使用“ j ”来替代“ k ”。

当第二层循环j != k时,此时需要利用continue跳过循环。

上述功能实现如下:

			if (arr[i] != hash1[j] && j != k)
			{
				continue;
			}
			else
			{
				hash1[j] = arr[i];
				hash2[j]++;
			}

上面这些if判别代码也可以使用if——else if——else的形式,为了方便讲解就不用了,两者效果相同逻辑相同。

我们来总结一下:

 利用for循环检索arr中每个元素,每个元素都需要再次在两个hash中进行检索,需要在for循环中再嵌套使用for循环完成检索。

为了节省空间,需要利用一个k变量用以确认arr中到底有多少个不同数字出现,且k刚好可以用以hash1及hash2中定位空位存储新元素以及次数,为了精确判别何时k+1,需要在第二层for循环中利用变量mid进行判别,如果元素重复则无需+1,反之则需+1。

在进行元素二次检索时,也即进行至第二层for时,首先使用if判别当前arr【i】元素是否与正在循环检索的hash1【j】元素相同。如果相同,在hash2【j】中进行+1处理(此前已经在hash1【j】中存入元素并在hash2【j】中置1),此外还需要使用break跳出第二层for循环,并将mid赋值为1;反之,则程序继续。

再创建一个判别式,用以确保j = k,也就是第二层for循环中hash1【j】以及hash2【j】都位于空位上,也就是利用j != k跳过空余循环。如果空余循环中出现重复数字,已经在第一个if中跳出循环并在hash2中记录了。

当第二层for循环j进行到k时(即j == k),第二个if判别错误,程序进行至else,只需将当前第一层for循环检索的arr【i】赋值给hash1【j】,然后hash2【j】++即可。

整体代码

整体代码如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	int arr[10];
	int hash1[10] = { 0 };
	int hash2[10] = { 0 };
	int i = 0;
	int j = 0;
	int k = 0;
	for (i = 0; i < 10; i++)
	{
		scanf("%d", &arr[i]);
	}
	for (i = 0; i < 10; i++)
	{
		int mid = 0;
		for (j = 0; j <= k; j++)
		{
			if (hash1[j] == arr[i])
			{
				mid++;
				hash2[j]++;
				break;
			}
			else if (j != k)
			{
				continue;
			}
			else
			{
				hash1[j] = arr[i];
				hash2[j]++;
			}


		}
		if (mid)
			continue;
		else
			k++;
	}
	for (i = 0; i < k; i++)
	{
		if (hash2[i] == 1)
			printf("%d \n", hash1[i]);
	}
	return 0;
}

注意整体代码中第二层for循环不再使用if——if——else形式,将第二个if判别条件修改为“j != k”,并改为if——else if——else的形式。两者完全等效,前文为了讲解没做此改动。

我们来调试看看~

调试

举例1 2 2 1 4 5 6 3 3 8

出现数字:1,2,4,5,6,3,8

次数:【1,2】,【2,2】,【4,1】,【5,1】,【6,1】,【3,2】,【8,1】

arr依次录入上述数据。

3839f1cd69884de0abc7eda8796a5069.png

第二层for循环结束,hash1中存入1,hash2中记录次数1。

9d821bf6d2aa4e358f662a4660ea3e50.png

由于mid没有置1,k++,注意监视窗口中变量k以及mid。

规定:mid==1,跳过循环k无需+1;mid==0,k+1.

9a84f9fe139b4e91a788fc41991cd9b2.png

当运行到arr【2】时,此时hash1【1】中已存在该元素“ 2 ”,进行第一个if,执行跳出与hash2记录命令。

注意右侧监视窗口hash2.

ede00178c3ea4e8fb0c8ae250559e6a3.png

9d848de74174479890123fe425b5b8e5.png

当第一层for循环遍历完整个arr数组后,hash1以及hash2完成对arr数组中元素的检索

3dc87f1676a04cd985ba26387a78494d.png得出答案为:

【1,2】,【2,2】,【4,1】,【5,1】,【6,1】,【3,2】,【8,1】,与此前得出答案一致。

若规定要求打印只出现一次的数字,则在最后打印的时候将判别条件改为hash2【i】==1:

956815404b794d6e9076b13c50944abf.png

对比监视窗口中hash2中存储的数据,发现一致,题目得解。

打印时候记得将截止条件设为k,因为hash1和hash2中只有k个元素。

 

如果再遇到类似的需要存储某个元素的多项数据的题目,优先想到哈希表,人家想要什么元素我们就调用存储该类元素的哈希表,哪怕三重四重,都不在话下。

那么好的,文章至此结束,下次再见。

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值