目录
→ 查找表
→ 平均查找长度
→ 查找表的结构
→ 查找的应用范围:
→ 算法实现:
→ 算法改进:
→ 算法实现:
→ 概述
↓ 除留余数法
↓ 数字分析法
↓ 平方取中法
↓ 分段叠加法
↓ 基数转换法
→ 处理冲突的方法
↓ 开放地址法
→ 线性探测再散列
→ 二次探测再散列
→ 随机探测再散列
↓ 链地址法
→ 运行结果:
→ 算法实现:
一、概述
查找表
平均查找长度
查找表的结构
第一类查找算法——比较式查找算法
1、基于线性表的查找
2、基于树的查找
第二类查找算法——散列法
零比较次数,不需要进行关键字间的比较。
二、基于线性表的查找、
查找的应用范围:
顺序查找(线性查找)
顺序表数据类型描述:
#define MAXSIZE 100
type struct
{
KeyType key; //关键字
OtherType other_data; //其他数据类型
}RecordType;
type struct
{
RecondType r[MAXSIZE+1];
int length; //序列长度
}SeqRList; //记录顺序表类型
SeqRList L;
算法实现:
int SeqSearch(SeqRList L,KeyType K)
{
int i=L.length;
while(i>=1&&L.r[i].key!=K)
i--;
return i;
}
算法改进:
加“监视哨”,记录序列从下标0开始,让下标为0的位置作为“监视哨”。减少循环判断条件,提高执行效率。
int SeqSearch(SeqRList L,KeyType K) { L.r[0].key=K; //监视哨 int i=L.length; while(L.r[i].K!=K) i--; return i; }
折半查找(二分查找)
任何一个线性表,若其中的所有数据元素按关键字的大小呈非递增或非递减排列,称为有序表。
折半查找,应用范围必须采用顺序存储(数组)结构,并且关键字按大小排列。
查找过程为:
每次将待查范围的中间位置上的数据元素 mid 与关键字 K 比较,相等则查找成功;否则,该位置将此表分为前后两个区,如果关键字K大于mid,在后区再次进行查找,相反,在前区进行查找。
算法实现:
折半查找非递归实现 int BinSearch(SeqRList L,KeyType K) { int low=1; int high=L.length; while(low<=high) { int mid=(low+heigh)/2; if(K==L.r[mid].key) return mid; else if(K<L.r[mid].key) heigh=mid-1; else low=mid+1; } return 0; }
折半查找性能分析——判定树:
二叉判定树的根结点为当前区间中间位置的序号,左子树代表前半部分子表,右子树代表后半部分子表。
索引查找(分块查找)
索引查找基本思想:
1、线性表分成若干块,每块包含若干记录,块中存放的记录都是任意的,但块与块之间有序。
2、建立一个索引表,把每块若干记录中的最大关键字值,及每块的第一个记录和最后一个记录在表中的位置存放在索引表中。
查找时,先在待查找的关键字在索引表中(折半查找或顺序查找)查找,确定关键字的结点在哪一个分块中,然后根具顺序查找在分块中继续查找。
索引查找的优点:
在线表中插入或删除一个结点时,只要找到该结点属于的块,在块内进行操作,块内存放是任意的。
三、基于树的查找(待更新)
四、散列
概述
散列方法是计算式方法,又称“hash法”或“关键字地址计算法”。
哈希函数:计算关键字记录的地址单元。
哈希表:把每个关键字通过哈希函数计算存入到其地址单元中。
查找时,通过哈希函数直接查找到关键字对应的地址。
散列方法的核心就是由哈希函数决定关键字与散列地址之间对应的关系。
地址冲突:不同的关键字对应的地址单元相同,同义字对应的地址单元相同(如按照首字母字典序方式存储,都是以字母a开头的地址单元相同)
哈希函数的构造方法
构造函数考虑的因素:
1、计算哈希函数需要的时间
2、关键字的长度
3、散列表的大小
4、关键字的分布情况
5、记录查找的频率
除留余数法
假设表长为m,p为小于等于表长m的最大素数,哈希函数为H(k)=key%p;
例如:
数字分析法
假设关键字由多个数字组成,分析关键字集中地频度,估计出全体关键字每一位上的数字出现的频度,从中提取分布均匀的若干位或组合作为哈希地址。(例如经过分析,各个关键字第4~6位中取值均匀。)
平方取中法
通过关键字平方扩大相近数的差别,然后根据表长长度取中间的几位数作为哈希函数值。(相乘比除余法可以提高算法的效率,两数相乘,其中间结果的几位数与乘数的每一位都相关。)
分段叠加法
将关键字分割成相同的几部分,然后这几部分进行叠加,叠加结果(舍弃进位作为散列地址)
基数转换法
将关键字看成另一种的进制数,然后再转换成原来的进制数,结果中选择几位作为地址。
处理冲突的方法
开放地址法
基本思想:
当关键字的初始化地址冲突时,以h0为基础查找下一个h1(增量di)的地址,如果再次发生冲突,再次从h0为基础查找下一个h2的散列地址,直到找到一个不冲突的地址。
线性探测再散列
先根据关键字哈希函数确定散列地址,然后依次按表中顺序进行存放,如果地址有冲突根据线性探测再散列继续查找下一个位置。每判断一个地址单元是否为空,比较次数多一次。
二次探测再散列
随机探测再散列
di为伪随机数,需要建立一个随机数发生器,给定一个随机数作为起始点。
链地址法
根据关键字与哈希函数确定散列地址,然后根据每个关键字确定的散列地址进行存放,如果地址有冲突,将冲突的链表记录在同一单链表中。 此方法更适合于表长不确定,链表上动态申请结点。
散列表查找的性能分析
a越小,冲突的可能性越小,但存储空间利用率低,反之相反。哈希表的平均查找长度与装填因子a相关,而与待散列记录的数目n无关。
哈希表查找算法实现
开放地址法为例:
运行结果:
算法实现:
#include<stdio.h> #include<stdlib.h> #define HASHSIZE 12 #define n 11 //元素个数 typedef struct data { int key; //可以添加其他类型 }datatype; typedef struct { datatype data; int time; }Hashtable; Hashtable ht[HASHSIZE] = { 0 }; //=== 哈希函数(除留余数法) === int HashFunc(int key) { return key % n; } //=== 开放地址法解决冲突 === int Collision(int di) { return (di + 1) % n; } //=== 哈希表的插入 === void InsertHash(Hashtable *ht, int key) { int address = HashFunc(key); //解决key经过哈希函数计算地址与哈希表产生地址冲突 while (ht[address].data.key!=0 ) { address = Collision(address); } ht[address].data.key = key; } //=== 构建哈希表 === void CreateHash() { int i; for (i = 0; i <HASHSIZE; i++) { ht[i].data.key= 0; ht[i].time = 0; } printf("-----请输入待查序列-----\n"); for (i = 0; i <n; i++) { int temp; scanf("%d", &temp); InsertHash(ht, temp); } /*for (i = 0; i < n; i++) { InsertHash(ht, demo[i]); }*/ } //=== 打印哈希表 === void PrintHash() { for (int i = 0; i < n; i++) printf("%d ", ht[i].data.key); printf("\n"); } //=== 哈希查找 === void SearchHash(int search) { int address = HashFunc(search); int count = 0; while (ht[address].data.key != search&&ht[address].data.key!=0) { address = Collision(address); count++; //设置一个计数器,整个哈希表查找一遍仍不满足时,则不存在需要查找的值 if (count==n) printf("查找失败,输入的元素不在待查序列中"); } if (ht[address].data.key == search) printf("其在哈希表中的下标位置为:%d\n", address); } main() { /*int demo[n] = { 11,2,3,1,4,5,6,8,7,10,9 }; CreateHash(&demo);*/ CreateHash(); printf("-----打印哈希表中的关键字-----\n"); PrintHash(); printf("-----请输入要查找的数-----\n"); int search = 0; scanf("%d", &search); SearchHash(search); }