字典与散列表

一、字典
字典(dictionary)是一些元素的集合。每个元素有一个称作key 的域,不同元素的key各
不相同。有关字典的操作有:
• 插入具有给定关键字值的元素。
• 在字典中寻找具有给定关键字值的元素。
• 删除具有给定关键字值的元素。


随机访问:若仅按照一个字典元素本身的关键字来访问该元素。
顺序访问:指按照关键字的递增顺序逐个访问字典中的元素。顺序访问需借助于Begin (用来返回关键字最小的元素)和
Next (用来返回下一个元素)等操作来实现。

字典的线性表描述:
字典可以保存在线性序列(e1 , e2 , ⋯) 中,其中ei 是字典中的元素,其关键字从左到右依次
增大。为了适应这种描述方式,可以定义两个类SortedList和SortedChain。
#ifndef _DICT   
#define _DICT
#include<iostream>
using namespace std;
template<class E,class K> class SortedChain;
template <class E ,class K>
class SortedChainNode
{
friend class SortedChain<E,K>;
private:
K data;
SortedChainNode<E,K> *link;
};
//E表示链表元素的数据类型, K是链表中排序所用到的关键字
template<class E ,class K>
class SortedChain
{
public:
SortedChain() {first =0;}
~SortedChain(){ }
bool IsEmpty() const {return first ==0;}
//int Length() const;
bool Search(const K& k , E& e) const;
SortedChain<E ,K>& Delete(const K& k , E& e);
//SortedChain<E ,K>& Insert(const E& e);
SortedChain<E ,K>& DistinctInsert(const E& e);
void Output(ostream& out) const;
private:
SortedChainNode<E ,K> *first;
};
template<class E, class K>
bool SortedChain<E,K>::Search(const K& k, E& e) const
{// 搜索与k匹配的元素,结果放入e
// 如果没有匹配的元素,则返回f a l s e
SortedChainNode<E,K> *p =first;
// 搜索与k相匹配的元素
for (;p&& p->data < k;p = p->link);
// 验证是否与k匹配
if (p && p->data == k) // 与k相匹配
{e = p->data; return true;}
return false; // 不存在相匹配的元素
}


template<class E, class K>
SortedChain<E,K>& SortedChain<E,K>
::Delete(const K& k, E& e)
{// 删除与k相匹配的元素
// 并将被删除的元素放入e
// 如果不存在匹配元素,则引发异常B a d I n p u t
SortedChainNode<E,K> *p = first,
*tp = 0; //跟踪p
// 搜索与k相匹配的元素
for (; p && p->data < k; tp = p,p = p->link;)


// 验证是否与k匹配
if (p && p->data == k) {// 找到一个相匹配的元素
e = p->data; // 保存d a t a域
// 从链表中删除p所指向的元素
if (tp) tp->link = p->link;
else first = p->link; // p是链首节点
delete p;
}


return *this;


}


template<class E, class K>
SortedChain<E,K>& SortedChain<E,K>::DistinctInsert(const E& e)
{// 如果表中不存在关键值与e相同的元素,则插入e


SortedChainNode<E,K> *p = first, *tp = 0; // 跟踪p
// 移动tp 以便把e 插入到t p之后
for (; p && p->data < e; tp = p, p = p->link);
// 检查关键值是否重复
//if (p && p->data == e)  return NULL;
// 若没有出现重复关键值, 则产生一个关键值为e的新节点
SortedChainNode<E,K> *q = new SortedChainNode<E,K>;
q->data = e;
// 将新节点插入到t p之后
q->link = p;
if (tp) tp->link = q;
else first = q;
return *this;
}


template<class E, class K>
void SortedChain<E,K>::Output(ostream& out) const  
{// 将链表元素送至输出流   
SortedChainNode<E,K> *current;  
for (current=first; current; current = current->link)  
out<<current->data<<" ";  
}  


//重载<<   
template<class E, class K>
ostream& operator<<(ostream& out, const SortedChain<E,K>& x)  
{  
    x.Output(out);   
    return out;  
}  


#endif


#include<iostream>
#include"dict.h"
using namespace std;
int main()
{
	SortedChain<int ,int> myChain;
	myChain.DistinctInsert(10).DistinctInsert(20).DistinctInsert(3);
	cout<<myChain<<endl;
	return 0;
}



二、散列表
    字典的另一种描述方法就是散列(hash),它是用一个散列函数(hash function)把关键字映射到散列表(hash table)中的特定位置。在理想情况下,如果元素e 的关键字为k,散列函
数为f,那么e 在散列表中的位置为f(k)。要搜索关键字为k 的元素,首先要计算出f(k),然后看表中f(k)处是否有元素。如果有,便找到了该元素。如果没有,说明该字典中不包含该元素。
在前一种情况中,如果要删除该元素,只需把表中f(k)位置置为空即可。在后一种情况中,可以通过把元素放在f(k)位置以实现插入。

   在理想情况下,初始化一个空字典需要的时间为O(b)(b为散列表中位置的个数) ,搜索、插入、删除操作的时间均为O(1)。尽管理想的散列方法在许多场合都适用,但还有许多应用因为关键字变化范围太大而不能创建一个这样的散列表。

 2.1线性开型寻址散列
       当关键字的范围太大,不能用理想方法表示时,可以采用比关键字范围小的散列表以及把多个关键字映射到同一位置的散列函数。虽然有多种函数映射方法,但最常用的还是除法映射。
