哈希/散列(hash)

哈希:

大意:通俗点讲 就是一般我们对有些要查找统计的题,在我们输入时就把他们分成几类(通过某些关系),等回过头查找时  直接在属于他的分区找就是了 这样就大大缩短了时间。这里的某些关系又被称为哈希函数。当然  哈希不止这些作用  在网络安全方面也是一大功臣。

官方定义:哈希算法将任意长度的二进制映射为较短的固定长度的二进制值,这个小的二进制值称为哈希值哈希值是一段数据唯一且极其紧凑的数值表示形式。如果散列一段明文而且哪怕只更改该段落的一个字母,随后的哈希都将产生不同的值。要找到散列为同一个值的两个不同的输入,在计算上是不可能的,所以数据的哈希值可以检验数据的完整性。一般用于快速查找和加密算法。

构造哈希函数通常由以下途径:

 1. 直接寻址法取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a•key + b,其中a和b为常数(这种散列函数叫做自身函数)

        2. 数字分析法分析一组数据,比如一组员工的出生年月日,这时我们发现出生年月日的前几位数字大体相同,这样的话,出现冲突的几率就会很大,但是我们发现年月日的后几位表示月份和具体日期的数字差别很大,如果用后面的数字来构成散列地址,则冲突的几率会明显降低。因此数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。

      3. 平方取中法:取关键字平方后的中间几位作为散列地址。

   4. 折叠法:将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。

   5. 随机数法:选择一随机函数,取关键字的随机值作为散列地址,通常用于关键字长度不同的场合。

   6. 除留余数法:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p, p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词。

方法1和方法6常会用到

 当我们要输入很多数据时 可能会出现两个数对应的哈希值一样  这就是哈希冲突,解决冲突的方法也有好几种 

       开放定址法

开放定址法就是从发生冲突的那个单元开始,按照一定的次序,从散列表中查找出一个空闲的存储单元,把发生冲突的待插入元素存入到该单元中的一类处理冲突的方法。在开放定址法中,散列表中的空闲单元(假定下标为d)不仅向散列地址为d的同义词元素开放,即允许它们使用,而且向发生冲突的其它元素开放,因它们的散列地址不为d,所以称为非同义词元素。总之,在开放定址法中,空闲单元既向同义词元素开放,也向发生冲突的非同义词元素开放,此方法的名称也由此而来。在使用开放定址法处理冲突的散列表中,下标为d的单元最终存储的是同义词中的一个元素,还是其它元素,就看谁先占用它。查找一个元素的过程是:首先根据给定的关键字K,利用与插入时使用的同一散列函数h计算出散列地址(假定为下标d),然后,用K同d单元的关键字进行比较,若相等则查找成功,否则按照插入时处理冲突的相同次序,依次用K同所查单元的关键字进行比较,直到查找成功或查找到一个空单元(表明查找失败)为止。
在开放定址法中,从发生冲突的散列地址为d的单元起进行查找有多种方法,每一种都对应着一定的查找次序,所经过的单元构成了一条查找路径或称探查序列。在查找的多种方法中,主要有线性探查法、平方探查法和双散列函数探查法等。
(1)线性探查法
线性探查法是用开放定址法处理冲突的一种最简单的探查方法,它从发生冲突的d单元起,依次探查下一个单元(当达到下标为m—l的表尾单元时,下一个探查的单元是下标为O的表首单元,即把散列表看作为首尾相接的循环表),直到碰到一个空闲单元或探查完所有单元为止。这种方法的探查序列为d,d+l,d+2,…,或表示为(d+i)%m(O≤i≤m—1)。
当然,这里的i在最坏的情况下才能取值到m—1,一般只需取前几个值就可能找到一个空闲单元。找到一个空闲单元后,把发生冲突的待插入元素存入该单元即可。
(2)平方探查法
平方探查法是一种较好的处理冲突的方法,它能够较好地避免堆积现象。它的缺点是不能探查到散列表上的所有单元,但至少能探查到一半单元(证明从略)。例如,当do=5,m=17时,只能探查到下标依次为5,6,9,14,4,13,7,3,1的单元,而不能探查到剩余的单元。不过在实际应用中,能探查到一半单元也就可以了。若探查到一半单元仍找不到一个空闲单元,表明此散列表太满,应该重新建立。
(3)双散列函数探查法
这种方法使用两个散列函数hl和h2。其中hl和前面的h一样,以关键字为自变量,产生一个0至m—l之间的数作为散列地址;h2也以关键字为自变量,产生一个l至m—1之间的、并和m互素的数(即m不能被该数整除)作为探查序列的地址增量(即步长),探查序列的步长值是固定值l;对于平方探查法,探查序列的步长值是探查次数i的两倍减l;对于双散列函数探查法,其探查序列的步长值是同一关键字的另一散列函数的值。

