【数据结构】|第十章:跳表和散列

  • 跳表:增加了额外的向前指针的链表。采用随机技术来决定链表的哪些节点应该增加向前指针,以及增加多少个指针。
  • 散列:用来查找、插入、删除的另一种随机方法
    在这里插入图片描述

10.1 字典

在这里插入图片描述

10.2抽象数据类型

ADT
在这里插入图片描述
C++抽象类

# include<utility>//提供std::pair结构,使用pair.first与pair.second来访问数据
template<class K, class E>
class dictionary
{
    public:
    	virtual ~dictionary(){}
 		//返回true,当且仅当字典为空
    	virtual bool empty() const = 0;    
    	//返回字典中数对的数目
     	virtual int size() const = 0;    
    	//返回匹配数对的指针
    	virtual pair<const K,E>* find(const K&) const = 0;    
    	//删除匹配的数对
    	virtual void erase(const K&) = 0;    
    	//往字典中插入一个数对
    	virtual void insert(const pair<const K,E>&) = 0;
};

10.3线性表描述

  • 字典的线性结构描述
    • 线性表描述
    • 跳表描述
    • 散列表描述
      在这里插入图片描述

数组描述

在这里插入图片描述

链表描述

在这里插入图片描述

template<typename K,typename E>
struct pairNode{
    pair<const K,E>element;
    pairNode<const K,E>* next;
};

类sortedChain

template<typename K, typename E>
class sortedChain : public dictionary<K, E>
{
    protected:
        pairNode<const K, E>* firstNode;//指向链表第一个节点的指针
        int dSize;//表中的数对个数
    public:
        sortedChain() { firstNode = NULL; dSize = 0; }
        ~sortedChain();
        bool empty() const { return dSize == 0; }
        int size() const { return dSize; }
    	//返回关键字theKey匹配的数对的指针,若不存在匹配的数对,则返回NULL
        pair<const K, E>* find(const K& theKey) const;
        //插入一个数对thePair,覆盖已经存在的数对
        void insert(const pair<const K, E>& thePair);
    	//删除关键字theKey匹配的数对
        void erase(const K& theKey);
};

方法find

按给定键值搜索数对,若找到则返回对应数对指针,没有则返回空指针

template<class K, class E>
pair<const K,E>* sortedChain<K,E>::find(const K& theKey) const
{
    pairNode<K,E>* currentNode = firstNode;
    
    //搜索关键字为theKey的数对
    //循环结束时有三种可能:1.current指向节点包含一个大于目标键值的数据对;2.包含一个等于目标键值的数据对;3.指向空,链表结束
    while(currentNode != NULL && currentNode->element.first != theKey)
        currentNode = currentNode->next;
    
    //判断是否匹配
    if(currentNode != NULL && currentNode->element.first == theKey)
    	//找到
        return &currentNode->element;
    
    //无匹配的数对
    return NULL;
}

方法insert

  • 插入给定数对p到链表中
    • 若已有节点包含对应键值,更新其值
    • 否则插入一个新节点
  • 按find中方法找到插入位置时,需要同时定位前置节点
template<class K,class E>
void sortedChain<K,E>::insert(const pair<const K,E>& thePair)
{//往字典中插入thePair,覆盖已经存在的匹配的数对
    pairNode<K,E> *p = firstNode, *tp = NULL;//跟踪p
    //移动指针tp,使thePair可以插在tp的后面
    //tp为p的前置节点
    while(p != NULL && p->element.first < thePair.first)
    {
        tp = p;
        p = p-> next;
    }
    
    //检查是否有匹配的数对
    //有匹配的数对
    while(p != NULL && p->element.first == thePair.first)
    {//替换旧值
        p->element.second = thePair.second;
        return;
    }
    //无匹配的数对,为thePair建立新节点
    pairNode<K,E> *newNode = new pairNode<K,E>(thePair, p);
    //在tp之后插入新节点
    if(tp == NULL) firstNode = newNode;
    else tp->next = newNode;
    
    dSize++;
    return;
}

