分治法 —— 快速排序和归并排序(自底向上和自顶向下)

问题:

对于给定的含有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);
	} 


猜你喜欢

转载自blog.csdn.net/gl620321/article/details/108204785