2    拉链法

(1)拉链法解决冲突的方法
     拉链法解决冲突的做法是:将所有关键字为同义词的结点链接在同一个单链表中。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数组T[0..m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。在拉链法中,装填因子α可以大于1,但一般均取α≤1。

【例9.2】已知一组关键字和选定的散列函数和例9.1相同,用拉链法解决冲突构造这组关键字的散列表。
  解答:不妨和例9.1类似,取表长为13,故散列函数为h(key)=key%13,散列表为T[0..12]。
注意:
     当把h(key)=i的关键字插入第i个单链表时,既可插入在链表的头上,也可以插在链表的尾上。这是因为必须确定key不在第i个链表时,才能将它插入表中,所以也就知道链尾结点的地址。若采用将新关键字插入链尾的方式,依次把给定的这组关键字插入表中,则所得到的散列表如下图所示。
 144411_pCb3_1762578.jpg     

(2)拉链法的优点
     与开放定址法相比,拉链法有如下几个优点:
  (1)拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;
  (2)由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
  (3)开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
  (4)在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。而对开放地址法构造的散列表,删除结点不能简单地将被删结点的空间置为空,否则将截断在它之后填人散列表的同义词结点的查找路径。这是因为各种开放地址法中,空地址单元(即开放地址)都是查找失败的条件。因此在用开放地址法处理冲突的散列表上执行删除操作,只能在被删结点上做删除标记,而不能真正删除结点。

(3)拉链法的缺点
     拉链法的缺点是:指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度。

/*
*哈希表 拉链法
*/
#include<stdio.h>
#include<stdlib.h>
#define MinTableSize 10
typedef int ElemType;
typedef unsigned int Index;
typedef struct ListNode
{
 ElemType element;
 struct ListNode *next;
}*Position;
typedef Position List;
/* List *TheList will be an array of lists, allocated later */
/* The lists use headers (for simplicity), */
/* though this wastes space */
typedef struct HashTbl
{
 int TableSize;
  List *TheLists;
}*HashTable;
int NextPrime(int N)
{
 int i;
 if(N%2==0)
  N++;
 for(;;N+=2)
 {
  for(i=3;i*i<=N;i+=2)
   if(N%i==0)
    return 0;
  return N; 
 }
}
/*Hash function for ints*/
Index Hash(ElemType Key,int TableSize)
{
 return Key%TableSize;
}
HashTable InitializeTable(int TableSize)
{
 HashTable H;
 int i;
 if(TableSize<MinTableSize)
 {
  printf("Table size too small!\n");
  return NULL;
 }
 
 /*Allocate table*/
 H=(HashTable)malloc(sizeof(struct HashTbl));
 if(NULL==H)
   printf("Out of space!!!\n");
 H->TableSize=NextPrime(TableSize);
 
 /*Allocate array of lists*/
 H->TheLists=(List *)malloc(sizeof(List)*H->TableSize);
 if(NULL==H->TheLists)
 {
   printf("Out of space!!!\n");
   free(H);
   return NULL;
 }
 /*Allocate list  headers*/
 for(i=0;i<H->TableSize;i++)
 {
  H->TheLists[i]=(Position)malloc(sizeof(struct ListNode));
  if(NULL==H->TheLists[i])
   printf("Out of space!!!\n");
  else
   H->TheLists[i]->next=NULL;
  H->TheLists[i]->element=0;//哈希表中所有元素的key初始化为0
 }
 return H;
}
Position Find(ElemType Key,HashTable H)
{
 Position p;
 List L;
 L=H->TheLists[Hash(Key,H->TableSize)];
 p=L->next;
 while(p!=NULL&&p->element!=Key)/*Probably need strcmp!!*/
  p=p->next;
 return p;
}
void Insert(ElemType Key,HashTable H)
{
 Position pos,newCell;
 List L;
 pos=Find(Key,H);
 if(NULL==pos)/*Key is not found*/
 {
  newCell=(Position)malloc(sizeof(struct ListNode));
  if(NULL==newCell)
    printf("Out of space!!!"); 
  else
  {
   L=H->TheLists[Hash(Key,H->TableSize)];
   newCell->next=L->next;
   newCell->element=Key;/*Probably need strcpy*/
   L->next=newCell;
  }
 }
}
void DestroyTable(HashTable H)
{
 int i;
 for(i=0;i<H->TableSize;i++)
 {
  Position p=H->TheLists[i];
  Position temp;
  while(p!=NULL)
  {
   temp=p->next;
   free(p);
   p=temp;
  }
 }
 free(H->TheLists);
 free(H);
}
void printHash(HashTable H,int len)
{
 int i;
 for(i=0;i<len;i++)
 {
  Position p=H->TheLists[i];
  while(p)
  {
   printf("address=%d value=%d\n",i,p->element);
   p=p->next;
  } 
 }
}
int main()
{
 
 HashTable H;
 Position p=NULL;
 int array[]={19,14,23,01,68,20,84,27,55,11,10,79};
 int len=sizeof(array)/sizeof(array[0]);
 int i;
 ElemType k;
          
 H=InitializeTable(len);
 for(i=0;i<len;i++)
 {
  Insert(array[i],H); 
 }
 printHash(H,len);
 printf("\n\n");
 
 printf("please input the value which need find:");
 scanf("%d",&k);
 p=Find(k,H);
 if(p)
   printf("%d",p->element);
 else
   printf("cannot find the value!");
 printf("\n\n");
  
 printf("free the table\n");
 DestroyTable(H);
 printf("it's done!!!");
 printf("\n\n");
 return 0;
}

例题:

 

A - SnowflakeSnowSnowflakes

Crawling in process... Crawling failed Time Limit:4000MS     Memory Limit:65536KB     64bit IO Format:%I64d & %I64u

Submit Status Practice POJ 3349

Description

You may have heard that no two snowflakes are alike. Your task is to write a program to determine whether this is really true. Your program will read information about a collection of snowflakes, and search for a pair that may be identical. Each snowflake has six arms. For each snowflake, your program will be provided with a measurement of the length of each of the six arms. Any pair of snowflakes which have the same lengths of corresponding arms should be flagged by your program as possibly identical.

Input

The first line of input will contain a single integer n, 0 < n ≤ 100000, the number of snowflakes to follow. This will be followed by n lines, each describing a snowflake. Each snowflake will be described by a line containing six integers (each integer is at least 0 and less than 10000000), the lengths of the arms of the snow ake. The lengths of the arms will be given in order around the snowflake (either clockwise or counterclockwise), but they may begin with any of the six arms. For example, the same snowflake could be described as 1 2 3 4 5 6 or 4 3 2 1 6 5.

Output

If all of the snowflakes are distinct, your program should print the message:
No two snowflakes are alike.
If there is a pair of possibly identical snow akes, your program should print the message:
Twin snowflakes found.

Sample Input

2 1 2 3 4 5 6 4 3 2 1 6 5

Sample Output

Twin snowflakes found.

 

题意:

    在n (n<100000)个雪花中判断是否存在两片完全相同的雪花,每片雪花有6个角,每个角的长度限制为1000000

    两片雪花相等的条件:

    雪花6个角的长度按顺序相等(这个顺序即可以是顺时针的也可以是逆时针的)

code:

#include<stdio.h>
#include<string.h>
#define M 200000
#define F 100000
struct NODE
{
    int data,next;
} node[M];
int a[M][6]={0},ha[M]={0};
int judge(int k1,int k2)//开放定址法
{
    int ar[20],i,j,k,ans;
    for(i=0;i<6;i++) 
           ar[i]=ar[i+6]=a[k1][i];
    for(i=0;i<6;i++)
    {
        ans = 1;
        for(j = 0; j < 6; j++)
            if (ar[i+j]!=a[k2][j])
                ans = 0;
        if (ans)
            return 1;
    }
    for(i=0;i<6;i++)
        ar[5-i]=ar[5-i+6]=a[k1][i];
    for(i=0;i<6;i++)
    {
        ans=1;
        for(j=0;j<6;j++)
            if(ar[i+j]!=a[k2][j])
                ans=0;
        if (ans)
            return 1;
    }
    return 0;
}
int hash(int k)//除留余数法
{
    int i,sum=0;
    for(i=0;i<6;i++)
        sum=(sum+a[k][i])%F;
    return sum;
}
int main()
{
    int  i,j,n,temp,t,k,l=1;
    memset(node,0,sizeof(node));
    scanf("%d",&n);
    for(i = 1; i <= n; i++)
    {
        for(j = 0; j <  6; j++)
            scanf("%d",&a[i][j]);
        t = hash(i);
        if (ha[t] == 0)
        {
            ha[t] = l;
            node[l].data = i;
            node[l++].next = 0;
        }
        else
        {
            temp=ha[t];
            while(temp!=0)
            {
                if(judge(node[temp].data,i))
                {
                    printf("Twin snowflakes found.\n");
                    return 0;
                }
                else
                    temp=node[temp].next;
            }
            node[l].next=ha[t];
            node[l].data=i;
            ha[t]=l++;
        }
    }
    printf("No two snowflakes are alike.\n");
    return 0;
}

 

 

 

转载于:https://my.oschina.net/u/1762578/blog/298913

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值