前言————
上回——也就是哈希表解决数字出现次数问题-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依次录入上述数据。
第二层for循环结束,hash1中存入1,hash2中记录次数1。
由于mid没有置1,k++,注意监视窗口中变量k以及mid。
规定:mid==1,跳过循环k无需+1;mid==0,k+1.
当运行到arr【2】时,此时hash1【1】中已存在该元素“ 2 ”,进行第一个if,执行跳出与hash2记录命令。
注意右侧监视窗口hash2.
当第一层for循环遍历完整个arr数组后,hash1以及hash2完成对arr数组中元素的检索
得出答案为:
【1,2】,【2,2】,【4,1】,【5,1】,【6,1】,【3,2】,【8,1】,与此前得出答案一致。
若规定要求打印只出现一次的数字,则在最后打印的时候将判别条件改为hash2【i】==1:
对比监视窗口中hash2中存储的数据,发现一致,题目得解。
打印时候记得将截止条件设为k,因为hash1和hash2中只有k个元素。
如果再遇到类似的需要存储某个元素的多项数据的题目,优先想到哈希表,人家想要什么元素我们就调用存储该类元素的哈希表,哪怕三重四重,都不在话下。
那么好的,文章至此结束,下次再见。