您的位置:首页 > 理论基础 > 数据结构算法

POJ 3349 所谓哈希

2009-12-15 23:17 337 查看
数据结构者,“数据间关系+数据存储方式”也。

选择何种数据结构,取决于需要解决什么样的问题。

任何一个数据结构都有它的优势,这个优势说白了就是“本数据结构在进行XX操作时快”,而选择何种数据结构就看要解决的问题需要在数据结构上进行何种操作来决定。

哈希表就是体现这个道理的一个很好的例子。

哈希表提供这么一种其它数据结构并不擅长的操作:

“在理想情况下,能用常量时间查找到指定值的数据”。

普通数据结构如线性表、树、图等,其结点内的数据与数据所存储的位置之间的关系是随机的,所以要想提供“查找某已知值的数据的位置”只能通过“比较”方式进行,如:对于未排序的线性表,要从头至尾逐一比较是否“==”;对于二分查找,则要比较“>”“<”还是“==”。这样,时间复杂度是n或者logn。

而哈希表通过“在存储位置和数据值之间建立映射关系”这一手段,将该操作的时间复杂度降低为O(1)。

这个描述“在存储位置和数据值之间建立映射关系”的映射关系就是哈希函数。

将数据存入哈希表时,利用哈希函数为该数据安排存储位置;查找指定值数据时,也按照哈希函数得到目标索引。

实际操作起来时,由于数值域和索引域大小不同,所以不能简单地线性映射,而是需要建立较复杂的哈希函数,这就有可能造成“冲突”——这是哈希面临的主要问题。好的哈希函数应该让随机数据值得到的哈希结果尽可能地随即和分散,而且减少冲突。

 

=====================分割线=======================

 

有这样一类问题,查找是否有出现多于一次的数据?笨方法只能从头至尾逐一比对,复杂性为O(N*N)。聪明一点的方法,如果数据不是很分散,可以用做记号的方法,用一个位数组(可用bool实现)记录各个位所代表的数据是否出现过,如果读入一个已经出现过的数据则说明它出现多于一次,用int代替bool,还可以记录下每个数据出现的次数,在遍历一次便可得到出现最多次的数据,复杂度为O(N)。但是如果数据很分散,这种线性映射就不管用了,因为内存会严重浪费,如果数据过于夸张,会造成很大BUFFER的申请,但是这种映射记录的思想还是可取的,这时就需要一个从很大的数据域向相对很小的地址域映射的工具来辅助,自然就是“哈希”。

 

POJ3349就是利用哈希这个特点的一个典型应用。题目本身与存取无关,而只是要找出是否有数据出现过一次以上。就可以采取上述“标记”+“映射”的方法。代码如下:

#include <iostream>
#include <vector>
#include <cstdio>
#define BIG_PRIMER 87719

using namespace std;

struct snowflake
{
bool flag;	//0 for empty;
unsigned long arms[6];
snowflake()
{
flag = false;
}
};

bool cmpTwoSnowflakes(const snowflake& s1,const snowflake& s2)
{
int start[6] = {0},count = 0;
for(int i  = 0;i < 6;i++)
{
if(s1.arms[0]==s2.arms[i])
{
start[count++] = i;
}
}
if(count==0)
return false;
bool IsSame = true;
for(int j = 0;j < count;j++)
{
for(int i = 1;i < 6;i++)
{
if(s1.arms[i]!=s2.arms[(start[j]+i)%6])
{
IsSame = false;
break;
}
}
if(IsSame == true)
return true;
IsSame = true;
for(int i = 1;i < 6;i++)
{
if(s1.arms[i]!=s2.arms[(start[j]-i+6)%6])
{
IsSame = false;
break;
}
}
if(IsSame == true)
return true;
IsSame = true;
}
return false;
}

unsigned long Hash(const snowflake& sf)
{
return (sf.arms[0]+sf.arms[1]+sf.arms[2]+sf.arms[3]+sf.arms[4]+sf.arms[5])%BIG_PRIMER;
}

int main()
{
vector<snowflake> vs[BIG_PRIMER+3];
int n;
cin>>n;
snowflake tempflake;
int address;
while(n--)
{
for(int i = 0;i < 6;i++)
{
scanf("%d",tempflake.arms+i);//cin>>tempflake.arms[i];
}
address = Hash(tempflake);
vs[address].push_back(tempflake);
}
for(int i = 0;i < BIG_PRIMER;i++)
{
for(int j = 0;j < vs[i].size();j++)
{
for(int k = j+1;k < vs[i].size();k++)
{
if(cmpTwoSnowflakes(vs[i][j],vs[i][k])==true)
{
cout<<"Twin snowflakes found./n";
return 0;
}
}
}
}
cout<<"No two snowflakes are alike./n";
}


 

《编程珠玑》里的开篇故事里提到的电话号问题与刚才提到的标记思想相当契合。要对一个海量的数据进行排序,可以用一块连续的内存标记某号码是否出现过,然后从头开始扫描,若出现过则打印对应号码,以此完成排序。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息