Download - 第九章 查找

Transcript
Page 1: 第九章    查找

第九章 查找 有关查找的基本概念

查找表,关键字,查找成功,查找失败 静态查找表,动态查找表 平均查找长度

静态查找表 动态查找表 哈希表(散列表)

Page 2: 第九章    查找

§9.1 静态查找表 顺序表的查找

顺序查找

i

12 23 6 99 45 30 51 28

1 2 3 4 5 6 7 8

Page 3: 第九章    查找

顺序查找

int Sq_search(int A[],int n,int e) {

// 在无序表中查找元素 e ,查找成功时,返回元素在表中的位置 // 否则返回 0

i=n;

while (i>0 && A[i]!=e) i--;

return i;

}

12 23 6 99 45 30 51 28

1 2 3 4 5 6 7 8

i

Page 4: 第九章    查找

顺序查找 无序表上的顺序查找,设监视哨

12 23 6 99 45 30 51 28

1 2 3 4 5 6 7 8

i

12 23 6 99 45 30 51 28

1 2 3 4 5 6 7 8

i监视哨

0

Page 5: 第九章    查找

无序表上的顺序查找

int Sq_search(int A[],int n,int e) {

// 在无序表中查找元素 e ,查找成功时,返回元素在表中的位置 // 否则返回 0

i=n; A[0]=e; // 监视哨:下标为 0 的位置存放待查找的元素 while (A[i]!=e) i--;

return i;

}

12 23 6 99 45 30 51 28

1 2 3 4 5 6 7 8

i监视哨

0

Page 6: 第九章    查找

平均查找长度 查找性能:平均查找长度( ASL )

为确定记录在查找表中的位置,需和给定值进行比较的关键字个数的期望值称为查找算法在查找成功时的平均查找长度。

n

iic

1ipASL

其中, n 是查找表中的长度, Pi 为查找第 i 个元素的概率ci 是找到表中其关键字与给定值相等的记录时 ( 第 i 个记录 ) ,和给定值已进行过比较的关键字的个数。

Page 7: 第九章    查找

无序表上的顺序查找int Sq_search(int A[],int n,int e) {

// 在无序表中查找元素 e ,查找成功时,返回元素在表中的位置 // 否则返回 0

i=n; A[0]=e;

while ( A[i]!=e) i--;

return i;

}

2

