问题:
对于给定的含有n个元素的数组a,对其按元素值递增排序。
- 快速排序
1.基本思想:
划分:在待排序的n个元素中任取一个元素(通常取第一个元素)作为基准,把该元素放入最终元素后,整个数据
序列被基准分割成两个子序列,所有小于基准的元素放在前子序列中,所有大于基准的元素放置在后子序列中,并
把基准排在这两个子序列中间。
2.分治策略:
(1)分解:
将原序列a[s…t]分解成两个子序列a[s … i-1]和a[i+1 … t],其中i为划分的基准位置,即将整个问题划分为两个自问题
(2)求解子问题
若子序列长度为0或1,则它是有序的,直接返回;否则,递归求解各个子问题
(3)合并
由于整个序列存放在有序数组a中,合并不需要其他操作
例:对(2,5,1,7,10,6,9,4,3,8)序列,进行快排
3.代码
#include<stdio.h>
void disp(int a[],int n){
int i;
for(int i=0;i<n;i++)
printf("%d ",a[i]);
printf("\n");
}
int Partition(int a[],int s,int t){
int i=s,j=t; //i:左指针 j:右指针
int tmp=a[s]; //序列的第一个记录作为基准
while(i!=j){
//从序列两端交替向中间扫描,直到i=j为止
while(j>i&&a[j]>=tmp)
j--; //从右向左扫描,找到第一个关键字小于tmp的a[j]
a[i]=a[j]; //将a[i]前移到a[i]的位置
while(i<j&&a[i]<tmp)
i++; //从左向右扫描,找到第一个关键字大一tmp的a[i]
a[j]=a[i]; //将a[i]后移到a[j]的位置
}
a[i]=tmp;
return i;
}
void QuickSort(int a[],int s,int t){
if(s<t){
int i=Partition(a,s,t);
QuickSort(a,s,i-1); //对左子序列递归排序
QuickSort(a,i+1,t); //对右子序列递归排序
}
}
int main(){
int n=10;
int a[]={
19,3,23,4,12,67,34,89,45,5};
printf("排序前:");
disp(a,n);
QuickSort(a,0,n-1);
printf("排序后:");
disp(a,n);
}
- 归并排序
基本思想:
时间复杂度——O(nlog2 n)
首先,将a[0 … n-1]看成n个长度为1的有序表,将相邻的k(k>=2)个有序表成对归并,得到n/k个长度为k的有
序子表;然后再将这些有序子表继续归并,得到n/k^2 个长度为k^2的有序子表,反复进行次操作。最后得到一
个长度为n的有序表。
k=2,称为二路归并
k>2,称为多路归并
- 自底向上的二路归并排序
1.基本原理:
第一趟归并排序时,将待排序的表a[0 … n-1]看作是n个长度为1的有序子表,将这些子表两两归并
若n为偶数,则得到n/2个长度为2的有序子表;
若n为奇数,则最后一个子表轮空(不参与归并),得到前(n/2)-1个有序子表的长度为2,最后一个子表长度仍为1;第二趟归并将第一趟归并所得的n/2的有序子表两两归并,重复此操作,直到得到一个长度为n的有序表为止。
2.分治策略
循环log2 n次,length依次取1,2,… log2 n,每次执行以下步骤
(1)分解:将原序列分解成length个长度的若干个子序列
(2)求解子问题:对相邻的两个子序列调用Merge算法合并成一个有序子序列。
(3)合并:由于整个序列存放在数组a中,排序过程就是就地进行,合并步骤不需要任何操作。
3.代码:
#include<stdio.h>
#include<malloc.h>
void disp(int a[],int n){
int i;
for(i=0;i<n;i++)
printf("%d ",a[i]);
printf("\n");
}
void Merge(int a[],int low,int mid,int high){
//将a[low ... mid-1]和a[mid+1 ... high]合并为a[low ... high]
int *tmp; //临时表tmp
int i=low,j=mid+1,k=0; //k:tmp下标,i,j两个子表的下标
tmp=(int *)malloc((high-low+1)*sizeof(int));
while(i<mid&&j<=high) //两个子表均未扫描完时循环
if(a[i]<=a[j])
{
tmp[k]=a[i]; //将第一个子表中的元素放入tmp表中
i++;
k++;
}
else{
tmp[k]=a[j]; //将第二个子表中的元素放入tmp表中
j++;
k++;
}
while(i<=mid){
//将第一个子表中的剩余元素复制到tmp表中
tmp[k]=a[i];
i++;
k++;
}
while(j<=high){
//将第二个子表中的剩余元素复制到tmp表中
tmp[k]=a[j];
j++;
k++;
}
for(k=0,i=low;i<=high;k++,i++) //将tmp复制回a中
a[i]=tmp[k];
free(tmp); //临时释放空间
}
void MergePass(int a[],int length,int n) //一趟二路归并排序
{
int i;
for(i=0;i+2*length-1<n;i=i+2*length) //归并length长的两个相邻子表
Merge(a,i,i+length-1,i+2*length-1);
if(i+length-1<n) //剩余两个子表,后者的长度小于length
Merge(a,i,i+length-1,n-1); //归并两个子表
}
void MergeSort(int a[],int n) //二路归并算法
{
int length;
for(length=1;length<n;length=length*2)
MergePass(a,length,n);
}
int main(){
int n=10;
int a[]={
2,5,1,7,10,6,9,4,3,8};
printf("排序前:");
disp(a,n);
MergeSort(a,n);
printf("排序后:");
disp(a,n);
}
- 自顶向下的二路归并排序
1.分治算法
算法简洁
设归并排序当前区间是a[low … high],递归归并步骤:
(1)分解:将当前序列a[low … high]一分为二,即求mid=(low+high)/2,分解为两个子序列a[low … mid]和a[mid+1 … high]
(2)子问题求解:递归地对两个子序列a[low … mid]和a[mid+1 … high]二路归并排序,其终结条件是子序列的长度为1或0(因为一个元素的子表或者空表可以看成有序表)
(3)合并:与分解过程相反,将已排序的两个子序列a[low … mid]和a[mid+1 … high]归并为一个有序序列a[low … high]
2.代码
#include<stdio.h>
#include<malloc.h>
void disp(int a[],int n){
int i;
for(i=0;i<n;i++)
printf("%d ",a[i]);
printf("\n");
}
void Merge(int a[],int low,int mid,int high){
//将a[low ... mid-1]和a[mid+1 ... high]合并为a[low ... high]
int *tmp; //临时表tmp
int i=low,j=mid+1,k=0; //k:tmp下标,i,j两个子表的下标
tmp=(int *)malloc((high-low+1)*sizeof(int));
while(i<mid&&j<=high) //两个子表均未扫描完时循环
if(a[i]<=a[j])
{
tmp[k]=a[i]; //将第一个子表中的元素放入tmp表中
i++;
k++;
}
else{
tmp[k]=a[j]; //将第二个子表中的元素放入tmp表中
j++;
k++;
}
while(i<=mid){
//将第一个子表中的剩余元素复制到tmp表中
tmp[k]=a[i];
i++;
k++;
}
while(j<=high){
//将第二个子表中的剩余元素复制到tmp表中
tmp[k]=a[j];
j++;
k++;
}
for(k=0,i=low;i<=high;k++,i++) //将tmp复制回a中
a[i]=tmp[k];
free(tmp); //临时释放空间
}
void MergePass(int a[],int length,int n) //一趟二路归并排序
{
int i;
for(i=0;i+2*length-1<n;i=i+2*length) //归并length长的两个相邻子表
Merge(a,i,i+length-1,i+2*length-1);
if(i+length-1<n) //剩余两个子表,后者的长度小于length
Merge(a,i,i+length-1,n-1); //归并两个子表
}
void MergeSort(int a[],int low,int high){
//二路归并算法
int mid;
if(low<high){
//子序列有两个以上的元素
mid=(low+high)/2; //取中间位置
MergeSort(a,low,mid); //分别对两个子序列排序
MergeSort(a,mid+1,high);
Merge(a,low,mid,high); //合并
}
}
int main(){
int n=10;
int a[]={
2,5,1,7,10,6,9,4,3,8};
printf("排序前:");
disp(a,n);
MergeSort(a,0,n);
printf("排序后:");
disp(a,n);
}