方法erase

  • 若有该键值,删除
  • 没有,不做操作
template<class K, class E>
void sortedChain<K,E>::erase(const K& theKey)
{//删除关键字为theKey的数对
    pairNode<K,E> *p = firstNode, *tp = NULL;
    //搜索关键字为theKey的数对
    //tp为p的前置节点    
    while(p !=NULL && p->element.first < theKey)
    {
        tp = p;
        p = p->next;
    }
    
    //确定是否匹配
    if(p != NULL && p->element.first == theKey)
    {//找到一个匹配的数对
        //从链表中删除p
        if(tp == NULL) firstNode = p->next;//p是第一个节点(头节点)
        else tp->next = p->next;
        delete p;
        dSize--;
    }
}

10.5散列表描述

10.5.1理想散列

  • 散列方法:
    在这里插入图片描述
  • 理想散列:假定散列表中的一个位置最多存放一个数对,每一个可能的键值也映射到唯一的位置。
    在这里插入图片描述

10.5.2散列函数和散列表

在这里插入图片描述

  • 桶(bucket):散列表的每一个位置叫一个桶
  • 起始桶(home bucket):对关键字为k的数对,f(k)是起始桶
  • 散列表的长度或大小:桶的数量。
    • 在除法散列中,桶的数量为D

在这里插入图片描述

在这里插入图片描述

  • 冲突和溢出

在这里插入图片描述

  • 冲突(collision):两个不同的关键字起始桶号相同时就出现冲突
  • 溢出(overflow):存储桶中若没有空间时就发生溢出
    • 解决溢出的方法:
      • 线性探查
      • 链式散列
  • 当每个桶只能存储一个数对时,碰撞和溢出就会同时发生

10.5.3线性探查

在这里插入图片描述

hashTable

template<class K,class E>
class hashTable <K,E>:public dictionary<K,E>
{
    public:
		hashTable(int theDivisor = 11);
		~hashTable() { delete[] table; }
		bool empty() const { return dSize == 0; }
    	int size() const { return dSize; }
    	//返回关键字theKey匹配的数对的指针,若不存在则返回NULL
    	pair<const K,E>* find(const K&)const;
    	//在字典中插入一个数对thePair,若存在关键字相同的数对,则覆盖
 		void insert(const pair<const K,E>&);
	protected:
		int search(const K&)const;
    	pair<const K,E>**table;//散列表
		int divisor;//散列函数的除数
		hash<K>hash;//把类型k映射到一个非负整数
    	int dSize;//字典中数对的个数
}

构造函数

template<class K, class E>
hashTable<K,E>::hashTable(int theDivisor)
{
    divisor = theDivisor;
    dSize = 0;
    
    //分配和初始化散列表数组
    table = new pair<const K,E>* [divisor];
    for(int i = 0; i < divisor; i++)
    	table[i] = NULL;
}

方法search

在这里插入图片描述
在这里插入图片描述

template<class K, class E>
int hashTable<K,E>::search(const K& theKey) const
{//搜索一个公开地址散列表,查找关键字为theKey的数对;
 //如果匹配的数对存在,返回它的位置
 //否则,如果散列表不满,则返回关键字为theKey的数对可以插入的位置
	int i = (int)hash(theKey) % divisor;//起始桶
    int j = i;//从起始桶开始
    do
    {
        if(table[j]==NULL || table[j]->first == theKey)//注意条件顺序
            return j;
        j = (j+1) % divisor;//下一个桶
    }while (j != i);//是否返回到起始桶?
    return j;//表满
}

方法find

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];//找到匹配数对  
}

方法insert

在这里插入图片描述

template<class K, class E>
void hashTable<K,E>::insert(const pair<const K,E>& thePair)
{//把数对thePair插入字典,若存在关键字相同的数对,则覆盖;若表满,则抛出异常
    //搜索散列表,查找匹配的数对
    int b = search(thePair.first);
    //检查匹配的数对是否存在
    //情况2:直接插入新数对
    if(table[b] == NULL)
    {
        //没有匹配的数对,而且表不满
        table[b] = new pair<const K,E>(thePair);
        dSize++;
    }
    else
    {//检查是否有重复的关键字数对或是否表满
    //情况1,修改数对中的值
        if(table[b]->first == thePair.first)
        {
            table[b]->second = thePair.second;
        }
        else
        //情况三,无法插入
            throw hashTableFull();//表满
    }
}

