一、概念
1.查找概念
2.查找方法
3.平均查找长度
Pi为查找Ri的概率。等概率情况下Pi=1/n;Ci为查找Ri时key的比较次数(或查找次数)。
4.顺序表的查找
顺序表,是将表中记录(R1 R2……Rn)按其序号存储于一维数组空间
记录Ri的类型描述如下:
typedef struct
{ keytype key; //记录key//
…… //记录其他项//
} Retype;
顺序表类型描述如下:
#define maxn 1024 //表最大长度//
typedef struct
{ Retype data[maxn]; //顺序表空间//
int len; //当前表长,表空时len=0//
} sqlist;
5.顺序查找算法及分析
算法思路 设给定值为k,在表(R1 R2……Rn)中,从Rn开始,查找key=k的记录。
int sqsearch(sqlist r, keytype k)
{ int i;
r.data[0].key = k; //k存入监视哨//
i = r.len; //取表长//
while(r.data[i].key != k) i--;
return (i);
}
设Ci(1≤i≤n)为查找第i记录的key比较次数(或查找次数):
若r.data[n].key = k, Cn=1;
若r.data[n-1].key = k, Cn-1=2;
……
若r.data[i].key = k, Ci=n-i+1;
……
若r.data[1].key = k, C1=n
故ASL = O(n)。而查找失败时,查找次数等于n+l,同样为O(n)。
对查找算法,若ASL=O(n),则效率是很低的,意味着查找某记录几乎要扫描整个表,当表长n很大时,会令人无法忍受。
6.折半查找算法及分析
算法思路
对给定值k,逐步确定待查记录所在区间,每次将搜索空间减少一半(折半),直到查找成功或失败为止。
设两个游标low、high,分别指向当前待查找表的上界(表头)和下界(表尾)。mid指向中间元素。
查找k=20:
查找失败:k=85
int Binsearch(sqlist r, keytype k) //对有序表r折半查找的算法//
{ int low, high, mid; low = 1;high = r.len;
while (low <= high)
{ mid = (low+high) / 2;
if (k == r.data[mid].key) return (mid);
if (k < r.data[mid].key) high = mid-1;
else low = mid+1;
}
return(0);
}
7.分块查找算法及分析
8.总结
二、hash表原理
1.查找方法
理想的查找方法是:对给定的k,不经任何比较便能获取所需的记录,其查找的时间复杂度为常数级O(C)。
这就要求在建立记录表的时候,确定记录的key与其存储地址之间的关系f,即使key与记录的存放地址H相对应:
当要查找key=k的记录时,通过关系f就可得到相应记录的地址而获取记录,从而免去了key的比较过程。
这个关系f就是所谓的Hash函数(或称散列函数、杂凑函数),记为H(key)。
它实际上是一个地址映象函数,其自变量为记录的key,函数值为记录的存储地址(或称Hash地址)。
不同的key可能得到同一个Hash地址,即当keyl≠key2时,可能有H(key1)=H(key2),此时称key1和key2为同义词。这种现象称为“冲突”或“碰撞”,因为一个数据单位只可存放一条记录。
一般,选取Hash函数只能做到使冲突尽可能少,却不能完全避免。这就要求在出现冲突之后,寻求适当的方法来解决冲突记录的存放问题。
根据选取的Hash函数H(key)和处理冲突的方法,将一组记录(R1 R2……Rn)映象到记录的存储空间,所得到的记录表称为Hash表,如图:
选取(或构造)Hash函数的方法很多,原则是尽可能将记录均匀分布,以减少冲突现象的发生。以下介绍几种常用的构造方法。
直接地址法、平方取中法、叠加法、保留除数法、随机函数法...
2.保留除数法
又称质数除余法,设Hash表空间长度为m,选取一个不大于m的最大质数p,令: H(key)=key%p
设记录的key集合k={28,35,63,77,105……},若选取p=21=3*7(包括质数因子7),有:
key:28 35 63 77 105 ……
H(key)=key%21: 7 14 0 14 0 ……
使得包含质数因子7的key都可能被映象到相同的单元,冲突现象严重。
若取p=l9(质数),同样对上面给定的key集合k,有:
key:28 35 63 77 105
H(key)=key%19: 9 16 6 1 10
H(key)的随机度就好多了。
3.处理冲突的方法
冲突是指:表中某地址j∈[0,m-1]中己存放有记录,而另一个记录的H(key)值也为j。
选取随机度好的Hash函数可使冲突减少,一般来讲不能完全避免冲突。设Hash表地址空间为0~m-l(表长为m):
处理冲突的方法一般为:在地址j的前面或后面找一个空闲单元存放冲突的记录,或将相冲突的诸记录拉成链表。
在处理冲突的过程中,可能发生一连串的冲突现象,即可能得到一个地址序列H1、H2……Hn,Hi∈[0,m-l]。H1是冲突时选取的下一地址,而H1中可能己有记录,又设法得到下一地址H2……直到某个Hn不发生冲突为止。这种现象称为“聚积”,它严重影响了Hash表的查找效率。
冲突现象的发生有时并不完全是由于Hash函数的随机性不好引起的,聚积的发生也会加重冲突。
还有一个因素是表的装填因子α,α=n/m,其中m为表长,n为表中记录个数。一般α在0.7~0.8之间,使表保持一定的空闲余量,以减少冲突和聚积现象。
4.开放地址法
当发生冲突时,在H(key)的前后找一个空闲单元来存放冲突的记录,即在H(key)的基础上获取下一地址:
Hi=(H(key)+di)%m
其中m为表长,%运算是保证Hi落在[0,m-l]区间;
di为地址增量。di的取法有多种:
(1)di=1,2,3,……(m-1)——称为线性探查法;
(2)di=12,-12,22,-22,……——称为二次探查法。
设记录的key集合k={23,34,14,38,46,16,68,15,07,31,26},记录数n=11。
令装填因子α=0.75,取表长m= én/αù =15。
用“保留余数法”选取Hash函数(p=13):
H(key)=key%13
5.链地址法
三、hash表的创建、插入与查找
常见命令
插入
//插入
int hash_insert(hash *HT, datatype key) {
linklist p, q;
if (HT == NULL) {
printf("HT is NULL\n");
return -1;
}
if ((p = (linklist)malloc(sizeof(listnode))) == NULL) {
printf("malloc failed\n");
return -1;
}
//孤立节点
p->key = key;
p->value = key % N;
p->next = NULL;
q = &(HT->data[key % N]);
while (q->next && q->next->key < p->key ) {
q = q->next;
}
p->next = q->next;
q->next = p;
return 0;
}
查找
//查找
linklist hash_search(hash *HT, datatype key) {
linklist p;
if (HT == NULL) {
printf("HT is NULL\n");
return NULL;
}
p = &(HT->data[key % N]);
while (p->next && p->next->key != key) {
p = p->next;
}
if (p->next == NULL) {
return NULL;
} else {
printf("found\n");
return p->next;
}
}
头文件hash.h
#ifndef _HASH_
#define _HASH_
#define N 15
typedef int datatype;
typedef struct node {
datatype key;
datatype value;
struct node * next;
}listnode, *linklist;
typedef struct {
listnode data[N];
}hash;
hash * hash_create();//创建
int hash_insert(hash *HT, datatype key);
linklist hash_search(hash *HT, datatype key);
#endif
源程序hash.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "hash.h"
hash * hash_create() {
hash * HT;
if ((HT = (hash *)malloc(sizeof(hash))) == NULL) {
printf("malloc failed\n");
return NULL;
}
memset(HT, 0, sizeof(hash));
return HT;
}
//插入
int hash_insert(hash *HT, datatype key) {
linklist p, q;
if (HT == NULL) {
printf("HT is NULL\n");
return -1;
}
if ((p = (linklist)malloc(sizeof(listnode))) == NULL) {
printf("malloc failed\n");
return -1;
}
//孤立节点
p->key = key;
p->value = key % N;
p->next = NULL;
q = &(HT->data[key % N]);
while (q->next && q->next->key < p->key ) {
q = q->next;
}
p->next = q->next;
q->next = p;
return 0;
}
//查找
linklist hash_search(hash *HT, datatype key) {
linklist p;
if (HT == NULL) {
printf("HT is NULL\n");
return NULL;
}
p = &(HT->data[key % N]);
while (p->next && p->next->key != key) {
p = p->next;
}
if (p->next == NULL) {
return NULL;
} else {
printf("found\n");
return p->next;
}
}
测试程序test.c
#include <stdio.h>
#include "hash.h"
int main(int argc, const char *argv[])
{
hash * HT;
int data[] = {23, 34, 14, 38, 46, 16, 68, 15, 7, 31, 26};
int i;
int key;
linklist r;
if ((HT = hash_create()) == NULL) {
return -1;
}
for (i = 0; i < sizeof(data)/sizeof(int); i++) {
hash_insert(HT, data[i]);//表中插入元素
}
printf("input:");
scanf("%d", &key);
r = hash_search(HT, key);
if (r == NULL)
printf("not found\n");
else
printf("found:%d %d\n", key, r->key);
return 0;
}
四、作业
1、下面关于二分查找的叙述正确的是(D)。
A.表必须有序,表可以顺序方式存储,也可以链表方式存储
B.表必须有序且表中数据必须是整型,实型或字符型
C.表必须有序,而且只能从小到大排列
D.表必须有序,且表只能以顺序方式存储
2、请说出顺序表查找的特点?
(1)随机访问:能在O(1)时间内找到第i个元素;
因为顺序表中的各个数据元素在内存中连续存放,因此可以根据起始地址和数据元素大小立即找到第i个元素--“随机存取”的特性
(2)存储密度高
(3)扩展容量不方便
(4)插入、删除数据元素不方便
3、设hash表长度为14,hash函数是 H(key) = key % 11 ,表中已有数据的关键字为15,38,61,84共四个,现要将关键字为49的结点加到表中,用二次探查法处理冲突,则放入的位置是(D)。
A.8 B. 3 C. 5 D. 9
4、将10个元素散列到100000个单元的哈希表中,则(C)产生冲突。
A. 一定会 B. 一定不会 C. 仍可能会
5、已知一个线性表(38,25,74,63,52,48),采用的散列函数为H(Key)=Key mod 7,将元素散列到表长为7的哈希表中存储。若采用线性探测的开放定址法解决冲突,则在该散列表上进行等概率成功查找的平均查找长度为 ____C___。
A. 1.5 B. 1.7 C. 2.0 D. 2.3