l4-d12 查找

一、概念

1.查找概念

设记录表L=(R1 R2……Rn),其中Ri(l≤i≤n)为记录,对给定的某个值k,在表L中确定key=k的记录的过程,称为查找。
若表L中存在一个记录Ri的key=k,记为Ri.key=k,则查找成功,返回该记录在表L中的序号i(或Ri 的地址),否则(查找失败)返回0(或空地址Null)。

2.查找方法

查找方法有顺序查找、折半查找、分块查找、Hash表查找等等。查找算法的优劣将影响到计算机的使用效率,应根据应用场合选择相应的查找算法。
对查找算法,主要分析其T(n)。查找过程是key的比较过程,时间主要耗费在各记录的key与给定k值的比较上。比较次数越多,算法效率越差(即T(n)量级越高),故用“比较次数”刻画算法的T(n)。
一般以“平均查找长度”来衡量T(n)。

3.平均查找长度

平均查找长度ASL(Average Search Length):对给定k,查找表L中记录比较次数的期望值(或平均值),即:

 

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;
若说明:sqlist r,则(r.data[1],……,r.data[r.len])为记录表(R1……Rn), Ri.key为r.data[i].key,  r.data[0]称为监视哨,为算法设计方便所设。

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.总结

•顺序、折半、分块查找和树表的查找中,其ASL的量级在O(n)~O(log2n)之间。
•不论ASL在哪个量级,都与记录长度n有关。随着n的扩大,算法的效率会越来越低。
•ASL与n有关是因为记录在存储器中的存放是随机的,或者说记录的key与记录的存放地址无关,因而查找只能建立在key的“比较”基础上。 

二、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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值