除法散列函数的形式如下:f(k) = k%D,其中k 为关键字,D是散列表的大小(即位置数),%为求模操作符。散列表中的位置号从0到D-1,若关键字不是正整数类型(如int,long, char, unsigned char等) ,则在计算f(k)之前必须把它转换成非负整数。对于一个长字符串,可以采用取其2个字母或4个字母而变成无符号整数或无符号长整数的方法。f ( k )是存储关键字为k的元素的起始桶。在良性情况下 ,起始桶中所存储的元素即是关键字为K的元素。

2.2 C++实现(采用线性开型寻址的散列表)
     在类定义中假定散列表中每个元素的类型为E,每个元素都有一个类型为K的key域。key是用来计算起始桶的,因此类型K必须能够适应取模操作%。散列表使用了两个数组,ht和empty。当且仅当ht[i]中 不含有元素时,empty[i]为true。
/********************************HashTable定义*************************/
template<class E, class K>
class HashTable {
public:
HashTable(int divisor = 11);
~HashTable() {delete [ ] ht; delete [] empty;}
bool Search(const K& k, E& e) const;
HashTable<E,K>& Insert(const E& e);
void Output(ostream& out) const;
private:
int hSearch(const K& k) const;
int D; // 散列函数的除数
E *ht; // 散列数组
bool *empty; // 一维数组
};
template<class E, class K>
HashTable<E,K>::HashTable(int divisor)
{// 构造函数
D = divisor;
// 分配散列数组
ht = new E[D];
empty = new bool[D];
// 将所有桶置空
for (int i = 0; i < D; i++)
empty[i] = true;
}


template<class E, class K>
int HashTable<E,K>::hSearch(const K& k) const
{// 查找一个开地址表
// 如果存在,则返回k的位置
// 否则返回插入点(如果有足够空间)
int i = k%D; // 起始桶
int j = i; // 在起始桶处开始
do {
if (empty[j] || ht[j] == k) return j;
j = (j + 1) % D; // 下一个桶
} while (j!=i); // 又返回起始桶
return j; // 表已经满
}


template<class E, class K>
bool HashTable<E,K>::Search(const K& k, E& e) const
{// 搜索与k匹配的元素并放入e
// 如果不存在这样的元素,则返回f a l s e
int b = hSearch(k);
if (empty[b]|| ht[b] != k) return false;
e = ht[b];
return true;
}


template<class E, class K>
HashTable<E,K>& HashTable<E,K>::Insert(const E& e)
{// 在散列表中插入
K k = e; // 抽取k e y值
int b = hSearch(k);
// 检查是否能完成插入
if (empty[b]) 
{
empty[b] = false;
ht[b] = e;
return *this;
}


// 不能插入, 检查是否有重复值或表满
//if (ht[b] == k) throw BadInput(); // 有重复


}


template<class E, class K>
void HashTable<E,K>::Output(ostream& out) const  
{  
	for (int i = 0; i < D; i++)
	{
     if (!empty[i])
		cout<<ht[i]<<" ";
	}


}  
//重载<<   
template<class E, class K>
ostream& operator<<(ostream& out, const HashTable<E,K>& x)  
{  
    x.Output(out);   
    return out;  
}  


主函数调用:
#include<iostream>
#include"dict.h"
using namespace std;
int main()
{
	SortedChain<int ,int> myChain;
	myChain.DistinctInsert(10).DistinctInsert(20).DistinctInsert(3);
	cout<<myChain<<endl;


	HashTable<int,int> myHash;
    
	myHash.Insert(10).Insert(1).Insert(4);
     cout<<myHash<<endl;
	return 0;
}


2.3性能分析
设b为散列表中桶的个数。散列函数中D为除数且b=D。初始化表的时间为O(b)。当表中有n个元素时,最坏情况下插入和搜索时间均为O(n)。当所有n个关键字值都在同一个桶中时即出现最坏情况。通过比较散列在最坏情况下的复杂性与线性表在最坏情况下的复杂性,可以看到二者完全相同。
  但散列的平均性能还是相当好的。用Un 和Sn 来分别表示在一次成功搜索和不成功搜索中平均搜索的桶的个数。对于线性开型寻址,有如下的公式:
Un=1/2(1+1/(1-a)^2)
Sn=1/2(1+1/(1-a))
其中a=n/b为负载因子。
所以若a=0.5,则在不成功搜索时平均搜索的桶的个数为2.5个,成功搜索时则为1.5个。当a= 0.8时,为50.5和5.5。此时假设n至少为51。当负载因子为0.5时,采用线性开型寻址散列表的平均性能要比线性表好得多。

2.4确定D
      在实际应用过程中,D的选择对于散列的性能有着重大的影响。当D为素数或D没有小于20的素数因子时,可以使性能达到最佳(D等于桶的个数b)。
为了确定D的值,首先要了解影响成功搜索和不成功搜索性能的因素。通过Un 和Sn 的公式,可以确定最大的值。根据n 的值(或是估计值)和的值,可以得到b 的最小许可值。然后
找到一个比b大的最小的整数,该整数要么是素数,要么没有小于20的素数因子。这个整数即可作为D和b的值。

例:设计一个有近1000个元素的散列表。要求成功搜索时平均搜索的桶的个数不得超过4,不成功搜索时平均搜索的桶的个数不超过50.5。由Un 的公式,可得到a≤0.9,由Sn的公式,可以得到4≥0.5 +1/(2 (1-a)) 或a≤6/7。因此, a≤min{0.9, 6/7}=6/7。因此b最小为(7n/6)=1167.b=D=37*37 =1369,应为一个比较好的值(虽然不是最小的选择)。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值