1、第 1 章 常用算法和数据结构大纲要求: 排序算法。 查找算法。 数据结构(线性表、栈、队列、数 组、树、 图)。1.1 排 序 算 法1.1.1 考点辅导1.1.1.1 选择排序若设 R1.n为待排序的 n 个记录, R1.i-1已按照主关键字由小到大排序,且任意xR1.i-1,yRi.n满足 x.keyy.key ,则选择排序的主要思路如下。(1) 反复从 Ri.n中选出关键字最小的结点 Rk。(2) 若 ik,则将 Ri与 Rk交换,使得 R1.i有序且保持原来的性质。(3) i 增 1,直到 i 为 n。为方便描述,被查找的顺序表 C 类型定义如下:#define MAXSIZE 10
2、00 /*顺序表的长度*/ typedef int KeyType; /*关键字类型为整数类型*/typedef structKeyType key; /*关键字项*/InfoType otherinfo; /*其他数据项*/RecType; /*记录类型*/ typedef struct RecType rMAXSIZE+1; /*r0空作为哨兵*/int length; /*顺序表长度*/SqList; /*顺序表类型*/顺序存储线性表的选择排序算法如下:void Sqsort(SqList for(i=0;i=0 h/=2) for(j=h;j=0 /*p 用来记录一趟排序最后下沉的结点
3、位置*/for(h=1;hq.rj+1.key)temp=q.rj; q.rj=q.rj+1;q.rj+1=temp;p=j;可见,对 n 个结点的线性表采用冒泡排序,外循环最多执行 n-1 趟,每一趟最多执行n-1 次比较;第 2 趟最多执行 n-2 次比较;依次类推,第 n-1 趟最多执行 1 次比较。因此,整个排序过程最多执行 n*(n-1)/2 次比较。由于关键字相等的结点不交换,故冒泡排序算法是稳定的。程序员考试同步辅导(下午科目 )(第 2 版)411.1.1.5 快速排序快速排序(Quick Sort)的主要思路为:通过对线性表序列的一趟扫描使某个结点移到中间的某个位置,且使其左
4、边序列的各结点的关键字都比该结点的关键字小,而其右边序列的各 结 点 的 关 键 字 都 不 比 该 结 点 的 关 键 字 小 , 常 称 这 样 的 一 次 扫 描 为 “划 分 ”。 然 后 ,对 左 、 右 序 列 进 行 同 样 的 处 理 , 直 到 所 有 序 列 均 只 包 含 一 个 结 点 为 止 , 这 样 便 可 将 原 线 性表 排 好 序。快速排序可以看做是冒泡排序的改进,冒泡排序可以看做是快速排序的退化,即每趟划分总是在同一端进行。若设待排序的记录序列 R1,R2,Rn 为 R1.n,则对其按关键值的非递减序列进行快速排序的算法如下:void QuickSort1
5、(SqList low=s;high=t;privotkey=R.rs.key;R.r0=R.rs;while(lowlow if (lownext , *mid, *midpre, *pre, *r;int temp;midpre=head;mid=p;if(!p)return 1;pre=p;p=p-next;while(p!=tail)r=p-next;if(p-datanext-data)pre-next=r; /*断开与 p 结点的链接*/midpre-next=p; /*将 p 结点插入到 mid 之前*/p-next=mid;midpre=p;p=r ;else /*结点的位置保
6、持不变*/pre=p;p=r;QuickSort2(head, midpre); /*对前一部分链表进行快速排序*/QuickSort2(mid-next,tail); /*对后一部分链表进行快速排序*/ /*QuickSort2*/可见,只要充分领会顺序存储结构下的算法思想,熟悉链表存储结构就可以通过掌握顺序存储结构下的算法得到链表存储结构下的相应算法。程序员考试同步辅导(下午科目 )(第 2 版)611.1.1.7 堆排序首先,要认真掌握堆的定义,然后才能进一步理解建堆的算法。堆的定义为:n 个关键字序列 k1,k 2,k 3,k n 称为堆,当且仅当该序列满足如下性质(也称堆性质) :k
7、 ik 2i 且 kik 2i+1 或 k ik 2i 且 kik 2i+1(1i n/2)。满足第种情况的堆称为小根堆,满足第种情况的堆称为大根堆。这里仅讨论第种情况。本质上,堆排序在排序过程中,是将顺序表中存储的数据看成一棵完全二叉树,利用完全二叉树中双亲结点和孩子结点之间的内在关系来选择关键字最小记录。堆排序分建堆和堆调整两个过程。建堆的过程为:堆排序的过程是一个不断从堆顶到叶子的调整过程(又称“筛选”) 。从一个无序序列建堆的过程就是一个反复筛选的过程。若将此序列看成是一个完全二叉树,则最后一个非终端结点是第 n/2 个元素,因此筛选只需从第 n/2 个元素开始。堆调整过程为:将该完全
8、二叉树中最后一个元素替代已输出的结点。若新的完全二叉树的根结点小于左右子树的根结点,则直接输出。反之,则比较左右子树根结点的大小。若左子树的根结点小于右子树的根结点(或右子树的根结点小于左子树的根结点) ,则将左子树( 或右子树)的根结点与该完全二叉树的根结点进行交换。重复上述过程,调整左子树(或右子树 ),直至叶子结点,则新的二叉树满足堆的条件。堆排序的算法描述如下:void HeapSort(Sqlist i0; i-) /*从最后一个内部结点开始调整*/HeapAdjust(R, i, R.length);void HeapAdjust(Sqlist for(j=2*i; jR.rj+1
9、.key)+j;/*找孩子结点中关键字最小的*/if(R.r0.key=q.length)end=q.length-1; /*最后一个段可能不足 len 个结点*/mergestep(/*相邻有序段合并*/start=end+1;if(start=1;i-)t(1)=ai;count(ai/ divisor)%Radix:=(2);for(i=1;i divisor%) Radix到 count(aj/divisor)%Radix+1-1 之 间 ; 同 时 L1 相 同 的 整 数 的 前 后 位 置 没 有 明 显 的 界 限 , 只 需 根 据 读 入 的 次 序来 定 。(4) 按照得
10、到的各个整数 ai的位置 countj将该元素登记在相应的位置上,同时countj增加 1,以便存放下一个和整数 ai的 L1 相同的元素。(5) 对 L2、L3、L4 重复上述过程。通过仔细分析,不难得到如下答案。(1) count(ai / divisor)% Radix(2) count(ai / divisor)% Radix+1(3) ai=ti(4) divisor*16进一步推广,若把整数看成八进制或其他进制,同样也能得到问题的答案。1.1.1.10 败者树采用败者树的目的是为了在进行最小键值的查找时减少比较次数。败者树是一棵完全二叉树,其中每个结点的键值都取其两个子结点的键值中
11、的较小者,因此,根结点的键值是这棵树中所有结点的键值中最小的。这就像 k 个参加淘汰赛的球队,胜者( 值较小者)进入下一轮的比赛,根结点为冠军(值最小者) 。败者树的构造过程是:对具有 k 个记录的序列,首先用这 k 个记录作为叶结点,然后把相邻的两个结点进行比较,把键值小的记录(优胜者) 作为这两个结点的父结点,按此方法自下而上一层一层地产生败者树的结点。为了节约内存空间,非叶子结点可不包含整个记录,只要存放记录的键值及指向该记录的指针即可。败者树的根结点的值是构成败者树的元素中最小的,在后面的应用中,往往把根结点的值输出并用一个新的元素替换,要求构成新的败者树,这时只要在原来的败者树的基础
12、上进行调整即可。调整仅在从根到新加入的叶子结点的树枝上的结点及其兄弟结点之间进行,自下而上进行比较并调整其父结点。1.1.1.11 k 路归并法有了 m 个初始归并段(都是有序段),便可进行 k 路归并了,即将 k 个初始归并段采用某种方法进行归并产生一个段,这样 m 个初始归并段便产生多个更大的段,然后对这些段再进行归并,如此下去,直到只生成一个段为止,这个段就是最后生成的归并段。在内存里进行 k 路归并的方法很多。当归并路数 k 较大时,为了减少合并时的比较次程序员考试同步辅导(下午科目 )(第 2 版)101数,常采用败者树进行合并的方法,其合并过程如下。(1) 用参加合并的 k 个有序
13、段的第一个记录构造一棵初始败者树,该树中的根结点就是这 k 个记录中具有最小键值的记录。(2) 把败者树根结点所代表的记录送到输出缓冲区。(3) 输出记录所在的有序段的下一个记录代替输出记录的位置,调整败者树。(4) 重复步骤(2)和(3) ,直到 k 个有序段的所有记录都输出为止。对于总共有 n 个记录的 k 个有序段的合并过程,如果采用败者树进行合并,那么要选取键值最小的记录,在建立败者树时需要进行 k-1 次比较,此后每次调整败者树只要进行log2k 次比较即可(因为树中保留了以前的比较结果)。所需总的比较次数为 k+nlog2k,当 nk时,总的比较次数约为 nlog2k。1.1.2
14、典型例题分析例 1 阅读以下说明和 C 语言函数,将应填入 (n) 处的字句写在答题纸的对应栏内。(2007 年上半年试题四)【说明】函数 sort(NODE *head)的功能是:用冒泡排序法对单链表中的元素进行非递减排序。对于两个相邻结点中的元素,若较小的元素在后面,则交换这两个结点中的元素值。其中,head 指向链表的头结点。排序时,为了避免每趟都扫描到链表的尾结点,设置一个指针 endptr,使其指向下趟扫描需要到达的最后一个结点。例如,对于图 1-1(a)所示的链表进行一趟冒泡排序后,得到图 1-1(b)所示的链表。图 1-1 排序链表的结点类型定义如下:typedef struct
15、 Node int data;struct Node *next;NODE;【C 语言函数】void sort(NODE *head) NODE *ptr,*preptr,*endptr;int tempdata;第 1 章 常用算法和数据结构11ptr = head - next;while (1) /*查找表尾结点*/ptr = ptr - next;endptr = ptr; /*令 endptr 指向表尾结点*/ptr =(2) ;while(ptr != endptr) while(3) ) if (ptr-data ptr-next-data)tempdata = ptr-data
16、; /*交换相邻结点的数据*/ptr-data = ptr-next-data;ptr-next-data = tempdata;preptr =(4) ;ptr = ptr - next;endptr =(5) ;ptr = head-next;分析:从(1)处代码中可知 ptr 最后应该指向表尾结点。所以(1) 处应为 ptr - next。进行冒泡排序时,不断调整元素的位置,最终使最大元素放到表的最后,所以(2) 处应为 head-next。(3)处的循环条件应该是 扫描的结点,不是最后一个结点,所以(3)处应为ptr!=endptr 。ptr 每向后修改一次, preptr 就要修改一
17、次,所以(4)处应为 ptr,(5)处应为preptr。答案:(1) ptr - next(2) head-next(3) ptr! =endptr,或其他等价形式(4) ptr(5) preptr1.1.3 同步练习1. 下面是一个链接存储线性表的直接插入排序函数。把未排序序列中的第一个结点插入到已排序序列中。排序完毕,链表中的结点按结点值从小到大链接,请在空缺处填上适当内容,每个空缺只填一个语句。typedef struct nodechar data;struct node *link;NODE;NODE *insertsort (NODE *h) 程序员考试同步辅导(下午科目 )(第
18、2 版)121NODE *t, *s, *u, *v;s=h-link;h-link=NULL;while(s!=NULL)for(t=s, v=h; v!=NULL (1), (2);s=s-link;if(v=h) (3);else (4) ;(5) ;return h;2. 请仔细阅读下面的堆排序算法。待排序记录存储在一维数组中,说明如下:typedef struct nodeint key;datatype info;Node;typedef Noden+1 heaptype;函数 heapsort 的功能是将数组 heap 中的前 n 个记录按关键码值递减的次序排序。heapsort
19、 调用 sift 函数,sift 函数的参数 heap、h 和 r 具有如下的含义:调用 sift 函数时,以 heaph+1, heaph+2, , heapr/2为根的子树已经成为堆;sift 函数执行后,以 heaph, heaph+1, heaph+2, , heapr/2为根的子树都成为堆。sift(heaptype Node x;i=h;x=heapi;j=2*i;finish=0;while( (1) )if(jheapj+1.key) j+;if(x.keyheapj.key)(2);elsefinish=1;(3);heapsort(heaptype Node x;for(h
20、=n/2; h=1; h-)(4);for(r=n; r=2; r-)x=heap1;heap1=heapr;heapr=x;(5);(1) 请在 sift 函数和 heapsort 函数的空缺处填入适当内容,使它们能正确执行。(2) 如果调用 heapsort 函数的参数值 n=10,那么在 heapsort 的执行过程中 sift 函数被调用了多少次?3. 下面是一改进了的快速排序算法,试补充其中的空白语句,并分析该算法所需的最大递归空间是多少。qsort(Sqlist if(iR.rj;R.rmR.ri;if(n-j=j-m) qsort(R, (1) , (2) ); (3) ; el
21、se qsort(R, (4) , (5) ); (6) ; 4. 下面的排序算法的思想是:第一趟比较将最小的元素放在 r1中,最大的元素放在rn中;第二趟比较将次小的放在 r2中,将次大的放在 rn-1中;依次类推,直到待排序程序员考试同步辅导(下午科目 )(第 2 版)141列递增有序(注:代表两个变量的数据交换) 。void sort(sqlist While( (1) )min=max=i;for(j=i+1; (2) ; +j)if( (3) )min=j;else if(rj.keyrmax.key)max=j;if( (4) )rminri;if(max!=n-i+1)if( (
22、5) )rminrn-i+1;else (6) ;i+;/sort5. 选择排序,试补充其中的空白语句。void selectionsort( datatype A, int N)for(int last= (1) ; last=1; last-)int L=indexoflargest(A, last+1);datatype temp;temp= (2) ;AL=Alast;Alast= (3) ;int indexoflargest(datatype A, int size)int indexsofar= (4) ;for(int currentindex=1; currentindexA
23、indexsofar)(5) ;return (6) ;1.1.4 同步练习答案1分析:本题的主要思路是:将当前结点插入到该结点前的有序单链表中,直到当前结点第 1 章 常用算法和数据结构15为空。在将当前结点插入到该结 点前的有序单链表的过程 类似顺序表的策略,从 单链表的表头开始查找,直到找到该结点 应插入的位置,然后完成插入任务。答案:(1) u=v(2) v=v-link (3) h=t(4) u-link=t(5) t-link=v2分析:本题的堆排序的功能是将数组 heap 中的前 n 个记录 按关键码值递减的次序排序,因此要构造一个“ 小根堆” ,需先选择一个关键字作为最小的记录
24、,并与序列中最后一个记录交接,然后对序列中前 n-1 个 记录进行筛选,重新将其 调整 为一个“ 小根堆”,如此反复直至排序的结束。答案:依据上述的分析,本题的答案如下。(1) jr hj=x; i=j(3) j=2*j(4) sift(h,k,n)(5) sift(h,1,r-1)若 n=10,那么 sift 共被调用了 5+9=14 次。3分析:本题修改的快速排序算法相对 1.1.1.5 中的快速排序算法而言,多了 对划分后所得两个子序列的长度进行判断的 if 复合语句,进而先对长度较短的子序列进行快速排序,此目的是为了将快速排序的运行栈空间降为 O( 10logn)。答案:依据上述分析,
25、本题的答案如下。(1) m(2) j-1(3) m=j+1(4) j+1(5) n(6) n=j-14分析:仔细分析,可以看出本 题采用的算法是选择排序算法的 变种, 传统的选择排序算法是每次从未排序序列中找一个最小关键字值的记录;这里的算法是每次找到一个最小关键值的记录,同时找到一个最大关 键字值的记录,使两端有序。答案:依据上述分析,可得问题的如下答案。(1) Irn-i+15分析:本题采用的算法是选择排序算法的变形,第一次从所有的元素中找一个最大的元素与 AN-1交换;第二次从剩下的元素中找一个最大的元素与 AN-2交换;依次类推,直到所有的元素都递增有序。答案:依据上述分析,本题的答案
26、如下。(1) N-1(2) AL(3) temp(4) 0(5) indexsofar=currentindex(6) indexsofar1.2 查 找 算 法1.2.1 考点辅导1.2.1.1 查找的基本概念所谓“查找”(检索)就是在一个含有众多数据元素(记录) 的查找表中找出某个“特定的”数据元素。查找表是一种非常重要的数据结构,是由同一类型的数据元素(记录) 构成的集合。在查找表中,通常通过查找其关键字是否等于给定值来确定查找是否成功。若查到其关键字等于给定值的记录,则称“查找成功”,否则,称“查找失败”。对查找表而言,除了按关键字查找外,查找表的插入和删除是对查找表进行的另两个基本操
27、作。关键字是数据元素(记录)中某个数据项的值,可以标识一个记录,若能惟一标识,则称为主关键字,否则,称为次关键字。考生特别要注意的是:在查找中是和关键字进行比较,而不是和数据元素进行比较,因而在算法描述中要体现关键字比较的特征。同时,考生要特别注意查找在顺序存储结构和链式存储结构上的区别。查找的效率通过平均检索长度(ASL)和所需的辅助空间来确定。在查找其关键字等于给定值的过程中,需要与给定值进行比较的关键字个数的期望值称为检索成功时的平均检索长度。第 1 章 常用算法和数据结构171.2.1.2 静态查找静态查找的算法有顺序查找、折半查找、分块查找和静态树查找等,其中顺序表上的顺序查找、折半
28、查找是静态查找的重点。被查找的顺序表 C 类型定义如下。#define MAXSIZE 1000 /*顺序表的长度*/typedef int KeyType; /*关键字类型为整数类型*/typedef structKeyType key; /*关键字项*/InfoType otherinfo; /*其他数据项*/RecType; /*记录类型*/ typedef struct RecType rMAXSIZE+1; /*r0空作为哨兵*/int length; /*顺序表长度*/SqList; /*顺序表类型*/1顺序查找顺序查找的基本思想是:从表中第 n 个(第 1 个) 记录开始,逐个进
29、行记录的关键字与给定值的比较,若某个记录的关键字和给定值比较相等,则查找成功,找到所查记录;反之,若直到第 1 个(第 n 个)记录,其关键字和给定值比较都不等,则表明该表中没有所查的记录,查找不成功。顺序查找的算法描述如下:int SeqSearch(SqList R, KeyType k) /*在 R.r1R.length中查找关键字*/*为 k 的记录*/ /*从后往前查找*/int i;R.r0.key=k; /*查找一定结束*/for(i=R.length; i=0 i-);if(ik) h=i-1; /*在左子表查找*/else l=i+1; /*在右子表查找*/return(-1
30、);折半查找的过程可用二叉树来描述,中间结点是二叉树的根,左子表相当于左子树,右子表相当于右子树,由此得到的二叉树便为描述折半查找的判定树。折半查找的过程是走了一条从根结点到叶子结点的过程,不论检索成功与失败,查找长度均不超过树的高度,其平均性能为 h= log2(n+1)。折半查找速度快,但表必须有序,且频繁插入和删除不方便。它适合表中元素很少变化而检索频繁的情况;顺序表检索适于检索少而表中元素频繁变化的情况。3分块查找分块查找综合了顺序查找和折半查找的优点,既有动态结构,又适于快速查找。分块查找的基本思想是:将待查找文件等长地分为若干个子文件( 最后一个子文件长度可能会小) 。子文件内的元
31、素无序,但子文件之间有序,即第一个子文件的最高关键字小于第二个子文件的所有记录的关键字,第二个子文件的最高关键字小于第三个子文件的所有记录的关键字,依次类推。再建立一个索引表(文件) ,文件中的每个元素含有各个子文件的最高关键字和各个子文件中第一个元素的地址(下标) ,索引文件按关键字有序。分块查找过程分两步:第一步是在索引表中确定待查记录所在的块,可以顺序查找或折半查找;第二步是在块内顺序查找。设待检索文件有 n 个记录,平均分成 b 块,每块有 s 个记录。若只考虑检索成功的概率,且在块内和索引表中均用顺序检索,则平均查找长度为:ASLbs=Lb+Lw= =(s2+2s+n)/(2s)22
32、/s1nn2ss其中,L b 为确定块的平均查找长度;L w 为块内查找次数。若 s= ,则平均查找长度取最小值: +1。n若对索引表采用二分法检索,则平均查找长度为:第 1 章 常用算法和数据结构19E(n)=Eb+Ew=log2(b+1)+ s1.2.1.3 动态查找动态查找也称树表查找,可执行动态查找的数据结构有二叉排序树、B-树和 B+树等。动态查找表的特点是:表结构本身是在查找过程中动态生成的,即对于给定值 key,若表中存在其关键字等于 key 的记录,则在查找成功后返回,否则插入关键字等于 key 的记录。考生重点掌握二叉排序树,因此这里主要介绍二叉排序树。二叉排序树(简称 BS
33、T)或者是一棵空树,或者是具有下列性质的二叉树。 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值。 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值。 它的左、右子树也分别为二叉排序树。从 BST 的性质可推出二叉排序树的另一个重要性质:按中序遍历该树所得到的中序序列是一个递增有序序列。二叉排序树的查找方法:类似于折半查找,当二叉排序树不空时,首先将给定值 k 与根结点的关键字进行比较,若相等则查找成功;否则依 k 的大小在左子树或右子树上查找。可见,二叉排序树的查找是一个递归过程。二叉排序树的构造:二叉排序树由依次输入的数据元素的序列构造而成。每读入一个元素,建立一
34、个新的结点,并按下列原则插入结点。 若二叉排序树为空树,则新结点为二叉排序树的根结点。 若二叉排序树非空,则新结点的值与根结点比较,若小于根结点,则插入到左子树;否则插入到右子树。二叉排序树的结点删除主要有以下三种情况: 若删除的结点为*p,其双亲为*f,如果*p 没有左右孩子,则可直接删除*p,修改*f 的指针即可。 若*p 结点只有左子树或只有右子树,此时只要令其左子树或右子树直接成为其双亲结点*p 的左子树即可。 若*p 结点的左子树和右子树均不空,如果希望中序遍历该二叉树得到的序列的相对位置在删除结点前后不变,则可采用如下方法:其一,令*p 的左子树为*f 的左子树,而*p 的右子树为
35、*s 的右子树。其中*s 为*p 左子树中最右边的一个结点。其二,令*p 的直接前驱( 或直接后继 )替代*p,然后再从二叉排序树中删除它的直接前驱(或直接后继)。这两种删除方法都能使原二叉排序树的中序遍历结果中结点的先后次序保持不变。就平均时间性能而言,二叉排序树上的查找和折半查找相似。但就维护表的有序性而言,前者更有效,因为无需移动记录,只需修改指针即可完成对二叉排序树的插入和删除操作,且其平均的执行时间均为 O(log2n)。有关平衡二叉树、B-树和 B+树的知识,考生可参考相关参考文献。程序员考试同步辅导(下午科目 )(第 2 版)2011.2.1.4 散列查找(或哈希查找)在记录的存
36、储位置与它的关键字之间建立一个确定的对应关系 f,通过这个对应关系 f找给定值 k 的像 f(k),进行查找的方法为散列方法。称这个关系 f 为哈希函数,按此建立的表为哈希表。设 哈 希 表 是 一 个 地 址 为 0 (n-1)的 向 量 , 冲 突 是 指 由 关 键 字 得 到 的 哈 希 地 址 为 j 0, n-1的位置上已存有记录,而冲突处理就是为该关键字的记录找到另一个空的哈希地址。1. 哈希函数的构造方法哈希函数的构造方法有如下几种。 直接定址法:取关键字或关键字的某个线性函数值为哈希地址,即:H(key)=key 或 H(key)=a*key+b;其中 a 和 b 为常数。
37、数字分析法:假设关键字是以 r 为基的数,并且哈希表中可能出现的关键字都是事先知道的,则可取关键字的若干数位组成哈希地址。 平方取中法:取关键字平方后的中间几位为哈希地址。 折叠法:将关键字分割成位数相同的几部分,然后取这几部分的叠加和。 除余数法:取关键字被某个不大于哈希表表长 m 的数 p 除后所得余数为哈希地址,即:H(key)=key mod p,pm(注:p 的选择很重要)。 随机数法:选择一个随机函数,取关键字的随机函数值为它的哈希地址。即:H(key)=random(key)。2处理冲突的方法处理冲突的方法有如下几种。 开放地址法:H i=(H(key)+di) mod m,i=
38、1, 2, 3, , n,其中,H(key)为哈希函数,m 为哈希表表长,di 为增量序列。若 di=1, 2, , n,则称线性探测再散列; di=12, -12, 22, -22, , n2, -n2,则称二次探测再散列;di=伪随机数序列,则称伪随机探测再散列。 再哈希法:H i=RHi(key), i=1, 2, , n,其中,RH i 均是不同的哈希函数,即在同义词产生地址冲突时计算另一个哈希函数地址,直到冲突不再发生。这种方法不易产生聚集,但增加了计算的时间。 链地址法:将所有关键字为同义词的记录存储在同一线性链表中。 建立一个公共溢出区:关键字和基本表中关键字为同义词的记录,一旦
39、发生冲突,就将其填入溢出表。3. 哈希表查找的性能分析给定 k 值,根据造表时设定的哈希函数求得哈希地址,若表中此位置没有记录,则查找不成功;否则比较关键字,若和给定值相等,则查找成功;若不等,则根据造表时设定的处理冲突的方法查找下一个地址,直至某个位置上表为空或关键字比较相等为止。可见,比较关键字的个数取决于三个因素:哈希函数、处理冲突的方法和哈希表的装填因子。哈希表的平均查找长度不是对象个数 n 的函数,而是装填因子 ( =n/m)的函数。线性探测法成功查找的平均查找长度为(1+1/(1- )/2,而不成功查找的平均查找长度为第 1 章 常用算法和数据结构21(1+1/(1- )2)/2;
40、二次探测再散列法的成功查找的平均查找长度为 ,而不成功查 (1)/loge找的平均查找长度为 1/(1- );链地址法的成功查找的平均查找长度为 1+ /2,而不成功查找的平均查找长度为 。e程序员考试同步辅导(下午科目 )(第 2 版)2211.2.2 典型例题分析例 1 阅读以下说明和 C 函数,将应填入 (n) 处的字句写在答题纸的对应栏内。(2009 年下半年试题二)【说明 1】函数 Counter ( int n , int w )的功能是计算整数 n 的二进制表示形式中 1 的个数,同时用数组 w 记录该二进制数中 1 所在位置的权。例如,十进制数 22 的二进制表示为 10110
41、。对于该二进制数,1 的个数为 3,在 w0中存入 2 (即 21),w1 中存入 4 (即 22),w2中存入 16 (即 24)。 【C 函数 1】 int Counter ( int n , int w ) int i = 0 , k = l ; while ( (1) ) if ( n % 2 ) w i + + = k ;n = n / 2 ; (2) ; return i; 【说明 2】函数 Smove (int A , int n )的功能是将数组中所有的奇数都放到所有偶数之前。其过程为:设置数组元素下标索引 i(初值为 0)和 j(初值为 n-1),从数组的两端开始检查元素的奇
42、偶性。若 Ai、Aj 都是奇数,则从前往后找出一个偶数,再与 Aj进行交换;若 Ai、Aj都是偶数,则从后往前找出一个奇数,再与 Ai进行交换;若 Ai是偶数而 Aj是奇数,则交换两者,直到将所有的奇数都排在所有偶数之前为止。【C 函数 2】void Smove (int A , int n) int temp , i= 0 , j = n-l ; if ( n length length length j+; else i= (1) ; j = 0 ; /*i 值回退,为继续查找 T 做准备*/ If( (2) ) /*在 S 中找到与 T 相同的子串*/i= (3) ; /*计算 S 中子
43、串 T 的起始下标*/for ( k = i + T. length ; k length ; k + + ) /*通过覆盖子串 T 进行删除*/S- ch (4) =S- chk ;S-length = ( 5 ) ; /*更新 S 的长度*/ else break ; /*串 S 中不存在子串 T*/分析:进行查找时,如果 T 中前 j 个字符和 S 中从下标 i 开始的 j 个字符相同,则 i 都会增加到 i+j,如果 T 中第 j+1 个和 S 中下一个字符不同,则查找结束,此时需将 i 的值(此时已是 i+j)回退到此遍历进行前的 值(即 i)的下一个字符位置即 i-j+1,并开始新
44、一轮的遍历查找,所以空(1) 处应为 i-j+1。一 轮查找结束后,如果 j 的值 等于 T 的长度,表明 S 中存在子字符串和 T 相同,因为只要其中有一个字母不同,j 就会清零一次,最后的值就一定会小于T 的长度,所以空(2) 处应为 j=T.length。找到时,同时 i 的 值也已经增加了 T 的长度,所以要计算 S 中子串 T 的起始下标只需 i 减去 T 的长度即可,所以空(3)处应为 i-T.length 或者 i-j。通过覆盖子串 T 进行删除时,从 i + T. length 到 s-length,只需将每个字符替换到其前 j 处,即空(4) 处应为 k-j。替 换后,将 S
45、 的长度减去 T 的长度即可更新 S 的长度。所以空(5)处应为 s-length-T.length。答案:(1) i-j+1(2) j=T.length(3) i-T.length 或 i-j第 1 章 常用算法和数据结构25(4) k-j(5) s-length-T.length例 3 阅读以下说明、C 函数和问题,将解答填入答题纸的对应栏内。(2009 年上半年试题三)【说明】二叉查找树又称为二叉排序树,它或者是一棵空树,或者是具有如下性质的二叉树: 若它的左子树非空,则其左子树上所有结点的键值均小于根结点的键值; 若它的右子树非空,则其右子树上所有结点的键值均大于根结点的键值; 左、右
46、子树本身就是二叉查找树。设二叉查找树采用二叉链表存储结构,链表结点类型定义如下:typedef struct BiTnode int key_value ; /*结点的键值,为非负整数*/struct BiTnode * left , * right ; /*结点的左、右子树指针*/ * BSTree ; 函数 find_key ( root , key )的功能是用递归方式在给定的二叉查找树(root 指向根结点)中查找键值为 key 的结点并返回结点的指针;若找不到,则返回空指针。【C 函数】BSTree find _ key ( BSTree root , int key)If( (l)
47、 )return NULL ;else if(key=root-key_value)return (2) ;else if(keykey_value)return (3) ;elsereturn (4) ;【问题 1】请将函数 find_key 中应填入(1)到(4)处的字句写在答题纸的对应栏内。【问题 2】若某二叉查找树中有 n 个结点,则查找一个给定关键字时,需要比较的结点个数取决于 (5) 。分析:依题意,当遍历到叶子 节点的左右子树时递归结束,返回 NULL,所以空(1)处应为 root=NULL。在进行查找的 过程中,如果 key 等于 root 的键值, 则 root 就是所要查找
48、的结点,所以空(2) 处应为 root。依题意若它的左子树非空, 则 其左子树上所有结点的键值均小于根结点的键值;若它的右子树非空, 则其右子树上所有结 点的键值均大于根结点的键值,所以如果 key 小于 root 的键值 ,应查询其左子树,大于 root 则查询其右子树,所以空 (3)和空(4)处分别为 find_key(root-left, key)和 find_key(root-right, key)。若某二叉查找树总有 n 个结点,则查找一个的给定关键 字时,需要比 较的结点个数取决于关 键字所在结点的层数和二叉树的高度。因为若结点存在,层次越高,查找次数就越多,若结点不存在,则就要遍历整个程