1n)1in(

n

1ASL

n

1i

成功查找成功:

1nASL 失败查找失败:

12 23 6 99 45 30 51 28

1 2 3 4 5 6 7 8

i监视哨

0

Page 8: 第九章    查找

有序表上的顺序查找

6 12 23 28 31 45 51 991 2 3 4 5 6 7 80

监视哨

例如: e=23

成功时,与顺序查找的平均查找长度( ASL )相同,失败时无须与所有元素进行比较

Page 9: 第九章    查找

有序表上的顺序查找

6 12 23 28 31 45 51 991 2 3 4 5 6 7 8

230

监视哨 >e=23?

Page 10: 第九章    查找

有序表上的顺序查找

6 12 23 28 31 45 51 991 2 3 4 5 6 7 8

230

监视哨 >e =23?

Page 11: 第九章    查找

有序表上的顺序查找

6 12 23 28 31 45 51 991 2 3 4 5 6 7 8

230

监视哨 >e =23?

Page 12: 第九章    查找

有序表上的顺序查找

6 12 23 28 31 45 51 991 2 3 4 5 6 7 8

230

监视哨 >e =23?

Page 13: 第九章    查找

有序表上的顺序查找

6 12 23 28 31 45 51 991 2 3 4 5 6 7 8

230

监视哨 >e =23?

Page 14: 第九章    查找

有序表上的顺序查找

6 12 23 28 31 45 51 991 2 3 4 5 6 7 8

230

监视哨 >e =23?

a[i]>e 不成立,停止查找

Page 15: 第九章    查找

有序表上的顺序查找int Sq_search(int A[],int n,int e) {

// 在无序表中查找元素 e ,查找成功时,返回元素在表中的位置 // 否则返回 0

i=n; A[0]=e;

while ( A[i]>e) i--;

if A[i]==e return i;

else return 0;

}

2

1n)1in(

n

1ASL

n

1i

成功

6 12 23 28 31 45 51 99

1 2 3 4 5 6 7 8

i监视哨

0

Page 16: 第九章    查找

有序表上的顺序查找:失败

6 12 23 28 31 45 51 991 2 3 4 5 6 7 8

400

监视哨

2

2n)2in(

1n

1ASL

1n

1i

失败

Page 17: 第九章    查找

静态查找表• 顺序查找

无序表的顺序查找 有序表的顺序查找

• 折半查找

Page 18: 第九章    查找

有序顺序表上的折半查找

6 12 23 28 31 45 51 991 2 3 4 5 6 7 8

low high

mid=[(low+high)/2]

下标

Page 19: 第九章    查找

有序顺序表上的折半查找

6 12 23 28 31 45 51 991 2 3 4 5 6 7 8

low highmide < A[mid]‖23

Page 20: 第九章    查找

有序顺序表上的折半查找

6 12 23 28 31 45 51 991 2 3 4 5 6 7 8

low highmid

e > A[mid]

e==23 mid=[(low+high)/2]

‖23

Page 21: 第九章    查找

有序顺序表上的折半查找:成功

6 12 23 28 31 45 51 991 2 3 4 5 6 7 8

low high

mid

e 等于 A[mid]‖23

Page 22: 第九章    查找

有序顺序表上的折半查找

6 12 23 28 31 45 51 991 2 3 4 5 6 7 8

low highmide > A[mid]‖55

Page 23: 第九章    查找

有序顺序表上的折半查找

6 12 23 28 31 45 51 991 2 3 4 5 6 7 8

low highmide > A[mid]‖55

Page 24: 第九章    查找

有序顺序表上的折半查找

6 12 23 28 31 45 51 991 2 3 4 5 6 7 8

low highmide > A[mid]‖55

Page 25: 第九章    查找

有序顺序表上的折半查找

6 12 23 28 31 45 51 991 2 3 4 5 6 7 8

low high

mide < A[mid]‖55

Page 26: 第九章    查找

有序顺序表上的折半查找:失败

6 12 23 28 31 45 51 991 2 3 4 5 6 7 8

lowhigh

?

Page 27: 第九章    查找

有序顺序表上的折半查找

mid=[(low+high)/2];

if (A[mid] = = e) return mid; // 查找成功 else if (e<A[mid]) high=mid-1; // 下一次到前半区间查找 else low=mid+1; // 下一次到后半区间查找

折半查找方法: 先确定待查记录所在的范围 ( 区间 ) ,若待查记录等于表中间位置

上的记录,则查找成功;否则,缩小查找范围,即若待查记录小于中间位置上的元素,则下一次到前半区间进行查找,若待查记录大于中间位置上的元素,则下一次到后半区间进行查找。

6 12 23 28 31 45 51 991 2 3 4 5 6 7 8

low highmid

Page 28: 第九章    查找

折半查找算法int B_search(int A[],int n,int e) {

// 在有序表中查找元素 e ,若查找成功,则返回元素在表中的位置

// 否则返回 0

low=1; high=n;

while ( low<=high) {

mid=[(low+high)/2];

if (A[mid]==e) return mid; // 查找成功 else if (e<A[mid]) high=mid-1;// 下一次到前半区查找 else low=mid+1; // 下一次到后半区查找 } //end of while

return 0; // 查找失败} //end of B_search

Page 29: 第九章    查找

折半查找法

问题: 若采用链表(单链表或双向链表)作为查找

表的存储结构,能否进行折半查找?

Page 30: 第九章    查找

折半查找判定树 折半查找判定树:描述折半查找过程的二叉树 有序顺序表: a1 , a2 , a3 , a4 , a5 , a6 , a7 , a8

a4

a2 a6

a1 a3 a5 a7

a8

Page 31: 第九章    查找

折半查找判定树a4

a2 a6

a1 a3 a5 a7

a8

ASL 成功 = (1+2+2+3+3+3+3+4)/8

= 21/8 ------- 等概率查找

Page 32: 第九章    查找

查找失败

若查找失败,即表中不存在要查找的元素,平均查找长度又如何呢?

Page 33: 第九章    查找

查找失败

ASL 失败 = ?

a1 a2 a3 a4 a5 a6 a7 a8

Page 34: 第九章    查找

折半查找判定树a4

a2 a6

a1 a3 a5 a7

a8

比 a1 小的所有元

比 a8 大的所有元

大于 a2而小于 a3的所有元

大于 a5而小于 a6的所有元

Page 35: 第九章    查找

折半查找算法int B_search(int A[],int n,int e) {

// 在有序表中查找元素 e ,若查找成功,则返回元素在表中的位置

// 否则返回 0

low=1; high=n;

while ( low<=high) {

mid=[(low+high)/2];

if (A[mid]==e) return mid; // 查找成功 else if (e<A[mid]) high=mid-1;// 下一次到前半区查找 else low=mid+1; // 下一次到后半区查找 } //end of while

return 0; // 查找失败: low>high

} //end of B_search

Page 36: 第九章    查找

折半查找判定树a4

a2 a6

a1 a3 a5 a7

a8

ASL 失败 = (3*7+4*2)/9 = 29/9

Page 37: 第九章    查找

折半查找判定树 n=11a6

a3

a1 a4

a9

a10a7

a11a2 a5 a8

Page 38: 第九章    查找

折半查找判定树 n=11a6

a3

a1 a4

a9

a10a7

a11a2 a5 a8

Page 39: 第九章    查找

折半查找的效率分析 如果查找表中有 n 个元素,那么

对应的折半查找判定树又如何?

查找成功和失败的平均查找长度与有 n个结点的完全二叉树的高度相同。即:

1nlog ASL 2 折半查找

完全二叉树

Page 40: 第九章    查找

折半查找的效率分析 设有序顺序表中有 n 个元素,进

行折半查找设 n=2h-1 ,则描述折半查找的判定树是

高度为 h 的满二叉树设表中每个记录的查找概率相等,即 Pi=

1/n

Page 41: 第九章    查找

折半查找的效率分析 则查找成功时折半查找的平均查

找长度为:

1)1n(log

1)1n(logn

1n

2jn

1CPASL

2

2

h

1j

1jn

1iii

折半查找

Page 42: 第九章    查找

索引顺序表的查找 元素分块有序 建立索引表

13 8 9 20 33 42 44 38 24 48 60 58 74 49 86 5322 12

B1 B2 B3

22

1

48

7

86

13

最大关键字

起始地址( 下标 )

索引表

Page 43: 第九章    查找

索引顺序表的查找:分块查找 在索引顺序表中查找指定元素时,分两步:

先在索引表中确定元素所在的块; 再在块中顺序查找;

13 8 9 20 33 42 44 38 24 48 60 58 74 49 86 5322 12

B1 B2 B3

22

1

48

7

86

13

最大关键字

起始地址( 下标 )

索引表

Page 44: 第九章    查找

索引顺序表的查找:分块查找 在索引顺序表中查找指定元素时,分两步:

先在索引表中确定元素所在的块; 再在块中顺序查找;

块内查找索引表内的查找分块查找 ASLASLASL

Page 45: 第九章    查找

索引顺序表的查找:分块查找 设查找表中的元素可均匀地分为 b 块,每块含有 s 个记录 若在索引表和块内都进行顺序查找,则:

块内查找索引表内的查找分块查找 ASLASLASL

实际上,在索引表中可进行折半查找。

b

1jj

b

1

s

1ii

s

1

1)ss

n(

2

1

2

1s

2

1b

Page 46: 第九章    查找

完全二叉树 n=11

return 111log h 2

Page 47: 第九章    查找

9.2 动态查找表 动态查找表的特点

表结构本身是在查找过程中动态生成的,即对于给定值 key ,若表中存在关键字等于 key 的记录,则查找成功返回,否则插入关键字等于 key 的记录。

动态查找表的主要运算 创建、销毁 查找、插入和删除 遍历

Page 48: 第九章    查找

二叉排序树和平衡二叉树

1. 二叉排序树 ( 二叉检索树、二叉查找树 ) 定义

二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:

1) 若其左子树不空,则左子树上所有结点的值均小于它的根结点的值;

2) 若其右子树不空,则右子树上所有结点的值均大于它的根结点的值

3) 其左、右子树也分别为二叉排序树

Page 49: 第九章    查找

二叉排序树图示45

12 53

3 37 100

90

24 61

78

CAO

ZHAO

DING

CHEN WANG

LI XIA

DU MA

(a) (b)

Page 50: 第九章    查找

二叉排序树的查找运算

查找:查找键值等于 key 的记录 若二叉排序树为空树,则查找

失败,返回; 若根结点的键值等于 key ,则查

找成功,返回; 若根结点的键值大于 key ,则到

根的左子树上继续查找;否则,到根的右子树上继续查找;

45

12 53

3 37 100

90

24 61

78

Page 51: 第九章    查找

二叉排序树的查找算法BiTree SearchBST(BiTree T,keyType key) {// 在 T 指向根的二叉排序树中递归地查找关键字等于 key 的数据元

素,// 若找到,返回指向该结点的指针,否则返回 null

if (T==NULL) return NULL;

else if (T->data.key==key) return T;

else if (T->data.key>key)

return SearchBST(T->lchild,key);

else return SearchBST(T->rchild,key);

}//SearchBST

45

12 53

3 37 100

90

24 61

78

T

Page 52: 第九章    查找

二叉排序树的插入运算

插入:在二叉排序树中插入键值等于 key 的记录 若二叉排序树为空树,则插入元素

作为树根结点; 若根结点的键值等于 key ,则插入

失败; 若 key 小于根结点的键值,则插入

到根的左子树上;否则,插入到根的右子树上;

新插入的结点一定是个叶子结点

45

12 53

3 37 100

90

24 61

78

60

Page 53: 第九章    查找

二叉排序树的插入算法Status InsertBST(BiTree &T,ElemType e) {

// 当二叉排序树中不存在关键字等于 e.key 的数据元素时,// 插入元素 e 并返回 true ,否则返回 false

45

12 53

3 37 100

90

24 61

78

60

if (p) return false; // 键值为 e.key 的结点已经存在s = new BiTnode; s->data = e; s->lchild = s->rchild = null;

if (father==NULL) T = s;

else if (e.key>father->data.key) father->rchild = s;

else father->lchild = s;

}//InsertBST

p = T; father = NULL;

while (p && p->data.key!=e.key) { father = p;

if (e.key>p->data.key) p = p->rchild;

else p = p->lchild;

}//while

Page 54: 第九章    查找

二叉排序树的特点 将一个无序序列的元素依次插入到一棵

二叉排序树上,然后进行中序遍历,可得到一个有序序列。

在二叉排序树上插入元素时,总是将新元素作为叶子结点插入,插入过程中无须移动其他元素,仅需将一个空指针修改为非空。

Page 55: 第九章    查找

二叉排序树的删除运算 若待删除的结点 *p 是个叶子结点,则将 *p 的父结点 *f

的左孩子指针置空 ( 若 *p 是 *f 的左孩子 ) ,或者将 *f 的右孩子指针置空 ( 若 *p 是 *f 的右孩子 ) ,示例

若待删除的结点 *p 是仅有一个孩子的非叶子结点,则将*p 的左孩子 ( 或右孩子 ) 接为 *p 的父结点 *f 的左孩子( 若 *p 是 *f 的左孩子 ) ,或者将 *p 的左孩子 ( 或右孩子 ) 接为 *p 的父结点 *f 的右孩子 ( 若 *p 是 *f 的右孩子 ) ,示例

Page 56: 第九章    查找

二叉排序树的删除运算

若待删除的结点 *p 的两个子树都不为空,则有两种处理方式:

其一是令 *p 的左子树为 *f 的左子树,而 *p 的右子树为 *s 的右子树;示例

其二是令 *p 的直接前驱 ( 或直接后继 ) 代替 *p ,然后再删除其直接前驱 ( 或直接后继 ) ,示例

一般情况

Page 57: 第九章    查找

二叉排序树的删除运算

45

12 53

3 37 100

90

24 61

78

60

删除 24

45

12 53

3 37 100

90

61

78

60

f

p f->lchild = null;

delete p;

return

Page 58: 第九章    查找

二叉排序树的删除运算

45

12 53

3 37 100

90

24 61

78

60

删除 61

45

12 53

3 37 100

90

60

78

24

p

令 *p 的直接前驱 *s 代替 *p ,然后删除 *s

s

return

Page 59: 第九章    查找

二叉排序树的删除运算

45

12 53

3 37 100

90

24 61

78

60

删除 61

45

12 53

3 37 100

90

7824

60

p

s

令 *p 的直接后继 *s 代替 *p ,然后删除 *s

return

Page 60: 第九章    查找

二叉排序树的删除运算

45

12 53

3 37 100

90

24 61

78

60

删除 61

s

令 *p 的左子树为 *f 的左子树

p

f

令 *p 的右子树为 *s 的右子树,然后删除 *p

return

45

12 53

3 37 100

90

6024

78

Page 61: 第九章    查找

二叉排序树的删除运算

45

12 53

3 37 100

90

24 61

78

60

删除 37

45

12 53

3 24 100

90

61

78

60f->rchild = p->lchild;

delete p;

f

p

return

Page 62: 第九章    查找

二叉排序树的删除运算

45

12 53

3 37 100

90

40 61

78

60

删除 37

45

12 53

3 40 100

90

61

78

60f->rchild = p->rchild;

delete p;

f

p

return

Page 63: 第九章    查找

二叉排序树的删除运算

45

12 53

3 37 100

90

10 61

78

60

删除 3

45

12 53

10 37 100

90

61

78

60f->lchild = p->rchild;

delete p;

f

p

return

Page 64: 第九章    查找

删除两个子树均不空的 *p 结点

p

F

P

f

PL PR

(a)

c

F

P

f

PR

(b)

C

p

CLQ

QLS

SL

q

s

*p 的直接前驱在其左子树上最右下方的结点

Page 65: 第九章    查找

删除两个子树均不空的 *p 结点

F

C

f

(c) 删除 *p之后,以PR作为 *s 的右子树

c

CLS

SL

s

PR

c

F

P

f

PR

(b) 删除 *p之前

C

p

CLQ

QLS

SL

q

s

c

F

S

f

PR

(d) 删除 *p之后,以*s 代替 *p

C

p

CLQ

QL SL

q

Page 66: 第九章    查找

二叉排序树的查找性能 若查找成功,则走了一条从根结点到某结点的路径,若查找失败,

则走到一棵空的子树时为止。因此,最坏情况下,其平均查找长度不会超过树的高度。

45

12 53

3 37 100

90

24 61

78

55

n

iic

1ipASL

ASL 成功 =(1+2*2+3*3+4*2+5*2+6*1)/11

=38/11

Page 67: 第九章    查找

二叉排序树的查找性能 ( 续 )

n

iic

1ipASL

ASL 失败 = (2+3*4+4*2+5*3+6*2) /12

= 49/12

45

12 53

3 37 100

90

24 61

78

55

大于 90 且小于 100的

大于 100的数

大于 12且小于 24 的数

Page 68: 第九章    查找

二叉排序树的查找性能 ( 续 ) 若查找成功,则走了一条从根结点到某结点的路径,若

查找失败,则走到一棵空的子树时为止。因此,最坏情况下,其平均查找长度不会超过树的高度。

具有 n个结点的二叉树的高度取决于其形态。45

12 53

3 37 100

90

24 61

78

55

Page 69: 第九章    查找

二叉排序树的形态 关键字序列为 (45,24,53,12,37,93) 所构造的二叉排序树如图 (a) 所示

45

24 53

12 37 93

关键字序列为 (12,24,37,45,53,93) 所构造的二叉排序树如图 (b) 所示

(a)

12

24

37

45

53

93(b) 单枝树

Page 70: 第九章    查找

二叉排序树的形态 ( 续 ) 关键字序列为 (12,93,24,53,37,45) 所构造的二叉排序树如图 (c) 所

示12

93

24

53

37

45(c) 单枝树

如果根据关键字的输入序列构造的二叉树为单枝树,则其平均查找长度与顺序查找相同

Page 71: 第九章    查找

平衡二叉树概念2. 平衡二叉树 (AVL 树 ) 定义:平衡二叉树或者是一棵空树,或者是具有下列性质的二

叉树:它的左子树和右子树都是平衡二叉树,且左、右子树的深度之差的绝对值不超过 1。

45

24 53

12 37 93

(a) 平衡二叉树

12

24

37

45

53

93(b) 非平衡二叉树

平衡因子 定义二叉树中结点的平衡因子 bf等于结点左子树的高度减

去右子树的高度 平衡二叉树中结点的平衡因子为 0、 1或 -1 。

Page 72: 第九章    查找

9.3 哈希表(散列表 )

9.3.1 基本概念

1. 散列

也称为杂凑或哈希。它既是一种查找方法,又是一种存贮方法,称为散列存贮。散列存贮的内存存放形式也称为哈希表或散列表。

散列查找,与前面介绍的查找方法完全不同,前面介绍的所有查找都是基于待查关键字与表中元素进行比较而实现的查找方法,而散列查找是通过计算哈希函数来得到待查关键字的地址,理论上讲,在哈希表中查找元素无须进行关键字间的比较。例如,要找关键字为 k的元素,则只需求出函数值 H(k) ,

H 为给定的哈希函数, H(k) 代表关键字 k 在存贮区中的地址。

Page 73: 第九章    查找

假设有一批关键字序列 18,75,60,43,54,90,46 ,给定哈希函数 H(k)=k % 13 ,存贮区的内存地址从 0 到 15 ,则可以得到每个关键字的散列地址为:

H(18) = 18%13 = 5 H(75) = 75%13 = 10

H(60) = 60%13 = 8 H(43) = 43%13 = 4

H(54) = 54%13 = 2 H(90) = 90%13 = 12

H(46) = 46%13 = 7

于是,根据散列地址,可以将上述 7 个关键字序列存贮到一个一维数组HT(哈希表或散列表)中,具体表示为:

HT

0 1 2 3 4 5 6 7 8 9 10 11 12

54 43 18 46 60 75 90

13 14 15

例如

Page 74: 第九章    查找

9.3 哈希表(散列表 )

因此,一个散列结构是一块地址连续的存储空间,它与一个称为散列函数的函数相关联,该函数是数据记录的关键字到地址空间的映射。

这种存储空间的使用方式,使得

(1) 存储记录时,通过散列函数计算出记录的存储位置并按此存储位置存储记录

记录位置 = Hash( 记录的关键字 )

Hash :关键字→ 地址

(2)访问记录时,同样利用散列函数计算存储位置,然后根据所计算出的存储位置访问记录

Page 75: 第九章    查找

9.3 哈希表(散列表 )

2. 散列技术中的主要问题

表面上看,设置了散列函数后,只需作简单的函数计算就可以实现元素的定位及查找操作,但事实上没有这么简单,概括起来,主要有以下两个问题:

(1)冲突

一般情况下,设计出的散列函数很难是单射的,即不同的关键字对应到同一个存储位置,这样就造成了冲突 ( 碰撞 ) 。

此时,发生冲突的关键字互为同义词。(2) 散列函数的设计

设计一个简单、均匀、存储空间利用率高、冲突少的散列函数是关键。

Page 76: 第九章    查找

9.3 哈希表(散列表 )

3. 散列过程

散列的一般使用方法是,首先根据具体问题的特点 (数据集的特点、存储空间的限制等 ) 设计散列函数和解决冲突的方法,然后执行以下过程:

(1)提取需要插入或访问的记录的关键字,用散列函数计算出存储位置 addr 。

(2) 如果是存储 ( 插入 ) 记录,且 addr 处已被其他记录占用 ( 插入冲突 ) ,则转 (4) ;否则将记录存入该位置,结束。(3) 如果是访问 ( 查找、删除、修改等 ) 记录,则检查 addr 处的记录是否为要访问的记录,若不是 ( 定位冲突 ) ,则转 (4) ;否则读取该记录进行相应的处理,结束。

Page 77: 第九章    查找

9.3 哈希表(散列表 )

(1)提取需要插入或访问的记录的关键字,用散列函数计算出存储位置 addr 。

(2) 如果是存储 ( 插入 ) 记录,且 addr 处已被其他记录占用( 插入冲突 ) ,则转 (4) ;否则将记录存入该位置,结束。

(3) 如果是访问 ( 查找、删除、修改等 ) 记录,则检查 addr处的记录是否为要访问的记录,若不是 ( 定位冲突 ) ,则转 (4) ;否则读取该记录进行相应的处理,结束。

(4)按既定的冲突处理策略处理冲突。若是插入冲突,则冲突处理是要找一个空闲位置以插入记录;若是定位冲突,则冲突处理是继续查找所要求的记录,确定记录是否在散列结构中,若是则返回该元素的位置信息,否则返回特殊标志。

Page 78: 第九章    查找

9.3 哈希表(散列表 ) 9.3.2 散列函数的构造方法

“好”的散列函数是指:对于关键字集合中的任意一个关键字,经散列函数映像到地址集合中任意一个地址的概率是相等的,称此类散列函数为均匀的哈希函数。

常用的散列函数构造方法有:

1 .直接定址法

可表示为 H(key)=a*key+b ,其中 a 、 b 均为常数。

这种方法计算特别简单,并且不会发生冲突,但当关键字分布不连续时,会出现很多空闲单元,会将造成大量存贮单元的浪费。

Page 79: 第九章    查找

9.3 哈希表(散列表 ) 9.3.2 散列函数的构造方法

1 .直接定址法 2 .数字分析法

分析关键字的各个位的构成,截取其中若干位作为散列函数值,尽可能使关键字具有大的敏感度。

通过对上述关键字序列分析,发现第 4 、 5 、 6 位的敏感度较大,于是有:

H(99346532) = 465

H(99372242) = 722

H(99387433) = 874

H(99301367) = 016

H(99322817) = 228

key1: 9 9 3 4 6 5 3 2

key2: 9 9 3 7 2 2 4 2

key3: 9 9 3 8 7 4 3 3

key4: 9 9 3 0 1 3 6 7

key5: 9 9 3 2 2 8 1 7

......

例如:

Page 80: 第九章    查找

9.3 哈希表(散列表 ) 9.3.2 散列函数的构造方法

1 .直接定址法

2 .数字分析法 3 .平方取中法

这种方法是先求关键字的平方值,然后在平方值中取中间几位为散列函数的值。因为一个数平方后的中间几位和原数的每一位都相关,因此,可以使用随机分布的关键字得到的记录的存储位置也是随机的。

关键字 (关键字)2 函数地址 0100 0010000 010 1100 1210000 210 1200 1440000 440 1160 1370400 370 2061 4310541 310 利用平方取中法得到散列函数地址

Page 81: 第九章    查找

9.3 哈希表(散列表 )9.3.2 散列函数的构造方法

1 .直接定址法

2 .数字分析法

3 .平方取中法 4 .折叠法

将关键字分割成位数相同的几部分 ( 最后一部分的位数可以不同 ) ,然后取这几部分的叠加和 (舍去进位 ) 作为散列函数的值,称为折叠法。

例如,假设关键字为某人身份证号码 430104681015355 ,则可以用 4 位为一组进行叠加,即有 5355+ 8101+ 1046+430= 14932 ,舍去高位,则有 H(430104681015355)= 4932 。

Page 82: 第九章    查找

9.3 哈希表(散列表 )9.3.2 散列函数的构造方法

1 .直接定址法

2 .数字分析法

3 .平方取中法

4 .折叠法 5 .除留余数法

该方法的散列函数形式为:

Hash(key) = key % p

其中, p 为不大于散列表表长 m 的数

Page 83: 第九章    查找

5 .除留余数法 ( 续 )

除留余数法计算简单,适用范围广,是一种最常使用的方法。这种方法的关键是选取较理想的 p 值,使得每一个关键字通过该函数转换后映射到散列空间上任一地址的概率都相等,从而尽可能减少发生冲突的可能性。一般情形下,取 p 为一个素数较理想。经验表明, p 为 1.1n~ 1.7n之间的一个素数较好,其中 n 为哈希表中待装入的元素个数。

6. 随机法

选择一个随机函数,取关键值的随机函数值为它的哈希地址,即

H(key)=random(key)

9.3.2 散列函数的构造方法

Page 84: 第九章    查找

9.3 哈希表(散列表 ) 9.3.3 解决冲突的方法

一般情况下,由于关键字的复杂性和随机性,很难有理想的散列函数存在,所以,冲突一般是难以避免的,因此需要设计合适的冲突解决方法。

常用的冲突处理方法有: 1 .开放定址法

开放定址法就是从发生冲突的那个单元开始,按照一定的次序,从哈希表中找出一个空闲的存储单元,把发生冲突的待插入关键字存储到该单元中,从而解决冲突。在哈希表未填满时,处理冲突需要的“下一个”空地址在哈希表中解决。

Page 85: 第九章    查找

9.3 哈希表(散列表 ) 9.3.3 解决冲突的方法

1 .开放定址法 开放定址法就是从发生冲突的那个单元开始,按照一定的次序,从哈希表中找出一个空闲的存储单元,把发生冲突的待插入关键字存储到该单元中,从而解决冲突。在哈希表未填满时,处理冲突需要的“下一个”空地址在哈希表中解决。

开放定址法利用下列公式求“下一个”空地址

Hi = (H(key)+di) MOD m i=1,2,…K(K<=m-1)

其中 H(key) 为散列函数, m 为散列表长度, di 为增量序列

Page 86: 第九章    查找

9.3 哈希表(散列表 ) 9.3.3 解决冲突的方法

1 .开放定址法 开放定址法利用下列公式求“下一个”空地址

Hi = (H(key)+di) MOD m i=1,2,…K(K<=m-1)

其中 H(key) 为散列函数, m 为散列表长度, di 为增量序列 根据 di 的取法,解决冲突时具体使用下面一些方法。

(1)线性探测再散列

(2)二次探测再散列

(3)伪随机探测再散列

Page 87: 第九章    查找

( 1 )线性探测再散列

假设哈希表的地址为 0~m-1 ,则哈希表的长度为 m 。若一个关键字在地址 d 处发生冲突,则依次探查 d+1 , d+2 ,…, m-1 等地址 (当达到表尾 m-1 时,又从 0 , 1 , 2 ,… .开始探查 ) ,直到找到一个空闲位置来存储冲突的关键字,将这一种方法称为线性探测再散列。假设发生冲突时的地址为 d0=H(k) ,则探查下一位置的公式为 di=(di-1+1)

%m (1≤i≤m-1) ,最后将冲突位置的关键字存入 di 地址中。

9.3.3 解决冲突的方法

例如 : 给定关键字序列

19 , 14 , 23 , 1 , 68 , 20 , 84 , 27 , 55 , 11 ,10 , 79

散列函数: H(k) = k%13

试用线性探测再散列法建立散列表。

Page 88: 第九章    查找

计算关键字的散列函数值。

Hi = (H(key)+di) MOD m i=1,2,…K(K<=m-1)

di = i 其中 i=1,2,…K

H(19) = 19%13 = 6 H(14) = 14%13 = 1

H(23) = 23%13 = 10 H(1) = 1%13 =1

H(68) = 68%13 = 3 H(20) = 20%13 = 7

H(84) = 84%13 = 6 H(27) = 27%13 = 1

H(55) = 55%13 = 3 H(11) = 11%13 = 11

H(10) = 10%13 =10 H(79) = 79%13 = 1

9.3.3 解决冲突的方法

Page 89: 第九章    查找

(2) 二次探测再散列法

该方法规定,若在 d 地址发生冲突,下一次探查位置为 d+12 ,d-12,d+22 , d- 22 , …,直到找到一个空闲位置为止。

9.3.3 解决冲突的方法

开放地址法充分利用了哈希表的空间,但在解决一个冲突时,可能造成新的冲突 ( 聚集 ) 。另外,用开放地址法解决冲突不能随便对结点进行删除。

例如 : 对于上例中 给定关键字序列

19 , 14 , 23 , 1 , 68 , 20 , 84 , 27 , 55 , 11 ,10 , 79

散列函数: H(k) = k%13

试用二次探测再散列法建立散列表。

Page 90: 第九章    查找

2. 链地址法

链地址法也称拉链法,是把相互发生冲突的同义词用一个单链表链接起来,若干组同义词可以组成若干个单链表。

9.3.3 解决冲突的方法

例如 : 对于上例中 给定关键字序列

19 , 14 , 23 , 1 , 68 , 20 , 84 , 27 , 55 , 11 ,10 , 79

散列函数: H(k) = k%13

试用链地址法建立散列表。

Page 91: 第九章    查找

1. 开放定址法

2. 链地址法

3. 再哈希法

4. 公共溢出区法

9.3.3 解决冲突的方法

Page 92: 第九章    查找

按理论分析,散列查找它的时间复杂度应为 O(1) ,因此它的平均查找长度应为 ASL=1 ,但由于冲突的存在,它的平均查找长度将会比 1 大。下面分析几种方法的平均查找长度。首先引入装填因子的概念。

9.3.4 散列查找的性能分析

装填因子 所谓装填因子是指散列表中 存入的元素个数 n 与哈希表的大小 m 的比值,即

装填因子 =n/m

Page 93: 第九章    查找

1. 线性探查法的查找性能分析

由于线性探查法解决冲突是线性地查找空闲位置,平均查找长度与表的大小 m 无关,只与所选取的哈希函数 H 、装填因子的值及该处理方法有关,这时的成功的平均查找长度为 ASL=1/2 (1+1/(1- )) 。

9.3.4 散列查找的性能分析

2. 链地址法的查找性能分析

由于拉链法查找就是在单链表上查找,查找单链表中第一个结点的次数为 1 ,第二个结点次数为 2 ,其余依次类推。它的平均查找长度 ASL=1+ /2 。

Page 94: 第九章    查找

散列表查找性能主要取决于三个因素散列函数冲突解决策略装填因子

9.3.4 散列查找的性能分析

Page 95: 第九章    查找

例: 给定关键字序列 11 , 78 , 10 , 1 , 3 , 2 , 4 , 21 ,试分别用顺序查找、二分查找、二叉排序树查找、平衡二叉树查找、哈希查找 ( 用线性探查法和拉链法 )来实现查找,试画出它们的对应存储形式 ( 顺序查找的顺序表,二分查找的判定树,二叉排序树查找的二叉排序树及平衡二叉树查找的平衡二叉树,两种哈希查找的哈希表 ) ,并求出每一种查找的成功平均查找长度。哈希函数 H(k)=k%11 。

Page 96: 第九章    查找

本章应掌握的内容 静态查找表,顺序查找、折半查找和平均查找长度 动态查找表

二叉排序树的查找、插入和删除 平衡二叉树的概念,平衡处理的基本方法 B树的基本概念及其查找过程

散列(哈希)查找 哈希造表:冲突及消解 装填因子与查找效率

本章小结

作业 9.1 9.3 9.7 9.9 9.11 9.19 9.21 9.23 9.25 9.10(选做 ) 9.13 (选做 ) 9.31 (选做 )


Top Related