排序的基本概念
什么是排序
排序算法的应用
排序算法的评价指标
排序算法的分类
插入排序
算法思想
- 我们会认为当前要处理的这个元素之前的序列是已经排好序的
算法实现
不带哨兵
带哨兵
#include<stdio.h>
void InsertSort(int a[10]){
for(int i = 1; i<=10; i++){
if(a[i]<a[i-1]){
a[0] = a[i];
int j;
for(j = i-1;a[j]>a[0];j--){
a[j+1] = a[j];
}
a[j+1] = a[0];
}
}
}
int main(){
int a[11] = {0,2,4,6,8,10,1,3,5,7,9};
for(int i = 1; i<=10; i++){
printf("%d ",a[i]);
}
printf("\n");
InsertSort(a);
for(int i = 1; i<=10; i++){
printf("%d ",a[i]);
}
return 0;
}
算法效率分析
空间复杂度
时间复杂度
稳定性
优化——折半插入排序
算法思想
算法实现
对链表进行插入排序
希尔排序
算法思想
- 对插入排序的一个优化
算法实现
- 一开始让
i = d + 1
的意思是刚开始我们让i指向第1个子表当中的第2个元素的位置,从这里开始直接插入排序
#include<stdio.h>
void ShellSort(int a[]){
for(int d = 10/2; d >= 1; d = d/2){
for(int i = d+1; i <= 10; i++){
if(a[i] < a[i-d]){
a[0] = a[i];
int j;
for(j = i-d; j > 0 && a[j] > a[0]; j -= d){
a[j+d] = a[j];
}
a[j+d] = a[0];
}
}
}
}
int main(){
int a[11] = {0,2,4,6,8,10,1,3,5,7,9};
for(int i = 1; i<=10; i++){
printf("%d ",a[i]);
}
printf("\n");
ShellSort(a);
for(int i = 1; i<=10; i++){
printf("%d ",a[i]);
}
return 0;
}
算法性能分析
空间复杂度
时间复杂度
稳定性
冒泡排序
冒泡?
冒泡排序
算法实现
#include<stdio.h>
void BubbleSort(int a[]){
for(int i = 1;i <= 10;i++){
int flag = 0;
for(int j = 10;j > i;j--){
if(a[j-1] > a[j]){
int temp = a[j];
a[j] = a[j-1];
a[j-1] = temp;
flag = 1;
}
}
if(flag == 0){
return ;
}
}
}
int main(){
int a[11] = {0,2,4,6,8,10,1,3,5,7,9};
for(int i = 1; i<=10; i++){
printf("%d ",a[i]);
}
printf("\n");
BubbleSort(a);
for(int i = 1; i<=10; i++){
printf("%d ",a[i]);
}
return 0;
}
算法性能分析
空间&时间复杂度
稳定性
- 冒泡排序是稳定的
冒泡排序是否适用于链表?
快速排序
算法思想
代码实现
#include<stdio.h>
int Partition(int a[],int low,int high){
int pivot = a[low];
while(low < high){
while(low < high && a[high]>=pivot) high--;
a[low] = a[high];
while(low < high && a[low]<=pivot) low++;
a[high] = a[low];
}
a[low] = pivot;
return low;
}
void QuickSort(int a[],int low,int high){
if(low < high){
int pivotpos = Partition(a,low,high);
QuickSort(a,low,pivotpos-1);
QuickSort(a,pivotpos+1,high);
}
}
int main(){
int a[11] = {0,2,4,6,8,10,1,3,5,7,9};
for(int i = 1; i<=10; i++){
printf("%d ",a[i]);
}
printf("\n");
QuickSort(a,1,10);
for(int i = 1; i<=10; i++){
printf("%d ",a[i]);
}
return 0;
}
算法效率分析
时间&空间复杂度
稳定性
简单选择排序
算法原理
代码实现
#include<stdio.h>
void QuickSort(int a[]){
for(int i = 1;i <= 10;i++){
int minpos = i;
for(int j = i+1;j <= 10;j++)
if(a[j] < a[minpos]) minpos = j;
if(minpos != i){
int temp = a[i];
a[i] = a[minpos];
a[minpos] = temp;
}
}
}
int main(){
int a[11] = {0,2,4,6,8,10,1,3,5,7,9};
for(int i = 1; i<=10; i++){
printf("%d ",a[i]);
}
printf("\n");
QuickSort(a);
for(int i = 1; i<=10; i++){
printf("%d ",a[i]);
}
return 0;
}
算法性能分析
空间&时间复杂度
稳定性&适用性
堆排序
回忆:二叉树的顺序存储
什么是“堆(Heap)”?
如何基于“堆”进行排序?
建立大根堆
建立大根堆的代码实现
void BuildMaxHeap(int a[],int len){
for(int i = len/2; i>=1; i--)
AdjustMaxHeap(a,i,len);
}
void AdjustMaxHeap(int a[],int k,int len){
a[0] = a[k];
for(int i = 2*k; i <= len; i *= 2){
if(i < len && a[i]<a[i+1]) i += 1;
if(a[0] > a[i]) break;
a[k] = a[i];
k = i; //k的位置其实就是我们正在筛选的有可能插入a[0]元素的位置
}
a[k] = a[0];
}
基于大根堆进行排序
基于大根堆进行排序的代码实现
void HeapSort(int a[],int len){
BuildMaxHeap(a,len);
for(int i = len; i >= 2; i--){
int temp = a[1];
a[1] = a[i];
a[i] = temp;
AdjustMaxHeap(a,1,i-1);
}
}
堆排序的代码实现
#include<stdio.h>
void AdjustMaxHeap(int a[],int k,int len){
a[0] = a[k];
for(int i = 2*k; i <= len; i *= 2){
if(i < len && a[i]<a[i+1]) i += 1;
if(a[0] > a[i]) break;
a[k] = a[i];
k = i; //k的位置其实就是我们正在筛选的有可能插入a[0]元素的位置
}
a[k] = a[0];
}
void BuildMaxHeap(int a[],int len){
for(int i = len/2; i>=1; i--)
AdjustMaxHeap(a,i,len);
}
void HeapSort(int a[],int len){
BuildMaxHeap(a,len);
for(int i = len; i >= 2; i--){
int temp = a[1];
a[1] = a[i];
a[i] = temp;
AdjustMaxHeap(a,1,i-1);
}
}
int main(){
int a[11] = {0,2,4,6,8,10,1,3,5,7,9};
for(int i = 1; i<=10; i++){
printf("%d ",a[i]);
}
printf("\n");
HeapSort(a,10);
for(int i = 1; i<=10; i++){
printf("%d ",a[i]);
}
return 0;
}
堆排序算法效率分析
空间&时间复杂度
稳定性
练习:基于“小根堆”的建堆和排序
堆的插入删除
在堆中插入新元素
在堆中删除元素
归并排序
什么是Merge(归并/合并)
“2路”归并
“4路”归并
手动模拟归并排序
归并的代码实现
void Merge(int a[],int low,int mid,int high){
int i,j,k;
for(k=low; k<=high; k++){
b[k] = a[k];
}
for(i=low,j=mid+1,k=i; i<=mid && j<=high; k++){
if(b[i]<=b[j]){
a[k] = b[i++];
}else{
a[k] = b[j++];
}
}
while(i<=mid) a[k++] = b[i++];
while(j<=high) a[k++] = b[j++];
}
归并排序的代码实现
#include<stdio.h>
int b[11] = {0};
void Merge(int a[],int low,int mid,int high){
int i,j,k;
for(k=low; k<=high; k++){
b[k] = a[k];
}
for(i=low,j=mid+1,k=i; i<=mid && j<=high; k++){
if(b[i]<=b[j]){
a[k] = b[i++];
}else{
a[k] = b[j++];
}
}
while(i<=mid) a[k++] = b[i++];
while(j<=high) a[k++] = b[j++];
}
void MergeSort(int a[],int low,int high){
if(low < high){
int mid = (low + high)/2;
MergeSort(a,low,mid);
MergeSort(a,mid+1,high);
Merge(a,low,mid,high);
}
}
int main(){
int a[11] = {0,2,4,6,8,10,1,3,5,7,9};
for(int i = 1; i<=10; i++){
printf("%d ",a[i]);
}
printf("\n");
MergeSort(a,1,10);
for(int i = 1; i<=10; i++){
printf("%d ",a[i]);
}
return 0;
}
算法效率分析
基数排序
算法思想
算法效率分析
空间&时间复杂度
稳定性
基数排序的应用
外部排序
外存、内存之间的数据交换
- 要修改磁盘存储空间里面的数据,首先要在内存里边申请开辟一片和块大小保持一致的缓冲区,把磁盘块的数据读入内存里边
外部排序原理
构造初始“归并段”
- 读入第一块和第二块磁盘块进入内存
- 对两块输入缓冲区中的数据进行内部排序,得到两块有序的子序列
- 把有序的子序列依次放回到输出缓冲区中,写回磁盘
- 同理,依次把后续的磁盘块读入内存,在内存中进行排序后再通过输出缓冲区写回磁盘
第一趟归并
- 把归并段1和归并段2中更小的一段分别读入内存,分别放到缓冲区1和缓冲区2,依次对比两个归并段中最小的元素,依次放到输入缓冲区
- 当这个输入缓冲区空了之后,我们就需要立即用归并段1的后一个磁盘块里的内容来填补输入缓冲区1
- 这么做可以保证我们的输入缓冲区1当中永远是包含了归并段1里边此时暂时还没有被归并但是数值最小的一个记录
- 然后我们就可以用类似的方法把后面的两个归并段给归并成一整个更长的有序序列
第二趟归并
- 我们会把归并段1和归并段2中更小的一个段分别读入内存,接下来用归并排序的方法挑选出3个最小的记录,凑足一整块之后把其写回外存
- 每当一个缓冲区空的时候,我们都要立即用新的归并段里的磁盘块填补空缺,不然归并排序会出现问题
- 比如下图,我们如果在输入缓冲区1空了之后把13放入输出缓冲区,然后再读入两块,如果读入的输入缓冲区中的数据有比13小的话,归并排序就会出现问题
- 这里还有一个需要注意的点,我们归并之后的更长的子序列其实是放在磁盘的另外一片空间当中的,以前的那两片空间我们会归还给系统,这里这样看起来像是同一片空间的表述只是为了美观
第三趟归并
时间开销分析
如何优化?——多路归并
优化——减少初始归并段数量
纠正:什么叫做多路平衡归并?
败者树
多路平衡归并带来的问题
什么是败者树?
败者树在多路平衡归并中的应用
- 叶子结点空缺了就立即用相应的归并段里面的数据进行比较,只需比较新加入的数据到根节点的这一条路径,其他路径不需要进行比较
败者树的实现思路
置换_选择排序
用土办法构造初始归并段
置换-选择排序
- 依次读入初始待排序文件数据,当缓冲区满了的时候选择一个缓冲区中最小的文件记录加入初始归并段,并把内存工作区
MINIMAX
字段设为缓冲区中刚输出的字段大小,以后要输出缓冲区的字段大小不能小于这个MINIMAX
值,保证归并段内的数据有序
最佳归并树
归并树的神秘性质
构造2路归并的最佳归并树
多路归并的情况
多路归并的最佳归并树
如果减少一个归并段
正确的归并方法
添加虚段的数量
- 比如现在有19个初始归并段要用8路归并构造一个最佳归并树,
(19-1)%(8-1) = 4
,所以我们只需要补充(8-1)-4 = 3
个长度为0的虚段,那么这些虚段和初始归并段的总数就是19+3=22
,此时(22-1)/(8-1) = 3
说明可以构成一棵严格的8叉树,可以让读写磁盘的次数最少