这是我的博文系列《内部排序算法》的第一篇。所谓排序,就是要整理文件中的记录,使之按关键字递增(或递减)次序排列起来。所谓内部排序,是指在排序过程中,若整个文件都是放在内存中处理,排序时不涉及数据的内、外存交换(外排序的定义则相反)。
内部排序法按照策略可以划分为五类:插入排序、选择排序、交换排序、归并排序和分配排序。待排文件的存储方式采用顺序表(或直接用向量)作为存储结构(其他的存储结构还有以链表作为存储结构等)。
在这个系列的博文中,我按照排序算法的给出,排序算法的分析(包括算法的时空复杂度分析)来展开。对于排序算法的分析包括:对于算法思路的分析,对于算法时间复杂度的分析,对于算法空间复杂度的分析,对于排序算法稳定性的分析(待排序的文件中,若存在多个关键字相同的记录,经过排序后这些具有相同关键字的记录之间的相对次序保持不变,该排序方法是稳定的)。
本文来介绍交换排序。交换排序的基本思想是:两两比较待排序记录的关键字,发现两个记录的次序相反时即进行交换,直到没有反序的记录为止。应用交换排序基本思想的主要排序方法有:冒泡排序和快速排序。
首先来介绍冒泡排序。给出排序算法:
Step1:初始化。初始化文件记录R,R[1..n]为无序区。
Step2:扫描。从无序区底部向上依次比较相邻的两个气泡的重量,若发现轻者在下、重者在上,则交换二者的位置。即依次比较(R[n],R[n-1]),(R[n-1],R[n-2]),…,(R[2],R[1]);对于每对气泡(R[j+1],R[j]),若R[j+1].key<R[j].key,则交换R[j+1]和R[j]的内容。
Step3:判断。判断文件是否有序,如果有序,则结束算法,否则返回Step2,继续进行下一趟扫描。
冒泡算法特征:
#include<cstdio> #include<cstdlib> #include<algorithm> using namespace std; #define MaxSize 100 typedef int KeyType; void BubbleSort(KeyType R[],int n){ //冒泡排序算法 //R(l..n)是待排序的文件,采用自下向上扫描,对R做冒泡排序 bool exchange; //交换标志 for(int i=1;i<n;i++){ //最多做n-1趟排序 exchange=false; //本趟排序开始前,交换标志应为假 for(int j=n-1;j>=i;j--){//对当前无序区R[i..n]自下向上扫描 if(R[j+1]<R[j]) swap(R[j],R[j+1]);//交换记录 exchange=true;//发生了交换,故将交换标志置为真 } if(!exchange)//本趟排序未发生交换,提前终止算法 return; } } //*****************************测试函数*************************** int main(){ int n,R[MaxSize]; printf("请输入数组大小:\n"); scanf("%d",&n); printf("请输入数组元素:\n"); for(int i=1;i<=n;i++) scanf("%d",&R[i]); printf("Before sorting:\n"); for(int i=1;i<=n;i++) printf("%d ",R[i]); printf("\n"); BubbleSort(R,n); printf("After sorting:\n"); for(int i=1;i<=n;i++) printf("%d ",R[i]); printf("\n"); return 0; }
冒泡排序的具体过程举例就不给出了,读者不难在纸上模拟出。对冒泡算法进行分析:
- 空间复杂度分析:所需要的辅助空间为O(1),即就地排序(若排序算法所需的辅助空间并不依赖于问题的规模n,即辅助空间是O(1),则称之为就地排序)。
- 算法的最坏时间复杂度:若初始文件是反序的,需要进行n-1趟排序。每趟排序要进行n-i次关键字的比较(1≤i≤n-1),在这种情况下,比较次数均达到最大值:n(n-1)/2=O(n^2)。
- 算法稳定性:显然冒泡排序是稳定的。
下面来介绍快速排序法。给出排序算法:(设当前待排序的无序区为R[low..high])
Step1:分解。在R[low..high]中任选一个记录作为基准(Pivot),以此基准将当前无序区划分为左、右两个较小的子区间R[low..pivotpos-1)和R[pivotpos+1..high],并使左边子区间中所有记录的关键字均小于等于基准记录(不妨记为pivot)的关键字pivot,右边的子区间中所有记录的关键字均大于等于pivot,而基准记录pivot则位于正确的位置(pivotpos)上,它无须参加后续的排序。
Step2:求解。通过递归调用快速排序对左、右子区间R[low..pivotpos-1]和R[pivotpos+1..high]快速排序。
Step3:组合。因为当"求解"步骤中的两个递归调用结束时,其左、右两个子区间已有序。对快速排序而言,"组合"步骤无须做什么,可看作是空操作。
算法特征:
#include<cstdio> #include<cstdlib> #include<algorithm> #include<time.h> using namespace std; #define MaxSize 100 typedef int KeyType; int Partition(KeyType R[],int i,int j){ //对R[low..high]做划分,并返回基准记录的位置 //用区间的第1个记录作为基准 KeyType pivot=R[i];//用区间的第1个记录作为基准 while(i<j){//从区间两端交替向中间扫描,直至i=j为止 while(i<j&&R[j]>=pivot)//pivot相当于在位置i上 j--;//从右向左扫描,查找第1个关键字小于pivot.key的记录R[j] if(i<j) //表示找到的R[j]的关键字<pivot.key R[i++]=R[j];//相当于交换R[i]和R[j],交换后i指针加1 while(i<j&&R[i]<=pivot)//pivot相当于在位置j上 i++;//从左向右扫描,查找第1个关键字大于pivot.key的记录R[i] if(i<j)//表示找到了R[i],使R[i].key>pivot.key R[j--]=R[i];//相当于交换R[i]和R[j],交换后j指针减1 } R[i]=pivot;//基准记录已被最后定位 return i;//基准元素位置 } int RandomPartition(KeyType R[],int i,int j){ //对R[low..high]做随机划分,并返回基准记录的位置 //取位于low和high之间的随机数k(low≤k≤high),用R[k]作为基准 srand((unsigned)time(NULL)); int k=i+rand()%(j-i+1); swap(R[k],R[i]); //下面的步骤与Partition函数相同,不再作注释 KeyType pivot=R[i]; while(i<j){ while(i<j&&R[j]>=pivot) j--; if(i<j) R[i++]=R[j]; while(i<j&&R[i]<=pivot) i++; if(i<j) R[j--]=R[i]; } R[i]=pivot; return i; } void QuickSort(KeyType R[],int low,int high){ //对R[low..high]快速排序 int pivotpos;//划分后的基准记录的位置 if(low<high){//仅当区间长度大于1时才须排序 //pivotpos=Partition(R,low,high); pivotpos=RandomPartition(R,low,high);//对R[low..high]做划分 QuickSort(R,low,pivotpos-1);//对左区间递归排序 QuickSort(R,pivotpos+1,high);//对右区间递归排序 } } //*****************************测试函数*************************** int main(){ int n,R[MaxSize]; printf("请输入数组大小:\n"); scanf("%d",&n); printf("请输入数组元素:\n"); for(int i=1;i<=n;i++) scanf("%d",&R[i]); printf("Before sorting:\n"); for(int i=1;i<=n;i++) printf("%d ",R[i]); printf("\n"); QuickSort(R,1,n); printf("After sorting:\n"); for(int i=1;i<=n;i++) printf("%d ",R[i]); printf("\n"); return 0; }
原文地址:http://blog.csdn.net/u013275340/article/details/38822629