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

数据结构学习笔记07散列查找(哈希表)

2016-05-16 13:12 656 查看
1.散列表(Hash)

查找的本质: 已知对象找位置。
  有序安排对象:全序、半序
  直接“算出”对象位置:散列
  时间复杂度几乎是常量:O(1),即查找时间与问题规模无关

散列查找法的两项基本工作:
  计算位置:构造散列函数确定关键词存储位置;
  解决冲突:应用某种策略解决多个关键词位置相同的问题

散列(Hashing) 的基本思想是:
  ①以关键字key为自变量,通过一个确定的函数 h(散列函数),计算出对应的函数值h(key),作为数据对象的存储地址。
  ②可能不同的关键字会映射到同一个散列地址上,即h(keyi) = h(keyj)(当keyi ≠keyj),称为“冲突(Collision)”。
                                          ----需要某种冲突解决策略

2.散列函数的构造方法

散列函数两个关键:

  ①计算简单,以便提高转换速度;
  ②关键词对应的地址空间分布均匀,以尽量减少冲突。

数字关键词的散列函数构造

①直接定址法
  取关键词的某个线性函数值为散列地址,即
  h(key) = a * key + b (a、b为常数)

②除留余数法
  散列函数为:h(key) = key mod p  (一般p取素数)

③数字分析法
  分析数字关键字在各位上的变化情况,取比较随机的位作为散列地址
    Eg:取11位手机号码key的后4位作为地址:
    散列函数为:h(key) = atoi(key+7) (char *key)

④折叠法
  把关键词分割成位数相同的几个部分,然后叠加

    Eg: 56793542
        542
        793
       + 056
       ———
        1391
            h(56793542) = 391

⑤平方取中法
    Eg: 56793542
      56793542
     x 56793542
    —————————
 3225506412905764
            h(56793542) = 641

字符关键词的散列函数构造

①一个简单的散列函数——ASCII码加和法
  对字符型关键词key定义散列函数如下:
  h(key) = (Σkey[i]) mod TableSize

②简单的改进——前3个字符移位法(易造成空间浪费)
  h(key)=(key[0]*27^2 + key[1]*27 + key[2]) mod TableSize

③好的散列函数——移位法
  涉及关键词所有n个字符,并且分布得很好:

//分离链接法
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <math.h>
using namespace std;

#define MAXTABLESIZE 100000 /* 允许开辟的最大散列表长度 */
#define KEYLENGTH 15                   /* 关键词字符串的最大长度 */
typedef char ElementType[KEYLENGTH+1]; /* 关键词类型用字符串 */
typedef int Index;                     /* 散列地址类型 */

/******** 以下是单链表的定义 ********/
typedef struct LNode *PtrToLNode;
struct LNode {
ElementType Data;
PtrToLNode Next;
};
typedef PtrToLNode Position;
typedef PtrToLNode List;
/******** 以上是单链表的定义 ********/

typedef struct TblNode *HashTable; /* 散列表类型 */
struct TblNode {   /* 散列表结点定义 */
int TableSize; /* 表的最大长度 */
List Heads;    /* 指向链表头结点的数组 */
};

int NextPrime( int N )
{ /* 返回大于N且不超过MAXTABLESIZE的最小素数 */
int i, p = (N%2)? N+2 : N+1; /*从大于N的下一个奇数开始 */

while( p <= MAXTABLESIZE ) {
for( i=(int)sqrt(p); i>2; i-- )
if ( !(p%i) ) break; /* p不是素数 */
if ( i==2 ) break; /* for正常结束,说明p是素数 */
else  p += 2; /* 否则试探下一个奇数 */
}
return p;
}

HashTable CreateTable( int TableSize )
{
HashTable H;
int i;

H = (HashTable)malloc(sizeof(struct TblNode));
H->TableSize = NextPrime(TableSize);/* 保证散列表最大长度是素数 */
H->Heads = (List)malloc(H->TableSize*sizeof(struct LNode));/* 以下分配链表头结点数组 */

/* 初始化表头结点 */
for( i=0; i<H->TableSize; i++ ) {
H->Heads[i].Data[0] = '\0';
H->Heads[i].Next = NULL;
}

return H;
}

Index Hash(ElementType Key, int TableSize )
{

return (*Key - 'a') % TableSize;
}

Position Find( HashTable H, ElementType Key )
{
Position P;
Index Pos;

Pos = Hash( Key, H->TableSize ); /* 初始散列位置 */
P = H->Heads[Pos].Next; /* 从该链表的第1个结点开始 */
/* 当未到表尾,并且Key未找到时 */
while( P && strcmp(P->Data, Key) )
P = P->Next;

return P; /* 此时P或者指向找到的结点,或者为NULL */
}

bool Insert( HashTable H, ElementType Key )
{
Position P, NewCell;
Index Pos;

P = Find( H, Key );
if ( !P ) { /* 关键词未找到,可以插入 */
NewCell = (Position)malloc(sizeof(struct LNode));
strcpy(NewCell->Data, Key);
Pos = Hash( Key, H->TableSize ); /* 初始散列位置 */
/* 将NewCell插入为H->Heads[Pos]链表的第1个结点 */
NewCell->Next = H->Heads[Pos].Next;
H->Heads[Pos].Next = NewCell;
return true;
}
else { /* 关键词已存在 */
printf("键值已存在");
return false;
}
}

void DestroyTable( HashTable H )
{
int i;
Position P, Tmp;

/* 释放每个链表的结点 */
for( i=0; i<H->TableSize; i++ ) {
P = H->Heads[i].Next;
while( P ) {
Tmp = P->Next;
free( P );
P = Tmp;
}
}
free( H->Heads ); /* 释放头结点数组 */
free( H );        /* 释放散列表结点 */
}

int main()
{
HashTable hash;
hash = CreateTable(5); //real size 7: 0 1 2 3 4 5 6
Insert( hash, "a" );
Insert( hash, "b" );
Insert( hash, "c" );
Insert( hash, "d" );
Insert( hash, "e" );
Insert( hash, "h" );
Insert( hash, "g" );
return 0;
}


分离链接法



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: