`
tangyuan1314
  • 浏览: 39665 次
  • 性别: Icon_minigender_2
  • 来自: 南京
社区版块
存档分类
最新评论

插入排序、选择排序、合并排序几种排序比较

阅读更多

1、 插入排序,时间复杂度O(n^2)

一个数组A[0,...,n-1],将A[j]插入到子数组A[0,...,j-1]中,其中数组A[0,...,j-1]是排好序的(假如为非降序),把A[j]和A[j-1]->A[0]逐个进行比较,大的数往后移位,将A[j]插入适当的位置(找到最后一个比它大的,放在前面)。

递归法,为排序A[0,...,n-1],先排序A[0,...,n-2],然后把A[n-1]插入到已经排序的数组A[0,...,n-2]中去。如下:

 

#include<stdio.h>
#include<iostream.h>

//算法导论2.3-4
//将数number插入已经排好序(非降序)的数组中
void insert(int num[],int len,int number){
	int i=len-1;
	while(i>=0&&num[i]>number){
	   num[i+1]=num[i];
	   i--;
	}
	num[i+1]=number;
}
//递归程序
void insert_into(int num[],int n){

	if(n>1){
	   insert_into(num,n-1);
	} 
	   insert(num,n,num[n]);
    

	
}

int main(){
	int num1[8]={4,2,5,1,8,10,3,7};
	
	int len=8;
	insert_into(num1,len-1);
	for(int k=0;k<len;k++){
	   cout<<num1[k]<<endl;
	}
	return 0;
}

 

 2、选择排序,时间复杂度O(n^2)

首先找出A中最小的元素,并将其与A[0]互换,接着找A中次小元素,与A[1]互换,以此类推。并且仅需要在n-1个元素上运行。伪代码如下:

      for i<-1 to n-1 do//伪代码中下标从1开始

            min<-Find_min(A[i,...,n])//此函数找出A中从下标i到n的最小元素,时间复杂度O(n)

            min<->A[i]

      endfor

根据以上伪代码可知,选择排序的时间复杂度为O(n^2)

最佳和最坏情况下的运行时间都为Θ(n²)。因为这种直接选择排序,其关键字的比较次数与各元素原来的排列顺序无关。

3、合并排序(递归法),时间复杂度O(n2n)

分解:将n个元素分成各含n/2个元素的子序列

解决:用合并排序法对两个子序列进行递归排序

合并:合并两个已经排好序的子序列得到结果(merge函数)

 

#include<stdio.h>
#include<iostream.h>
#include <limits.h>

//算法导论p17

void merge(int num[],int p,int q,int r){//假设num[p..q],num[q+1...r]已经排好序,将其合并成一个已经排好序的数组
   int n1=q-p+1;
   int n2=r-q;
   int *left=new int[n1+1];
   int *right=new int[n2+1];

   for(int i=0;i<n1;i++){
      left[i]=num[p+i];

   }
   for(i=0;i<n2;i++){
      right[i]=num[q+1+i];

   }
   left[n1]=INT_MAX;//哨兵牌
   right[n2]=INT_MAX;
   int j=0,k=0;

   for(i=p;i<=r;i++){
	   if(left[j]>right[k]){
	      num[i]=right[k];
		  k++;
	   }
       else{
	      num[i]=left[j];
		  j++;
	   }
   }
   for(i=p;i<=r;i++){
      cout<<num[i]<<endl;
   }

}
void merge_sort(int num[],int p,int r){
	int q=0;
	if(p<r){
	   q=(p+r)/2;
       merge_sort(num,p,q);
       merge_sort(num,q+1,r);
       merge(num,p,q,r);

	}

}

int main(){
	int num[8]={2,4,5,7,1,2,3,6};	
    int p=0,r=7;
	int q=(p+r)/2;
	merge_sort(num,p,r);
    return 0;
}
 

 

merge函数是从头开始分别比较两个子序列的数的大小,将较小的数字存入num中,相应队列的指针往后移。为了避免在每个步骤中都要检查每个序列是否为空,增加一个哨兵牌,包含一个特殊的值,来简化代码。此函数中,用正无穷来作为哨兵值,这样每当露出一张值为无穷的哨兵牌,其不可能是两张中最小的一张,除非另外一个序列也露出哨兵牌。

merge函数的时间复杂度为O(n),n=r-p+1

而合并排序,将问题分为1/2->1/4->1/8,总共分了2n层,回推时每一层的代价是O(n)

4、合并排序的应用,求逆序对的个数,且算法的时间复杂度为O(n2n)

逆序对:如果有i<j时,A[i]>A[j],则(i,j)叫做一个逆序对

合并排序法求逆序对的个数,当求出逆序对数目后,数组也已经排好序了,如果不想让原数组发生变化,可以把原数组拷贝到另一个数组,对新数组操作就好。整个过程一边排序一边数,递归到最深的地方如果有一个数,那么返回0,如果有两个数,看做有两个元素的子数组,两个子数组都已经排好序,并且左边只有一个元素的子数组的逆序对是0,右边只有一个元素的子数组的逆序对是0,那么两个元素的子数组的逆序对的个数就是1,若这两个数逆序,否则是0对。所以,当递归进行到最后一步,也就是这样的:a[1]a[n/2]已经排好序,并且逆序对的个数为Inv1a[n/2+1]a[n]已经排好序,并且逆序对的个数为Inv2,那么整个数组的逆序对总数就是Inv1+Inv2+最后一步归并过程中的逆序对数。那么在merge这一步的逆序对个数又是怎么计算的呢?若i是左边子数组的下标(i的范围是pq),j是右边子数组的下标(j的范围是q+1r),假设某对(i, j),有a[i]>a[j],那么a[i]a[q]都大于a[j],贡献的逆序对数为q-i+1

 

#include<stdio.h>
#include<iostream.h>
#include <limits.h>

//算法导论p24 2-4d

int merge_num(int num[],int p,int q,int r){//假设num[p..q],num[q+1...r]已经排好序,将其合并成一个已经排好序的数组
   int n1=q-p+1;
   int n2=r-q;
   int *left=new int[n1+1];
   int *right=new int[n2+1];
   int n=0;//n表示逆序对数

   for(int i=0;i<n1;i++){
      left[i]=num[p+i];

   }
   for(i=0;i<n2;i++){
      right[i]=num[q+1+i];

   }
   left[n1]=INT_MAX;//32767
   right[n2]=INT_MAX;
   int j=0,k=0;

   for(i=p;i<=r;i++){
	   if(left[j]>right[k]){
	      num[i]=right[k];
		  k++;
		  n+=n1-j;//注意不可是q-j+1,因为q和j指的意义不同,一个是原来数组里的下标,一个是新数组里的下标
	   }
       else{
	      num[i]=left[j];
		  j++;
	   }
   }
  
   return n;
}

int merge_sort(int num[],int p,int r){
	int q=0;
	int total=0;
	if(p<r){
	   q=(p+r)/2;
	   total+=merge_sort(num,p,q);//之前的左半部
	   total+=merge_sort(num,q+1,r);//之前的右半部分
	   total+=merge_num(num,p,q,r);//这次合并时加上total

	}
	
	return total;
}

int main(){
	int num[8]={8,7,6,5,4,3,2,1};	
    int p=0,r=7;
	int q=(p+r)/2;
	//cout<<merge_num(num,p,q,r)<<endl;
    cout<<merge_sort(num,p,r)<<endl;
	for(int i=p;i<=r;i++){
      cout<<num[i]<<endl;
   }
    return 0;
}
 

此时,因为要记录每次递归的逆序对数,因此,递归函数merge_sort中total的写法需要注意。

5、折半查找(递归程序),时间复杂度O(2n)

 

int binary_search(int num,int num_arr[],int start,int end){
	int mid=0;
    if(start>=end&&num_arr[start]!=num){//注意递归终止条件
		return 0;
	   
	}
	mid=(start+end)/2;
	   if(num==num_arr[mid]){
		   return mid;
	      
	   }
	   else if(num<num_arr[mid]){
	      binary_search(num,num_arr,start,mid);
	   }
	   else{
	      binary_search(num,num_arr,mid,end);
	   }
}
 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics