散列(hashing)

散列(hashing)是C++STL 字典实现的一种方式,散列的实现一般包括两个步骤:
1.将键值映射成一个整数。
2.将这个整数通过函数映射到一个线性表中
首先介绍第一个问题:怎么将键值映射成为一个整数
第一种转换法:

int stringToInt(string s)
{//把s转换成一个非负整数,这种转换依赖s的所有字符
int length=(int)s.length();
int answer=0;
if(length%2==1)
{//长度为奇数
    answer=s.at(length-1);
    length--;//先把长度转换为偶数
}
for(int i=0;i<length;i+=2)
{//同时转换两个字符
answer+=s.at(i);
answer+=((int)s.at(i+1))<<8;//这里+=的优先级小于<<.
}
return (answer<0)?-answer:answer;

}

如果键字符串为一个奇数则将其最高位(转换为int)直接加到一个2字节变量里面,然后键长度变为偶数,然后将剩下的键的偶数位加到变量的低八位,奇数位加到变量的高八位。这样就将一个字符串映射成一个整数了,并且不同的字符串映射成同一个整数的可能极小。

第二种转换:

template<>
calss hash<string>
{
    public:
    size_t operator()(const string theKey)const
    {//把关键字theKey转换成一个非负整数
        unsigned long hashValue=0;
        int length=(int)theKey.length();
        for(int i=0;i<length;i++)
            hashValue=5*hashValue+theKey.at(i);
        return size_t(hashValue)    
    }
};

上面为C++的STL模板类hash实现散列的一个专业版,它的转换方法非常直接和暴力,直接一个简单的线型变换搞定。

现在介绍第二个问题:怎么将这个整数通过函数映射到一个线性表中
首先建立一个线型表

pair<const K,E>** table;
hash<K> hash;
int dSize;
int divisor;

template<class K,class E>
hashTable<K,E>::hashTable(int divisor)
{
    divisor=theDivisor;
    dSize=0;

    //分配和初始化散列表函数
    table=new pair<const K,E>* [divisor];//table每个成员是个指向pair的指针
    for(int i=0;i<divisor;i++)
        table[i]=NULL;//初始化指针为NULL
}

我们用到的映射公式为f(k)=k%D;我们将整数为K的键,通过取余映射到长度为D的数组里面。当K>D时,就会发生这样的问题即不同的两个键映射到了同样的数组元素里面,这就是所谓的冲突,发生冲突时我们一般采取一种叫做线型探查的方法进行解决。(一般设计的数组的一个元素里面能够容纳很多个pair对,不存在冲突,但是我们这里的设定是,一个元素只能容纳一个pair对,默认会发生冲突)上面函数创建了一个长度为D的数组。

线型探查:如果两个不同的键映射到了相同的数组元素(一般称为起始桶),那么我们就将后加入的那么元素,放到离该元素最近的右边的一个空元素里面,这就叫做线型探查。(认为数组是循环的即,数组的首尾相连)

template<class K,class E>
int hashTable<K,E>::search(const K& theKey)const
{//搜索一个公开地址散列表,查看关键字为theKey的数对
//如果匹配数对存在,返回它的位置,否则,如果散列表不满,
//则返回关键字为theKey的数对可以插入的位置。
int i=(int)hash(theKey)%divisor;//hash()将键映射成一个整数,%divisor找到起始桶的位置
int j=i;//从起始桶开始
do{
    if(table[j]==NULL||table[j]->first==theKey)
        return j;//如果起始位置为空或者起始位置存储的pair就是我们查找的则返回这个位置
    j=(j+1)%divisor;//下一个桶,到最后一个元素后会从第一个元素开始循环
}while(j!=i);
    return j;//表满j=i.
}

以上就是线型探查的程序。

template<class K,class E>
pair<const K,E>* hashTable<K,E>::find(const K& theKey)const
{//返回匹配数对的指针
//如果匹配数对不存在,返回NULL
//搜索散列表
int b=search(theKey);
//判断table[b]是否匹配成功
if(table[b]==NULL||table[b]->first!=theKey)
    return NULL;//没有找到
return table[b];//找到匹配数对

}

template<class K,class E>
void hashTable<K,E>::insert(const pair<const K,E>& thePair)
{//把数对thePair插入字典。如存在相同的数对,则覆盖
//若表满,则抛出异常
int b=search(thePair.first);
if(table[b]==NULL)
{
    tale[b]=new pair<const K,E>(thePair);
    dSize++;
}
else
{//检查是否有重复的关键字数对或者是否表满
if(table[b]->first==thePair.first)
{
    table[b].second=thePair.second;
}
else
    throw hashTableFull();

}

}

以上是查找和插入的程序。

散列和普通线性表的效率公式如下:
U(n)=1/2(1+1/(1-a)^2)
S(n)=1/2(1+1/(1-a)) (散列)
其中a=n/b为负载因子。一般而言散列效率高
D的选取:
理想的D是一个素数,或者是一个不能被小于20的数整除的数。

链表实现
链表实现就是将数组的每个元素都当做一个链表头,将数组存在这些个链表里面这样就不会出现冲突了。

template<class K,class E>
pair<const K,E>* find(const K& theKey)const
{
    return table[hash(theKey)%divisor].find(theKey);//因为在元素里面存的是一个链表,所以直接用链表的find方法
}

void insert(const pair<const K,E>& thePair)
{
    int homeBucket=(int)hash(thePair.first)%divisor;
    int homeSize=table[homeBucket].size();
    table[homeBucket].insert(thePair);
    if(table[homeBucket].size()>homeSize)
        dSize++;//这个我插入了一个新数据难道还会变小不成。
}

void erase(const K& theKey)
{
    table[hash(theKey)%divisor].erase(theKey);
}

猜你喜欢

转载自blog.csdn.net/Du_Shuang/article/details/81221023