五、哈希表的删除操作
从哈希表中删除记录时,要作特殊的处理,相应地,要修改查找算法。
六、对静态查找表,有时也可能找到不发生冲突的哈希函数,即此时的哈希表ASL=0,称此类哈希函数为理想(perfect)的哈希函数。
1. 顺序表和有序表的查找方法及其平均查找长度的计算方法。
2. 静态查找树的构造方法和查找算法,理解静态查找树和折半查找的关系
3. 熟练掌握二叉排序树的构造和查找方法。
4. 理解B-树,B+树和键树的特点以及它们的建树过程
5. 熟练掌握哈希表的构造方法,深刻理解哈希表与其它结构的表的实质性的差别
6. 掌握按定义计算各种查找方法在等概率情况下查找成功时的平均查找长度。
第十章排序
10.1概述
10.2插入排序
10.3快速排序
10.4堆排序
10.5归并排序
10.6基数排序
10.7各种排序方法的综合比较
10.8外部排序
10.1概述
一、排序的定义
二、内部排序和外部排序
三、内部排序方法的分类
一、什么是排序
排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列。
例如:将下列关键字序列
52,49,80,36,14,58,61,23,97,75
调整为
14,23,36,49,52,58,61,75,80,97
一般情况下,
假设含n个记录的序列为{R1,R2,…,Rn},其相应的关键字序列为{K1,K2,…,Kn}这些关键字相互之间可以进行比较,即在它们之间存在着这样一个关系。Kp1<=Kp2<=….Kp,按此固有关系将上式记录序列重新排列为{Rp1,Rp2,…,Rpn}的操作称作排序。
二、内部排序和外部排序
若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序;
反之,若参加排序的记录数量很大,整个序列的排序过程不可能在内存中完成,则称此类排序问题为外部排序。
在本章内先讨论内部排序的各种方法,然后介绍外部排序的基本过程。
三、内部排序的方法
内部排序的过程是一个逐步扩大记录的有序序列长度的过程。
逐步扩大记录有序序列长度的方法大致有下列几类:插入类交换类选择类归并类其他方法
1. 插入类
将无序子序列中的一个或几个记录“插入”到有序序列中,从而增加记录的有序子序列的长度。
2.交换类
通过“交换”无序序列中的记录从而得到其中关键字最小或最大的记录,并将它加入到有序序列中,以此方法增加记录的有序序列的长度。
3.选择类
从记录的无序子序列中“选择”关键字最小或最大的记录,并将它加入到有序子序列中,以此方法增加记录的有序子序列的长度。
4.归并类
通过“归并”两个或两个以上的记录有序子序列,逐步增加记录有序序列的长度。
5.其它方法
10.2插入排序
一趟直接插入排序的基本思想:
实现“一趟插入排序”可分为三步进行
1. 在R[1,..i-1]中查找R[i]的插入位置;R[1-j]<=R[i]<R[j+1,..i-1]
2. 将R[j+1,..i-1]中的所有记录均后移一个位置;
3. 将R[i]复制到R[j+1]的位置上。
直接插入排序
折半插入排序
表插入排序
希尔排序(又称缩小增量排序)
一、直接插入排序
利用顺序查找实现“在R[1,…,i-1]中查找R[i]的插入位置”
算法的实现要点:
1. 从R[i-1]起向前进行顺序查找,监视哨设置在R[0];
R[0]=R[i]; //设置“哨兵”
for(j=i-1;R[0].key<R[j].key;--j) //从后往前找
循环结束表明R[i]的插入位置为j+1
2. 对于查找过程中的那些关键字不小于R[i].key的记录,并在查找的同时实现记录向后移动。
for(j=i-1;R[0].key<R[j].key;--j)
R[j+1]=R[j]
3. i=2,3,…n,实现整个序列的排序。
for(i=2; i<n;++i)
if(R[i].key<R[i-1].key)
{
将R[i]插入到R[1,…,i-1]中
}
41_001 |
void InsertionSort(Elem R[], int n) { //对记录序列R[1,...n]作直接插入排序 for(i = 2; i <= n; ++ i) { if(R[i].key < R[i-1].key) { R[0] = R[i]; //赋值为监视哨 for(j = i - 1; R[0].key < R[j].key; -- j) R[j+1] = R[j]; //记录后移 R[j+1] = R[0]; //插入到正确位置 } } } |
排序的时间分析:
实现排序的基本操作有两个:
(1)“比较”序列中两个关键字的大小
(2)“移动”记录
对于直接插入排序:
最好的情况(关键字在记录序列中顺序有序)
最坏的情况(关键字在记录序列中逆序有序)
二、折半插入排序
因为R[1,…,i-1]是一个关键字有序的有序序列,则可以利用折半查找实现“在R[1,…,i-1]中查找R[i]的插入位置”,如此实现的插入排序为折半插入排序。
41_002 |
void BiInsertionSort(Elem R[], int n) { for(i = 2; i <= L.length; ++ i) { R[0] = R[i]; //将R[i]暂存到R[0] //在R[1,...i-1]中折半查找插入位置 low = 1; high = i - 1; while(low <= high) { m = (low + high) / 2; //折半 if(R[0].key < R[m].key) high = m - 1; //插入点在低半区 else low = m + 1; //拆入点在高半区 } for(j = i - 1; j >= high + 1; -- j) R[j+1] = R[j]; //记录后移 R[high+1] = R[0]; //插入 }//for }//BiInsertionSort |
三、表插入排序
为了减少在排序过程中进行的“移动”记录的操作,必须改变排序过程中采用的存储结构。利用静态链表进行排序,并在排序完成之后,一次性地调整各个记录相互之间的位置,即将每个记录都调整到它们所应该在的位置上。
41_003 |
void LInsertionSort(Elem SL[], int n) { //对记录序列SL[1,...n]作表插入排序 SL[0].key = MAXINT; SL[0].next = 1; SL[1].next = 0; for(i = 2; i <= n; ++ i) { for(j = 0, k = SL[0].next; SL[k].key <= SL[i].key; j = k, k = SL[k].next) { SL[j].next = i; SL[I].next = k; } //结点i插入在结点j和结点k之间 } } |
如何在排序之后调整记录序列?
算法中使用了三个指针:
其中:p指示第i个记录的当前位置;i指示第i个记录应在的位置;q指示第i+1个记录的当前位置。
42_001 |
void Arrange(Elem SL[], int n) { p = SL[0].next; //p指示第一个记录的当前位置 for(i = 1; i < n; ++ i) { while(p < i) p = SL[p].next; q = SL[p].next; //q指示尚未调整的表尾 if(p != i) { SL[p] <---> SL[i]; //交换记录,使第i个记录到位 SL[i].next = p; //指向被移走的记录 } p = q; //p指示尚未调整的表尾,为找第i+1个记录做准备 } } |