方法erase

在这里插入图片描述

性能分析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 除余散列中除数D的选择
    在这里插入图片描述
    在这里插入图片描述

10.5.4链式散列

在这里插入图片描述
在这里插入图片描述

hashChains

template<class K, class E>
class hashChains : public dictionary<K, E> {
protected:
	sortedChain<K,E> *table;//直接复用sortedChain为桶内链表
	int divisor;
	hash<K> hash;
	int dSize;
public:
	hashChains(int theDivisor = 11) : divisor(theDivisor), dSize(0)
		{ table = new sortedChain<K,E>[divisor]; }
	~hashChains() { delete [] table; }
	bool empty() const { return dSize == 0; }
	int size() const { return dSize; }
	
	pairNode<const K,E>* find(const K& theKey) const
		{ return table[hash(theKey)% divisor].find(theKey); }//使用哈希函数定位到桶后直接搜索

	void insert(const pair<const K,E>& thePair);

	void erase(const K& theKey)
		{ table[hash(theKey) % divisor].erase(theKey); //使用哈希函数定位到桶后直接删除

方法insert

template<class K, class E>
void hashChains<K,E>::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++;
}

与线性探查比较

  • 空间
    在这里插入图片描述

  • 时间复杂性
    在这里插入图片描述

    • 平均性能:
      在这里插入图片描述
      在这里插入图片描述

10.4 跳表表示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

10.6应用——文本压缩

  • LZW(Lempel、Ziv、Welch)压缩文本
    • 基于原始数据,创建一个字典,字典中存放文本中字符串到编码的映射。压缩时用字典中的编码来代替原始数据中的相应字符串
    • 规则
      • 开始,为该文本文件中所有可能出现的字母分配一个代码,构成初始字典
      • LZW压缩器不断地在输入文件的未编码部分中寻找在字典中出现的最长的前缀p,输出前缀p相应的代码,若输入文件中的下一个字符为c,则为pc分配一个代码,并插入字典

示例:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

10.6.2 LZW压缩的实现

//设当前前缀p为空,读取下一个字符c;
循环(当前字符串pc不为空)
{
    if(当前字符串pc在字典中)
		当前前缀p = 当前字符串pc;
 	else
    {
        将当前字符串pc插入到字典中;
        输出当前前缀p的编码;
        p=c;
    }
	读取下一个字符c;
}
输出最后一个当前前缀p的编码;

10.6.3 LZW解压缩方法

  • 解压时,需要重新构建字典
    • 把分配给单一字母的代码插入字典中
    • 输入第一个代码,用相应的文本(第一个代码一定对应于一个单一的字母)代替
    • 设当前输入代码为p,q为p前面的代码。两种情况:1)在字典中;2)不在字典中
      • 1)当p在字典中时
        • 找到与p相关的文本text(p)并输出
        • text(q)fc(p)插入字典
      • 2)当p不在字典中, 此时只会是一种情况,
        • text(p)=text(q)fc(q)
        • 输出text(q)fc(q)(p,text(p))插入字典
        • qp对应文本串:text(q)text(q)fc(q)
          在这里插入图片描述
          实现
(1)把分配给单一字母的代码插入字典中。
(2)输入第一个代码q,输出相应的文本(第一个代码一定对应于一个单一的字母)(3)循环:
	输入下一个代码p;
	if(p在字典中)
	{
        输出代码p对应的文本串text(p);text(q)fc(p)及代码插入到字典中
    }
	else
    {
		text(p)=text(q)fc(q);
		输出代码p对应的文本串text(q)fc(q);text(q)fc(q)及代码插入到字典中
    }
	q=p;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值