数据结构四——哈希表

散列表(哈希表)

一、实现一个基于链表法解决冲突问题的散列表

哈希思想就是通过哈希函数f(key)找到在表中的位置。aKey和bKey不相等时,f(aKey) 可能等于 f(bKey)。这种现象称为冲突。 通常有两类方法处理冲突:开放定址法和拉链法。前者是将所有结点均存放在散列表T[0…m-1]中;后者通常是将互为同义词的结点链成一个单链表,而将此链表的头指针放在散列表T[0…m-1]中。
1、开放定址法
开放地址法解决冲突的方法
 用开放定址法解决冲突的做法是:当冲突发生时,使用某种探查(亦称探测)技术在散列表中形成一个探查(测)序列。沿此序列逐个单元地查找,直到找到给定 的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。查找时探查到开放的 地址则表明表中无待查的关键字,即查找失败。
开放寻址法 Hi=(H(key) + di) MOD m,i=1,2,…,k(k<=m-1),其中H(key)为散列函数,m为散列表长,di为增量序列

从而有三种取法:

di=1,2,3,…,m-1,称线性探测再散列
di=12,-12,22,-22,⑶2,…,±(k)2,(k<=m/2)称二次探测再散列
di=伪随机数序列,称伪随机探测再散列
在这里插入图片描述
2、拉链法
拉链法解决冲突的方法
 拉链法解决冲突的做法是:将所有关键字为同义词的结点链接在同一个单链表中。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数 组T[0…m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。在拉链法中,装填因子α可以大于 1,但一般均取α≤1。
在这里插入图片描述
如图,取表长为8,故散列函数为h(key)=key%8,散列表为T[0…7]。
该方法将所有哈希地址相同的结点构成一个单链表,单链表的头结点存在哈希数组里,链地址法常出现在经常插入和删除的情况下,此时,哈希表的插入/删除/查找都是O(1)的时间复杂度。

#include "stdafx.h"
#include "cstdlib"
#define NULLKEY -1  //空关键字值
#define DELKEY -2   //被删关键字值
typedef int KeyType;  
typedef struct 
{
	KeyType key;  //关键字域
	int count;        //探测次数域
}HashTable;
 
//插入和建表
//把关键字K插入到哈希表中
void InsertHT(HashTable ha[], int &n, int m, int p, KeyType k)
{
 
	//n为哈希表总元素个数
	//p为取余的模,m也就是哈希表创建时总存储空间个数
 
	int i,adr;
	adr = k % p;   //计算哈希函数值
	if (ha[adr].key == NULLKEY || ha[adr].key == DELKEY)  //k可以直接放在哈希表中
	{
		ha[adr].key = k;
		ha[adr].count = 1;
	}
	else
	{
		i = 1;//i记录k发生冲突的次数
		while (ha[adr].key != NULLKEY || ha[adr].key != DELKEY)
		{
			adr = (adr + 1) % m;
			i++;
		}
		ha[adr].key = k;
		ha[adr].count = i;//记录发生冲突次数
 
	}
	n++;        //哈希表总元素个数
}
 
 
void createHT(HashTable ha[], int &n, int m, int p, KeyType keys[], int n1)
{
	//n1为keys长度
	for (int i = 0; i < m; i++)
	{
		ha[i].key = NULLKEY;
		ha[i].count = 0;
	}
	n = 0;
	for (int i = 0; i < n1; i++)
	{
		InsertHT(ha, n, m, p, keys[i]);
	}
 
}
 
//只能在被删元素上做被删标记,不能把被删元素删除
bool  deleteHT(HashTable ha[], int &n, int m, int p, KeyType key)
{
	int adr;
	adr = key % p;
	while (ha[adr].key != NULLKEY || ha[adr].key != key)
	{
		adr = (adr + 1) % m;  //线性探测法
	}
	if (ha[adr].key == key)
	{
		ha[adr].key = DELKEY;
		return true;
	}
	else
		return false;
}
 
void searchHT(HashTable ha[], int m, int p, KeyType key)
{
	int adr;
	int i = 1;//查找次数
	adr = adr % p;
	while (ha[adr].key != NULLKEY && ha[adr].key != key)
	{
		i++;                         //累计关键字的比较次数
		adr = (adr + 1) % m;//线性探测
	}
 
	if (ha[adr].key == key)
		printf("Search Successfully! 关键字%d,比较%d次\n", key, i);
	else
		printf("Search failed! 关键字%d,比较%d次\n", key, i);
 
}
 
void ASL(HashTable ha[], int n, int m, int p)
{
	int i, s,j;
	int success,unsuccess;
	for (i = 0; i < m; i++)
	{
		if (ha[i].key != NULLKEY)
			success += ha[i].count;  //count为查询该关键字的时候比较次数
	}
	printf("成功情况下ASL(%d)=%g\n", n, success*1.0 / n);
	
	for (i = 0; i < p; i++)
	{
		//s记录该关键字的探测次数
		//j为移动的指针
		s = 1; j = i;
		while (ha[j].key != NULLKEY)
		{
			s++;
			j = (j + 1) % m;
 
		}
		unsuccess += s;
	}
	printf("不成功情况下ASL(%d)=%g\n", n, unsuccess*1.0 / p);
}
 
 
typedef int KeyType;
typedef struct node
{
	struct node *next;
	KeyType key;
}NodeType;
 
typedef struct
{
	NodeType *firstp;
}HashTable2;
 
 
void insertHT2(HashTable2 ha[], int &n, int p, KeyType k)
{
	int adr;
	adr = k % p;
	NodeType *q;
 
	q = (NodeType*)malloc(sizeof(NodeType));
	q->next = NULL;
	q->key = k;
	if (ha[adr].firstp == NULL)//若单链表adr为空
		ha[adr].firstp = q;
	else //头插法
	{
		q->next = ha[adr].firstp;
		ha[adr].firstp = q;
	}
	n++; //哈希表中元素个数
}
 
void createHS(HashTable2 ha[], int &n, int m, int p, KeyType keys[], int n1) //n1为keys列表长度
{
	for (int i = 0; i < m; i++)
		ha[i].firstp = NULL;
 
	n = 0;
 
	for (int i = 0; i < n1; i++)
		insertHT2(ha, n, p, keys[i]);
}
 
//删除算法,要考虑首结点是否为所要删除的结点,若不是,另外开辟前驱结点prep来删除
bool deleteHS2(HashTable2 ha[], int &n, int m, int p, KeyType k)
{
	int adr;
	adr = k % p;
	NodeType *q, *prep;
 
	q = ha[adr].firstp;
 
	if (q == NULL)
		return false;
 
	if (q->key == k)
	{
		ha[adr].firstp = q->next;
		free(q);
		n--;
		return true;
 
	}
 
	//走到了这一步,说明首结点不为k
 
	prep = q; q = q->next;
	while (q != NULL)
	{
		if (q->key == k)
		{
			break;
		}
		q = q->next;
	}
 
	if (q != NULL)
	{
		prep->next = q->next;
		free(q);
		n--;
		return true;
	}
	else return false;
}
 
void searchHS2(HashTable2 ha[], int p, KeyType k)
{
	NodeType *q;
	int i=0,adr;
	adr = k % p;
	q = ha[adr].firstp;
	while (q != NULL)
	{
		i++;
		if (q->key == k)
			break;
		q = q->next;
	}
	if (q != NULL)
		printf("Search successfully %d 比较%d次\n", k, i);
	else
		printf("failed %d比较%d次\n", k, i);
}
 
 
void ASL2(HashTable2 ha[], int n, int m)
{
	int success, unsuccess;
	NodeType *q;
	for (int i = 0; i < m; i++)
	{
		q = ha[i].firstp;
		int s = 0;
		while (q != NULL)
		{
			s++;
			q = q->next;
			success += s;
		}
		unsuccess += s;//走到这一步,说明找不到
	}
	
}
#define MAXSIZE 100
typedef int KeyType;
typedef struct
{
	KeyType key;
	int link;
}IdxType;
 
int idxSearch(IdxType I[], int b, int R[], int n, KeyType k)
{
	int s = (n + b - 1) / b;  //s为每块的元素个数,应为[n/b] 例如25个元素,分为5块,那么每块五个元素
	int low, high = b- 1,i; //先在索引表中进行折半查找
 
	while (low <= high)
	{
		int mid = (low + high) / 2;
		if (I[mid].key >=k)
		{
			high = mid - 1;
		}
		else
		{
			low = mid + 1;
		}
 
	}
	//应该先在索引表的high+1块中寻找,再在主数据表中进行顺序查找
	i = I[high + 1].link;
	while (i<=I[high + 1].link + s - 1 && R[i] != k)
	{
		i++;
	}
 
	if (i <= I[high + 1].link + s - 1)
		return i + 1;
 
	else return 0;
 
}

二、实现一个 LRU 缓存淘汰算法

缓存是一种提高数据读取性能的技术,比如常见的cpu缓存以及浏览器缓存!但是缓存的大小有限,当缓存用满的时候,哪些数据应该被清理出去,哪些数据应该被保留?

解决方案:FIFO—>先进先出 LFU—> 最少使用 LRU–>最近最少使用
如何使用链表实现LRU缓存淘汰策率呢?
LRU的设计原理就是,当数据在最近一段时间经常被访问,那么它在以后也会经常被访问。这就意味着,如果经常访问的数据,我们需要然其能够快速命中,而不常访问的数据,我们在容量超出限制内,要将其淘汰。
思路:

每次访问的数据都会放在栈顶,当访问的数据不在内存中,且栈内数据存储满了,我们就要选择移除栈底的元素,因为在栈底部的数据访问的频率是比较低的。所以要将其淘汰。

public class LRUCache {
//“潜水”链表节点,抽象
   static class Node{
       //键值对
       private int key;
       private int value;
 
       //维护“潜水”键值对,双向链表
       private Node pre;
       private Node next;
 
       //构造器
       Node(){}
 
       Node(int key,int value){
        this.key = key;
        this.value = value;
    }
}
 
//指定的容量
private int cap;
 
//保留“潜水”双向链表的头尾指针
private Node head;
private Node tail;
 
//保存键值对的map
private HashMap<Integer,Node> map;
 
//构造器参数是:指定的容量
public LRUCache(int capacity) {
    this.cap = capacity;
 
    //初始化头尾节点,这里的头结点是辅助节点
    //head节点不存储任何有效元素
    head = new Node();
    tail = head;
 
    //构造器初试容量这样设置可以保证map
    //不会发生扩容,详见之前的HashMap
    //讲解文章
    map = new HashMap<>((int)(cap/0.75)+1);
}
 
//将指定节点从链表中删除
private void removeNode(Node cur){
    if(cur==tail){
        tail = tail.pre;
 
        tail.next = null;
        cur.pre = null;
    }else{
        cur.pre.next = cur.next;
        cur.next.pre = cur.pre;
 
        cur.pre = null;
        cur.next = null;
    }
}
 
 
//将指定节点追加到链表末尾
private void add(Node cur){
    tail.next = cur;
    cur.pre = tail;
 
    tail = cur;
}
 
//访问一个键值对
public int get(int key) {
    Node cur = map.get(key);
    //不存在这个key
    if(cur==null){
        return -1;
    }else{//存在
     //含义是当前潜水节点已经被访问了
     //将这个节点添加到链表末尾
        removeNode(cur);
        add(cur);
 
        return cur.value;
    }
}
 
//存储一个键值对
public void put(int key, int value) {
    Node cur =  map.get(key);
 
    if(cur==null){
        //put前不存在这个key
        cur = new Node(key,value);
 
       //将该键值对移动到链表末尾
        map.put(key,cur);
        add(cur);
 
        //超出了容量,移除链表头结点
        //后面那个元素(头结点是辅助节点)
       if(map.size()>cap && head!=tail){
           Node outDate  = head.next;
            removeNode(outDate);
 
            //不能忘记这里
             map.remove(outDate.key);
        }
    }else{
 
       //put之前已经存在
       //将这个键值对移到链表末尾即可
        removeNode(cur);
        add(cur);
        //更新这个key的值
        cur.value = value;            
    }
}
 
}
一、 设计课题:哈希表设计 二、 需求分析: 课题的目的和任务:根据数据元素的关键字和哈希函数建立哈希表并初始化哈希表,用开放定址法处理冲突,按屏幕输出的功能表选择所需的功能实现用哈希表对数据元素的插入,显示,查找,删除。 初始化哈希表时把elem[MAXSIZE]、elemflag[MAXSIZE]和count分别置0。创建哈希表时按哈希函数创建哈希表,输入数据元素的关键字时,以“0”结束输入且要求关键字为正整数,数据元素个数不允许超过表长MAXSIZE。 输出的形式:根据所选择的哈希表的功能输出相应提示语句和正确结果。 程序的功能:将一组个数不超过哈希表长度的数据元素,按其关键字和哈希函数存入哈希表中,如果产生冲突用开放定址法处理并找出相应的地址。能实现用哈希表对数据元素的插入,显示,查找,删除。 测试数据: maxsize=10 哈希函数:H(key)=key%7 处理冲突方法: 开放定址法 Hi=(H(key)+di)%13 i=1,2,3,…,9 三、实验概要设计: ADT HashTable { 数据对象:D1={ai|ai∈elem[MAXSIZE],i=0,1,2,…,0≦n≦MAXSIZE } elem [MAXSIZE]是哈希表中关键字的集合,MAXSIZE为哈希表长。} D2={ai|ai∈elemflag[MAXSIZE]是哈希表中有无关键字的标志的集合,MAXSIZE为哈希表长。} 基本操作: Hash(key) 初始条件:数据元素关键字key已存在 操作结果:根据哈希函数计算相应地址,并返回此地址。 InitialHash(HashTable &H) 初始条件:哈希表H已存在 操作结果:初始化哈希表把elem[MAXSIZE]、elemflag[MAXSIZE]和count分别置0。 SearchHash(HashTable &H,int k) 初始条件:哈希表H已存在 操作结果:在开放定址哈希表H中查找关键字为k的元素,若查找成功,并返回SUCCESS;否则返回UNSUCCESS。 InsertHash(HashTable &H,int e) 初始条件:哈希表H已存在 操作结果:查找不成功时插入数据元素e到开放定址哈希表H中,并返回SUCCESS;否则输出已有此数!插入失败!并返回UNSUCCESS。 CreateHash(HashTable &H) 操作结果:构造哈希表。 PrintHash(HashTable H) 初始条件:哈希表H已存在 操作结果:将哈希表存储状态显示输出。 DeleteHash(HashTable &H,int e) 初始条件:哈希表H已存在 操作结果:若通过哈希函数找到相应的位置并且此位置的elemflag[MAXSIZE]==1,删除相应位置的数据元素的关键字并把elemflag[MAXSIZE]==2,否则,输出无此数的提示语。 2. 本程序包括如下功能模块 哈希函数模块,冲突处理模块,哈希表初始化模块,哈希表创建模块,哈希表显示模块,按关键字查找模块,插入模块,删除模块和主程序模块。 、基本操作的算法描述: 1.宏定义 #define MAXSIZE 10 #define SUCCESS 1 #define UNSUCCESS 0 2.数据类型、数据元素的定义 typedef struct { int elem[MAXSIZE]; int elemflag[MAXSIZE]; int count; }HashTable; 3. 哈希表的基本操作的算法描述 //初始化哈希函数 void InitialHash(HashTable &H)/*哈希表初始化*/ { int i; H.count=0; for(i=0;i<MAXSIZE;i++) { H.elem[i]=0; H.elemflag[i]=0; } } //哈希函数 int Hash(int kn) /*哈希函数H(key)=key MOD 7*/ { return (kn%7); } //查找函数 int SearchHash(HashTable &H,int k) /*查找关键字为k的元素*/ { int t,s; s=t=Hash(k); //调用哈希函数 xun: if(H.elem[t]==k&&H.elemflag[t]==1) return SUCCESS; //如果相应的地址上的数等于k,返回1 else if(H.elem[t]!=k&&H.elemflag[t]==1) { //相应地址有数但不等于k并且H.elemflag[t]==1 t=(t+1)%MAXSIZE; // 处理冲突 goto xun; } else return UNSUCCESS; //返回0 } //插入函数 int InsertHash(HashTable &H,int e)/*插入元素e*/ { int p; p=Hash(e); if(SearchHash(H,e) ) //调用查找函数,如果有此数返回1 { cout<<"已有此数!"<<endl; return UNSUCCESS; //插入是吧,返回0 } else //查找失败,返回0 { H.elemflag[p]=1; //把状态标志置1 H.elem[p]=e; //把关键字放入相应的地址 H.count++; //计数加1 return SUCCESS; //插入成功,返回1 } } //创建哈希表的函数 void CreateHash(HashTable &H)/*创建哈希表*/ { int s; int e; cout<<"请输入哈希表:(输入0结束!)"<<endl; cin>>e; while(e) { s=InsertHash(H,e); //调用插入函数 if(!s) { cout<<"此数已存在!"; cin>>e; } else cin>>e; } } //显示函数 void PrintHash(HashTable H) /*显示元素及其位置*/ { cout<<"哈希表地址:"; int i; for(i=0;i<MAXSIZE;i++) cout<<i<<" "; cout<<endl<<"关键字: "; for(i=0;i<MAXSIZE;i++) cout<<H.elem[i]<<" "; cout<<endl<<"关键字标志:"; for(i=0;i<MAXSIZE;i++) cout<<H.elemflag[i]<<" "; } //删除函数 int DeleteHash(HashTable &H,int e) /*删除元素e*/ { int i; int a=0; for(i=0;i<MAXSIZE;i++) if(H.elem[i]==e&&H.elemflag[i]==1) { H.elemflag[i]=2; H.count--; H.elem[i]=0; a++; return SUCCESS; } if(!a) { cout<<"无此数!"<<endl; return UNSUCCESS; } } //主函数 void main() { HashTable H; int m,k,p; int R; do{ cout<<endl<<"\t\t******************请选择功能********************"<<endl; cout<<"\t\t\t1.初始化哈希表"<<endl<<"\t\t\t2.创建哈希表"<<endl<<"\t\t\t3.查找" <<endl<<"\t\t\t4.插入"<<endl<<"\t\t\t5.删除"<<endl<<"\t\t\t6.输出哈希表:"<<endl<<"\t\t\t0.退出"<<endl; cout<<"\t\t************************************************"<<endl; cin>>m; switch(m) { case 1: InitialHash(H);break; case 2: CreateHash(H);break; case 3: cout<<endl<<"请输入要查找的关键字:"; cin>>k; p=SearchHash(H,k); if(p) cout<<"查找成功!"<<endl; else cout<<"查找失败!"<<endl; break; case 4: cout<<endl<<"请输入要插入的关键字:"; cin>>R; p=InsertHash(H,R); if(p) cout<<"插入成功!"<<endl; else cout<<"插入失败!"<<endl; break; case 5: cout<<"请输出要删除的关键字:"; cin>>R; p=DeleteHash(H,R); if(p) cout<<"删除成功!"<<endl; else cout<<"删除失败!"<<endl; break; case 6: PrintHash(H);break; case 0: break; default: cout<<endl<<"选择错误!";break; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值