一、排序的介绍
1、排序的目的:便于查找
2、在排序问题中,通常将数据元素称为记录
3、排序算法的分类:
- 插入排序 —— 直接插入排序、希尔排序
- 交换排序 —— 起泡排序、快速排序
- 选择排序 —— 简单选择排序、堆排序
- 归并排序 —— 二路归并排序非递归实现、递归实现
- 分配排序 —— 桶式排序、基数排序
二、插入排序
主要思想:每次将一个待排序的记录按其关键码的大小插入到一个已经排好序的有序序列中,直至全部记录排好序
2.1 直接插入排序
基本思想:依次将待排序序列中的每一个记录插入到一个已排好序的序列中,直到全部记录都排好序
void InsertSort(int a[],int n){
int j;
for(int i=1;i<n;i++){
int x = a[i]; //从第一个元素开始,将该元素取出来
for(j=i-1;j>=0;j--){
//循环与该元素前面的元素进行比较找到合适的位置
if(x<a[j])
a[j+1] = a[j]; //如果找到了,那么就这个位置的元素向后移动一位
else
break;
}
a[j+1] = x; //将对应的值补到空缺的位置
}
}
2.2 希尔排序
基本思想:先将整个待排序记录序列分割成若干子序列,在子序列内分别进行直接插入排序,待整个序列基本有序时,再对全体记录进行依次直接插入排序
void ShellSort(int a[],int n){
for(int d=n/2;d>=1;d=d/2){
//设置间隔
for(int j=0;j<n-d;j++){
if(a[j] < a[j+d]){
int temp = a[j];
a[j] = a[j+d];
a[j+d] = temp;
}
}
}
}
三、交换排序
3.1 起泡排序
基本思想:两两比较相邻记录的关键码。如果反序则交换,直到没有反序的记录为止。
void BubbleSort(int a[],int n){
for(int i=1;i<n;i++){
//外循环:控制循环次数,一共循环n-1次
for(int j=0;j<n-i;j++){
//内循环:控制每轮要交换的次数,每轮交换n-i次
if(a[j] > a[j+1]){
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
}
3.2 快速排序
快速排序又称为分区交换排序,其基本思想是:首先选一个轴值(povit,即比较的基准),将待排序记录划分成独立的两部分,左侧记录的关键码均小于或等于轴值,右侧记录的关键码均大于或等于轴值,然后分别对这两部分重复上述过程,直到整个序列有序
快速排序一次划分算法
int Partition(int a[],int first,int end){
int i = first; //将i、j分别指向数组的第一个和最后一个元素下标
int j = end; //其中i指向的数组的第一个元素为轴值
while(i<j){
while(i<j && a[i] <= a[j]) //右侧进行扫描,直至找到右侧的元素小于轴值
j--; //如果右侧元素大于轴值,则下标前移一位
if(i<j){
int temp = a[i]; //将较小的元素交换到前面
a[i] = a[j];
a[j] = temp;
i++; //左侧下标前移一位(注意:上述交换完之后,j就指向轴值的数组下标,而不是i)
}
while(i<j && a[i]<=a[j]) //左侧扫描,直至找到左侧元素大于轴值的
i++; //如果左侧元素小于轴值,则下标前移一位
if(i<j){
int temp = a[j]; //再将较小的元素交换到前面,此时轴值又回到了i指向的位置
a[j] = a[i];
a[i] = temp;
j--;
}
}
return i; //将轴值返回
}
通过上述一次划分算法排序之后,得到的数组的轴值两边还没有排好序,因此我们用递归的方法再对左右两边逐渐划分排序
快速排序算法
void QuickSort(int a[],int first,int end){
if(first < end){
int pivot = Partition(a,first,end); //一次划分
QuickSort(a,first,pivot-1); //递归地对左侧子序列进行快速排序
QuickSort(a,pivot+1,end); //递归地对右侧子序列进行快速排序
}
}
四、选择排序
4.1 简单选择排序
void SelectSort(int a[],int n){
for(int i=0;i<n-1;i++){
//外循环:控制循环次数,共循环n-1次
int index = i; //得到要存放的指定的数组元素的位置的下标
for(int j=i+1;j<n;j++){
//循环,找到数值最小的元素的下标
if(a[index]>a[j])
index = j;
}
if(index != i){
//判断,如果得到的最小元素的下标与指定的存放位置的下标不同
int temp = a[index]; //交换元素
a[index] = a[i];
a[i] = temp;
}
}
}
4.2 堆排序
堆的定义:堆是具有下列性质的完全二叉树:每个结点的值都小于或等于其左右孩子结点的值(称为小根堆);或每个节点的值都大于或等于其左右孩子结点的值(称为大根堆)
筛选法调整堆的算法目的:使得乱序堆调整为一个大根堆或小根堆
void Sift(int a[],int k,int m){
//参数k表示要筛选的结点,参数m表示最后一个元素下标
int i = k; //i指向要筛选的结点的数组下标
int j = 2*i+1; //j就指要筛选的结点的左孩子的下标(注意:因为i是从0开始计数的,所以2*i+1才表示它的左孩子)
while(j<m){
//循环条件:若结点j已经是叶子结点(m指向最后一个结点),则筛选完毕,退出循环,
if(j<m-1 && a[j]<a[j+1]) //找出左右孩子中数组较大的结点
j++;
if(a[i]>a[j]) //如果该结点大于它的左右孩子中数值较大的结点,筛选完毕
break;
else{
//否则,交换
int temp = a[i];
a[i] = a[j];
a[j] = temp;
i = j; //下标下沉,再判断结点大小
j = 2*i+1;
}
}
}
堆排序算法
堆排序的基本思想:首先将待排序的记录序列构造成一个堆,此时,选出了堆中所有记录的最大者即堆顶记录。然后将堆顶记录移走,并将剩余的记录再次调整成堆,这样又找出了次大的记录。以此类推,直至堆中只有一个记录为止
void HeapSort(int a[],int n){
for(int i=n/2-1;i>=0;i--) //初始建堆,从最后一个分支结点至根节点,使完全二叉树上的所有结点的值均满足左右孩子结点大于(小于)双亲结点的值
Sift(a,i,n);
for(int i=0;i<n-1;i++){
//重复执行移走堆顶及重建堆的操作,共执行n-1次
int temp = a[0];
a[0] = a[n-i-1];
a[n-i-1] = temp; //让堆顶元素和堆的最后一个元素进行交换
Sift(a,0,n-i-1); //重建堆
}
}
五、归并排序
归并排序的主要思想:将若干有序子序列逐步归并,最终归并为一个有序序列
5.1 二路归并排序的实现
二路归并排序基本思想:将若干个有序序列进行两两归并,直到所有待排序记录都在一个有序序列为止
一次归并算法
void Merge(int a[],int b[],int s,int m,int t){
//将两个相邻的有序序列归并成一个有序序列
int i = s; //变量s表示指向第一个序列的第一个元素记录 m指向第一个序列的最后一个元素记录
int j = m+1; //变量m+1指向第二个序列的第一个记录 t指向第二个序列的最后一个元素记录
int k = s; //变量k指向存放归并结果的位置
while(i<=m && j<=t){
if(a[i] < a[j]) //取待归并数组中较小的数放入新的数组中
b[k++] = a[i++];
else
b[k++] = a[j++];
}
if(i<=m){
//若第一个子序列未处理完,进行收尾处理
while(i<=m){
b[k++] = a[i++];
}
}else if(j<=t){
//若第一个子序列未处理完,进行收尾处理
while(j<=t)
b[k++] = a[j++];
}
}
一趟归并排序算法
void MergePass(int a[],int b[],int n,int h){
//进行一趟归并的算法
int i = 0;
while(i<=n-2*h){
//如果从下标 i 开始的后面的数组的存储空间可以存放两个宽度为 h 的子序列
Merge(a,b,i,i+h-1,i+2*h-1);
i += 2*h;
}
if(i<n-h) //如果带归并序列中有一个长度小于h
Merge(a,b,i,i+h-1,n-1);
else{
//如果带归并序列中只剩一个子序列
for(int k=i;k<n;k++)
b[k] = a[k];
}
}
归并排序非递归算法
void MergeSort1(int a[],int b[],int n){
//归并排序非递归算法
int h = 1; //初始化时子序列长度为 1
while(h<=n){
MergePass(a,b,n,h); //将待排序数列从数组 a 传到 数组 b 中
h = 2*h;
MergePass(b,a,n,h); //将待排序数列从数组 b 传到 数组 a 中
h = 2*h;
}
}
*归并排序递归算法
void MergeSort2(int a[],int b[],int s,int t){
//归并排序递归算法
if(s == t) //如果待排序序列中只有1个记录,递归结束
b[s] = a[s];
else{
int m = (s+t)/2;
MergeSort2(a,b,s,m); //归并排序前半个子序列
MergeSort2(a,b,m+1,t); //归并排序后半个子序列
Merge(b,a,s,m,t); //将两个已排序的子序列归并
}
}
六、分配排序
分配排序是基于分配和收集的排序算法,其基本思想是:先将待排序记录序列分配到不同的桶里,然后再把各桶中的记录依次收集到一起
6.1 桶式排序
基本思想:假设待排序记录都在0~m-1之间,设置m个桶,分别存储从0到m-1的值,首先将值为i的记录分配到第i个桶中,然后将各个桶中的记录依次按顺序收集起来
6.2 基数排序
基本思想:将关键码看成由若干个子关键码复合而成,然后借助分配和收集操作采用LSD方法进行排序
七、各种排序方法的比较
7.1 时间复杂度
7.2 空间复杂度
7.3 稳定性
稳定的:直接插入、起泡、归并、基数排序
不稳定的:希尔、快速、简单选择、堆排序
7.4 算法复杂性
简单算法:直接、选择、起泡、桶式排序
复杂算法:希尔、快速、归并、基数排序
7.5 待排序的记录个数的n的大小
7.6 记录本身信息量的大小
7.7 关键码的分布情况