630 likes | 775 Views
第 7 章 查找. 7.1 查找的基本概念 7.2 线性表查找 7.3 索引顺序查找 7.4 动态查找 7.5 散列表 7.6 应用举例及分析. 本章要点. 顺序查找和二分法查找 索引的特点和应用 二叉排序树的概念及操作、平衡二叉树 B_ 树和 B+ 树 散列表的概念和应用. 本章难点. 二叉排序树的概念及操作、平衡二叉树 B_ 树和 B+ 树 散列表的概念和应用. 学习目标. 二叉排序树的概念及操作、平衡二叉树 B_ 树和 B+ 树 散列表的概念和应用. 7.1 查找的基本概念.
E N D
第7章 查找 7.1 查找的基本概念 7.2 线性表查找 7.3 索引顺序查找 7.4 动态查找 7.5 散列表 7.6 应用举例及分析
本章要点 • 顺序查找和二分法查找 • 索引的特点和应用 • 二叉排序树的概念及操作、平衡二叉树 • B_树和B+树 • 散列表的概念和应用
本章难点 • 二叉排序树的概念及操作、平衡二叉树 • B_树和B+树 • 散列表的概念和应用 学习目标 • 二叉排序树的概念及操作、平衡二叉树 • B_树和B+树 • 散列表的概念和应用
7.1 查找的基本概念 • 查找又称检索,就是从一个数据元素集合中找出某个特定的数据元素的算法。 • 查找表(Search Table) 在计算机中,被查找的数据对象是由同一类型的数据元素(数据记录)构成的集合,可称之为查找表。由于集合中的数据元素之间存在着完全松散的关系,因此查找表是一种非常灵活的数据结构。
7.1 查找的基本概念 • 关键字(Key) 在查找表中,每个数据元素由若干个数据项组成,我们规定能够用来标识(区分)不同数据元素的一个数据项或几个数据项的组合称为关键字(Key)。如果此关键字可以用来区分查找表中的每一个记录,则称此关键字为主关键字(Primary Key);反之称为次关键字(Secondary Key),它不能区分每一个记录,但是可以将记录分成若干不同的组。当记录只有一个数据项时,它就是该记录的关键字。
7.1 查找的基本概念 • 查找(Searching) 基于上述规定,我们给查找下一个定义:根据给定的值,在查找表中查找是否存在关键字等于给定值的记录,若存在一个或几个这样的记录,则称查找成功,查找的结果可以是对应记录在查找表中的位置或整个记录的值;若查找表中不存在关键字等于给定值的记录,则称查找不成功,查找的结果可以给出一个特定的值或“空”指针。
7.1 查找的基本概念 • 查找表上的基本操作一般有下列五种: • (1)CREATE(L) 建表,生成一个由用户给定的若干个记录组成的查找表。 • (2)SEARCH(L,K) 查找,若在表L中存在关键字等于给定值K的记录,结果返回该记录在表中的位置,否则结果为一特殊的值,比如-1或NULL。 • (3)GET(L,pos) 读取记录,结果是输出表L中pos位置上的记录。 • (4)INSERT(L,K) 插入,在表L中插入一个关键字为K的记录。 • (5)DELETE(L) 删除,在表L中删除一个记录,可以是指定位置上的记录,也可以是指定关键字的记录。
7.1 查找的基本概念 • 静态查找、动态查找 若对查找表只作前面查找和读取操作,在操作前后查找表没有任何变化,称此类查找表为静态查找表;若在查找过程中还经常有插入记录和删除记录的操作,使得操作前后查找表会发生变化,则称此类查找表为动态查找表。 • 内部查找、外部查找 若整个查找过程都在内存中进行的,则称之为内部查找;若查找过程中需要访问外存,则称之为外部查找。本书中只讨论内部查找的算法。
7.1 查找的基本概念 • 通常以查找成功时的平均查找长度(Average Search Length , ASL)作为衡量算法优劣的依据,所谓ASL是指在查找过程中,为确定记录在查找表中的位置,需和给定值进行比较的记录关键字个数的平均值。 • 假设查找表的表长为n(即含有n个记录),则平均查找长度ASL定义为: ASL= 其中,Pi为表中第i个记录的查找概率,且=1,如果每个记录的查找概率相等,则Pi =1/n。Ci表示如果第i个记录的关键字和给定值相等,要找到这个记录需和给定值进行比较的次数。
7.2 线性表查找 • 线性表是最简单的一种查找表的组织方式,一个线性表含有若干个结点,每个结点存放查找表的一个记录。若在线性表中找到了关键字与给定值相同的记录,则返回该记录在线性表中的位置,否则,返回特定的值,表示查找不成功。线性表的查找一般是静态查找,根据查找的方法不同分为: 顺序查找 二分法查找
7.2.1 顺序查找 • 顺序查找(Sequential Search) 也称线性查找,是一种最简单的查找方法,它的基本思想是:从表的一端开始,顺序扫描线性表,依次用待查找的给定值K与线性表里各结点的关键字比较,若在表中找到了与待查找的K相同的结点,则查找成功,否则,查找失败。 • 顺序查找对线性表的物理结构没有要求,可以采用顺序表或线性链表作为查找表的存储结构,对记录在表中存放的先后次序没有任何要求。
7.2.1 顺序查找 • 查找算法是以顺序表作为存储结构。顺序表的类型说明如下: #define MAXSIZE 100 #define KEYTYPE int typedef struct { KEYTYPE key; // otherdata … ; /*记录的其余数据部分,在下面的讨论和算法中忽略不考虑,写程序时可以根据实际需要替换成记录中的实际数据类型。*/ }SSELEMENT; /*查找表中的一个数据元素*/ typedef struct { SSELEMENT r[MAXSIZE]; int len; /*查找表长度,即查找表中元素个数*/ }SSTABLE; /*查找表*/
7.2.1 顺序查找 对应的顺序查找算法如下: int seq_search (KEYTYPE k, SSTABLE st) { int j; j = st.len; st.r[0].key = k; /*将K放入r[0],防止查找时下标溢出,r[0]叫做“监视哨”*/ while (st.r[j].key != k) j--; return j; }
7.2.1 顺序查找 • 这个算法是从顺序表的高端向低端依次查找记录的,表中的记录从下标为1的单元放起。0单元中设置了一个监视哨st.r[0].key = K,这样,可以在while循环的条件判断中省去下标越界的判断,节约算法的运行时间。 • 顺序查找的优点是算法简单且适用面广,对查找表的物理结构无任何要求,可以用顺序存储结构,也可以用链表存储结构,同时对查找表的元素存放的先后次序(即查找表是否有序)也没有要求。顺序查找的缺点是平均查找长度较大,特别是当n较大时,查找效率较低,不宜采用。
7.2.2 二分法查找 • 二分法查找(Binary Search) 又称折半查找,它是一种查找效率较高的方法。二分法查找要求查找表中的记录按关键字排好序,并且只能在顺序存储结构上实现,假设关键字按升序排列。 • 二分法查找的基本思想是:每次将给定值K与有序表中间位置上的记录关键字进行比较,确定待查记录所在的范围,然后逐步缩小范围直到确定找到或找不到对应记录为止。
7.2.2 二分法查找 • 设有序表ST中记录的关键字按升序排列,整型变量指针low和high分别指向有序表中待查记录所在范围的下界和上界,中间记录所在位置用mid指示,mid = (low + high)/2。将给定值K和mid所指的记录关键字ST.r[mid].key比较,有三种可能的结果: • (1)K < ST.r[mid].key:待查记录如果存在,必定落在mid位置的左半部分。于是,查找范围缩小了一半。修改范围的上界high = mid -1,继续对左半部分进行二分查找。 • (2)K = ST.r[mid].key:查找成功并结束算法,mid所指的记录就是查到的记录。 • (3)K > ST.r[mid].key:待查记录如果存在,必定落在mid位置的右半部分。于是,查找范围缩小了一半。修改范围的下界low = mid + 1,继续对右半部分进行二分查找。 • 重复上述过程,区间每次缩小1/2,当区间不断缩小,出现查找区间的下界大于上界时,宣告查找不成功并结束算法,确定关键字为K的记录不存在。
7.2.2 二分法查找 • 例如,一组记录的关键字的有序顺序表为(5, 12, 30, 45, 70, 73, 80, 85, 89, 100, 103, 109)。初始时low = 1,high = 12,设给定值K = 85或K = 15,则二分查找的过程如图7-1所示。 对应的算法如下: int search_bin (KEYTYPE k, SSTABLE st) { int low, high, mid; low = 1; high = st.len; while(low <= high) { mid = (low + high)/ 2; if(k == st.r[mid].key) /*给定值k = st.r[mid].key */ return mid; /*查找成功 */ else if(k < st.r[mid].key) /*给定值k < st.r[mid].key*/ high = mid - 1; else /*给定值k > st.r[mid].key*/ low = mid + 1; } return 0; // 查找不成功 }
7.2.2 二分法查找 (a)在有序表中查找关键字为85的记录的过程
7.2.2 二分法查找 (b)在有序表中查找关键字为15的记录的过程
7.2.2 二分法查找 • 二分法查找是一种效率较高的算法。算法中每次将给定值K与查找范围中间位置上的记录的关键字比较。整个查找过程可以用二叉树来描述,把当前查找区间的中间位置上的结点作为根,左子表和右子表中的结点分别作为根的左子树和右子树,由此而得到的二叉树,称为描述二分法查找的二叉判定树。由于二叉判定树每次查找的位置都是当前查找范围的中间位置,所以二叉判定树的形状和只和查找表的结点个数(及查找范围)有关,而和结点中存储的数据(关键字大小)无关。
7.2.2 二分法查找 图7-2 二分法查找过程对应的二叉判定树 图7-3 二分法查找不成功的情况示意图
7.3 索引顺序查找 • 索引顺序查找(Indexed Sequential Search)又称分块查找,它是一种性能介于顺序查找和二分法查找之间的查找方法,它是以索引顺序表表示的静态查找表。 • 索引顺序查找要求将查找表分成若干个子表(或称为块),并对子表建立索引表,查找表的每一个子表由索引表中的索引项确定。索引项包括两个字段:关键码字段(存放对应子表中的最大关键码值);指针字段(存放指向对应子表的指针)。索引顺序查找要求查找表的子表内部元素之间无序,但是子表之间必须有序(即后面子表中的任意元素都大于前面子表中的所有元素)。将每个子表中的最大值放入索引表中,形成对每个子表的索引项。
7.3 索引顺序查找 • 分块查找的基本思想是:首先用给定值K在索引表中查找,因为索引表是按关键字项有序排列的,可采用二分查找或顺序查找以确定待查记录在哪一子表中,然后在已确定的块中进行顺序查找,当子表是有序表时,在块中也可以用二分查找。对应图7-4,如果给定值K = 38,先将K和索引表各关键字进行比较,因为22 < K < 48,则关键字为38的记录如果存在,必定在第二个子表中,再从第二个子表的第一个记录的位置序号7开始,按记录顺序查找,直到确定第10个记录是要找的记录。又如当K = 29时,则仍在第二子表中查找,自第7个记录起按记录顺序查找至第12个记录,每个记录的关键字和K比较都不相等,则查找不成功。
7.3 索引顺序查找 一个索引顺序表的示例 图7-4 索引顺序表结构示意图
7.3 索引顺序查找 • 索引顺序查找是一种效率介于顺序查找和二分查找之间的查找方法,对于数据元素较多的查找表,使用这种算法效率会更好。而且必要的时候,索引查找可以在表中添加或删除元素,因为块内的元素是无序存放的,所以只要找到所在的分块,就可以插入或删除元素而无需移动大量元素,这也是索引查找算法的一个优点。但是索引查找要在原有的查找表之外附加一个索引表,因此空间开销要比顺序查找和二分法查找大。
7.4 动态查找 • 静态查找表一旦生成之后,所含记录在查找过程中一般是固定不变的。而动态查找表则不然,对表中记录经常进行插入和删除操作,所以动态查找表是一直在变化的。动态查找表的这种特性要求采用灵活的存储方法来组织查找表中的记录,以便高效率地实现动态查找表的查找、插入、删除等操作。 • 动态查找通常使用二叉排序树、平衡二叉树、B_树和B+树等。
7.4.1 二叉排序树的概念及操作 • 二叉排序树的概念及操作 • 二叉排序树(Binary Sort Tree)如果非空,则具有下列性质: (1)若它的左子树不空,则左子树上所有结点的关键字均小于它的根结点的关键字; (2)若它的右子树不空,则右子树上所有结点的关键字均大于它的根结点的关键字; (3)它的左子树、右子树分别也是二叉排序树。
7.4.1 二叉排序树的概念及操作 二叉排序树示意图
7.4.1 二叉排序树的概念及操作 • 采用二叉链表作为存储结构,数据结点类型说明如下: #define KEYTYPE int; typedef struct node { KEYTYPE key; otherdata ……; /*结点的其余数据部分,在下面的讨论和算法中忽略不考虑*/ struct node *lchild, *rchild; } BSTNODE
7.4.1 二叉排序树的概念及操作 • 二叉排序树生成的形式算法描述为: • { • 对于一组任意关键字的结点序列, • (1)从空二叉树开始,读入的第一个结点作为二叉排序树的根结点。 • (2)从读入的第二个结点起,将读入结点的关键字和根结点的关键字进行比较: • ① 读入结点的关键字等于根结点的关键字,则说明树中已有此结点,不作处理; • ② 读入结点的关键字大于根结点的关键字,则将此结点插到根结点的右子树中; • ③ 读入结点的关键字小于根结点的关键字,则将此结点插到根结点的左子树中。 • ④ 在子树中的插入过程和前面的步骤①,②,③相同。 • }
7.4.1 二叉排序树的概念及操作 • 插入算法: void insert_btree_onenode(KEYTYPE k, BSTNODE *p) { if (p = = NULL) /*如果二叉排序树空*/ { p = (BSTNODE *)malloc(sizeof(BSTNODE)); p->lchild = NULL; p->rchild = NULL; p->key = k; } else if (k > p->key) insert_btree_onenode(k,p->rchild); /*插入的值比根结点大,插向右子树*/ else insert_btree_onenode(k,p->lchild); /*插入的值比根结点小,插向左子树*/ }
7.4.1 二叉排序树的概念及操作 • 二叉排序树上的查找 • 二叉排序树上查找的形式算法描述为: • { 将给定值和二叉排序树的根结点的关键字比较: • ① 给定值等于根结点的关键字,则根结点就是要查找的结点; • ② 给定值大于根结点的关键字,则继续在根结点的右子树中查找; • ③ 给定值小于根结点的关键字,则继续在根结点的左子树中查找; • ④ 在子树中的查找过程和前面的步骤①,②,③相同。 }
7.4.1 二叉排序树的概念及操作 • 二叉排序树上的查找算法: BSTNODE * search_btree_node(KEYTYPE k, BSTNODE *r) { BSTNODE *p; if(r == NULL) p = NULL; else if(k == r->key) p = r; else if(k > r->key) p = search_btree_node(k, r->rchild); else p = search_btree_node(k, r->lchild); return p; }
7.4.1 二叉排序树的概念及操作 • 删除操作首先是进行查找,确定被删除结点是否在二叉排序树中。假设被删结点为p指针所指,其双亲结点为f指针所指,被删结点的左子树和右子树分别用PL和PR表示。下面分几种情况讨论如何删除该结点: • (1)若被删除结点是叶子结点,即PL和PR均为空子树,则只需修改被删除结点的双亲结点的指针即可。 • (2)若被删除结点只有左子树PL或只有右子树PR,此时只要令PL或PR直接成为其双亲结点的左子树或右子树即可。如图7-8(a),(b),(c),(d)所示。 • (3)若被删除结点的左子树和右子树均不空时,在删除该结点前为了保持其余结点之间的序列位置相对不变,首先要用被删除结点在该树中序遍历序列中的直接前驱(或直接后继)结点的值取代被删除结点的值,然后再从二叉排序树中删除那个直接前驱(或直接后继)结点。
7.4.2 平衡二叉树 • 平衡二叉树 • 平衡二叉树 平衡二叉树或者是一棵空树,或者是具有下列性质的二叉排序树:它的左子树和右子树都是平衡二叉树,且左子树和右子树高度之差的绝对值不超过1。 • 平衡因子 平衡因子是平衡二叉树的重要概念,任意一个结点的平衡因子等于其左子树的高度与右子树的高度之差,记作α。 非平衡二叉树 平衡二叉树
7.4.2 平衡二叉树 • 在平衡二叉树中插入一个结点x的步骤大体如下: • ① 像一般的二叉排序树那样插入结点x(x总是作为新的叶子结点插入的)。 • ② 沿着插入x的路线返回(从根节点到新叶子结点x的路线之逆),逐层回溯。必要时,修改x的祖先的平衡因子,因为插入x后会使某些子树的高度增加。 • ③ 回溯途中,一旦发现x的某个祖先p失衡,即平衡因子的绝对值大于1,则将这个祖先的子树旋转,使之保持平衡。重复②③,直到x的所有祖先都保持平衡为止。 • ④ 回到①,继续插入新的结点,直到所有的结点都插入完毕。 • 在平衡二叉树中,如果在插入过程中结点p失衡,则需要将p结点的左右子树加以旋转调整,使调整后的p结点仍旧保持平衡状态。通常旋转方式有4中,包括LL调整、RR调整、LR调整和RL调整。
7.4.2 平衡二叉树 • (1)LL调整 • P结点失衡是因为x结点被插入到p结点的左孩子pl的左子树上,这时应该将以p结点为根的二叉树向右旋转,变成以pl为根的新二叉树。即将pl变为新的根,p结点变成pl结点的右孩子,原来pl的右子树β变为p结点的左子树。 (a)插入前p为根 (b)插入后(黑色部分) (c)调整后pl为根
7.4.2 平衡二叉树 • (2)RR调整 • P结点失衡是因为x结点被插入到p结点的右孩子pr的右子树上,这时应该将以p结点为根的二叉树向左旋转,变成以pr为根的新二叉树。即将pr变为新的根,p结点变成pr结点的左孩子,原来pr的左子树α变为p结点的右子树。 (c)调整后pr为根 (a)插入前p为根 (b)插入后(黑色部分)
7.4.2 平衡二叉树 • (3)LR调整 • P结点失衡是因为x结点被插入到p结点的左孩子pl的右子树上,假定用q来表示pl的右孩子,这时应该将以q结点变成新二叉树的根。pl结点作为q结点的左孩子,并且原来q结点的左子树β变成pl结点的右子树;p结点作为q结点的右孩子,并且原来q结点的右子树δ变为p结点的左子树。 (c)调整后q为根 (b)插入后(黑色部分) (a)插入前p为根
7.4.2 平衡二叉树 • (4)RL调整 • P结点失衡是因为x结点被插入到p结点的左孩子pl的右子树上,假定用q来表示pl的右孩子,这时应该将以q结点变成新二叉树的根。pl结点作为q结点的左孩子,并且原来q结点的左子树β变成pl结点的右子树;p结点作为q结点的右孩子,并且原来q结点的右子树δ变为p结点的左子树。 (c)调整后q为根 (b)插入后(黑色部分) (a)插入前p为根
7.4.2 平衡二叉树 • 平衡二叉树的查找和删除 • 在平衡二叉树上删除给定结点x(假定平衡二叉树有且只有一个结点的值等于x)的大体步骤如下: • ① 用一般二叉排序树的删除算法找到并删除结点x • ② 沿根到被删除结点的路线之逆逐层向上回溯,必要时,修改x的祖先结点的平衡因子,因为删除结点x后,会使某些子树的高度降低。 • ③ 回溯途中,一旦发现x的某个祖先p失衡,就像插入操作那样进行旋转调整,使结点p重新平衡。 • ④ 如果旋转之后,子树的总高度比旋转之前降低了,回溯不能停止。也就是说,在平衡二叉树上删除一个结点有可能引起多次旋转,而不像插入那样至多旋转一次。 • 假设失衡的结点为p,p结点的右孩子为pr,则调整算法为: • 如果pr的平衡因子α≥0,则以p结点为根,执行RL旋转。 • 如果pr的平衡因子α<0,则以p结点为根,执行RR旋转。
7.4.2 平衡二叉树 • 在二叉排序树的插入和删除操作中采用平衡二叉树的优点是使二叉排序树的结构较好,从而提高了查找操作的时间效率;缺点是使得插入和删除操作复杂化,从而降低了运算速度;这是由于在每次运算中,不仅要插入或删除结点,而且要检查是否存在有不平衡的子树(实验表明,平均每两次插入或者五次删除操作都要出现一次不平衡);若存在失衡,则需要对不平衡的子树进行旋转调整。因此,平衡二叉树更适合于二叉排序树一经建立就很少进行插入和删除操作,而是主要用于查找的场合。
7.4.3 B_树和B+树 • 1. B_树 • B_树又称为多路平衡查找树,所有结点的孩子结点数最大值称为B_树的阶,通常用m表示,从查找效率考虑,要求m≥3。一棵m阶B_树或者是一棵空树,或者是满足下列要求的m叉树: • (1)树中每个结点至多有m个孩子结点,即至多有m-1个关键字。 • (2)除了根结点外,其他结点至少有m/2个孩子结点,即至少有m/2-1=(m-1)/2个关键字。 • (3)若根节点不是叶子结点,则根结点至少有两个孩子结点。 • (4)每个结点的结构如下: • 其中,n为该结点中的关键字个数,除根结点外,其他所有结点的n大于等于m/2-1,且小于等于m-1;ki(1≤i≤n)为该结点的关键字且满足ki < ki+1;pi(0≤i≤n)为该结点的孩子结点指针且满足pi结点上的关键字大于等于ki且小于ki+1,pn结点上的关键字大于kn。 • (5)所有叶子结点都在同一层上,即B_树是所有结点的平衡因子都等于0的多路查找树。
7.4.3 B_树和B+树 一棵4阶B_树
7.4.3 B_树和B+树 • B+树 • 在索引文件组织时,经常使用B_树的一些变形树,其中B+树是一种应用广泛的变形树,一棵m阶B+树满足下列条件: • (1)每个分支结点至多有m棵子树。 • (2)除根节点外,其他每个分支结点至少(m+1)/2有棵子树。 • (3)根节点至少有两棵子树,至多有m棵子树。 • (4)有n棵子树的结点有n个关键字。 • (5)所有叶子结点包含全部关键字及指向相应记录的指针(或存放数据文件分块后每块的最大关键字及指向该块的指针),而且叶子结点按照关键字大小顺序链接起来。 • (6)所有分支结点中仅包含他的各个子节点中最大关键字及指向子节点的指针。
7.4.3 B_树和B+树 • 对于m阶的B_树和B+树,定义中的主要差异在于: • (1)在B+树中,具有n个关键字的结点含有n棵子树,即每个关键字对应一棵子树,而在B_树中,具有n个关键字的结点含有n+1棵子树。 • (2)在B+树中,每个结点(除根结点以外)中的关键字的个数n的取值范围是m/2≤n≤m,根节点n的取值范围是1≤n≤m;而在B_树中,它们的取值范围分别是m/2-1≤n≤m-1和1≤n≤m-1。 • (3)B+树中的所有非叶子结点仅起到了索引的作用,即结点中的每个索引项只含有对应子树的最大关键字和指向该子树的指针,不含有该关键字对应记录的存储地址。在B_树中,每个关键字对应一个记录的存储地址。
7.4.3 B_树和B+树 • (4)B+树中的所有叶子结点包含了全部关键字,即其他非叶子结点中的关键字包含在叶子节点中。在B_树中,叶子结点包含的关键字与其他结构包含的关键字是不重复的。 • (5)通常在B+树上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子结点,所有的叶子结点链接成一个不定长的线性表。所以对于B+树,既可以按照树形结构查找元素,又可以顺序查找元素。
7.4.3 B_树和B+树 一棵4阶的B+树
7.5 散列表 • 7.5.1 散列表与散列函数 • 散列是一种重要的存储方式,又是一种查找方法。这种查找又称为哈希查找。按散列存储方式构造的动态表又称散列表(Hashed Table)。散列查找的核心是散列函数(Hashed Function),又称哈希函数。查找的核心思想是:以记录的关键字key为自变量通过一个确定的散列函数H,计算出对应的函数值H(key)作为记录的存储地址。这样,每个记录的关键字通过函数计算都对应得到一个记录的存储地址: Addr(i) = H (keyi)(第i个记录的关键字为keyi) 其中,H是散列函数;Addr(i)是计算得到的第i个记录的存储地址。
7.5.2 散列函数的构造 • 1. 直接定址法 取关键字或关键字的某个线性函数值为散列地址。即: H(key) = key 或H(key) = a × key + b 其中, a 和 b 为常数。例如,解放后每年出生人口调查表的关键字是年份,可以取关键字加一常数作为散列地址 。 • 2. 数字分析法 数字分析法又称为数字选择法。这种方法适用于事先知道所有可能出现的关键字值,并且关键字值的位数比散列地址的位数多的情况。在这种情况下,可以对关键字值的各位进行分析,选择分布较均匀的若干位组成散列地址。 • 3. 除留余数法 这是一种最简单也最常用的构造散列函数的方法。取关键字被某个不大于散列表表长m的数p除后所得的余数作为散列地址,即: H(key) = key MOD p, p ≤ m 这一方法的关键在于p的选择。例如,若选p为偶数,则得到的散列地址总是将奇数键值映射成奇数地址,偶数键值映射成偶数地址,就会增加冲突发生的机会。通常选p为不大于散列表容量的最小素数。