浅析几种排序

排序算法有很多,这里简单介绍一下,冒泡排序,简单选择排序,插入排序,快速排序,和归并排序.

在程序的编写中,可以直接用c++ 中#include<algorithm> 中的sort() 函数这个最简单sort()函数默认情况下是从小到大排序如对 int b[10]={99,55,2,1,6,33,4,5,1,0};进行排序。

sort(b,b+10); 要使其从大到小排序只需要外加一个谓词函数就可以了 bool compare(const int &a,const int &b){return a>b;}   sort(b,b+10,compare);

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
bool compare(const int &a,const int &b)//谓词函数 使sort()从大到小排序
{
     return a>b;
}
int main()
{
	int b[10]={99,55,2,1,6,33,4,5,1,0};
	sort(b,b+10);
	cout<<"*************************sort 默认情况下从小到大排序*************************"<<endl;
	for(int i=0;i<10;i++)
	{
	    printf("%d ",b[i]);
		if(i==9)
		{
		    printf("\n");
		}
	}
	sort(b,b+10,compare);//compare 为谓词函数使sort从大到小排序。
	cout<<"*************************sort加谓词函数从大到小排序*************************"<<endl;
	for(int i=0;i<10;i++)
	{
	    printf("%d ",b[i]);
		if(i==9)
		{
		    printf("\n");
		}
	}     
	
    return 0;
}


或这用#include<functional>   greater<>     sort(a,a+10,greater<int>);

先将讲一下最经典最常用的冒泡排序。

冒泡排序其时间复杂度为O(n^2)    空间复杂度为O(1)  基本思想:两两比较相邻元素的大小,如果反序则交换位置,直到某趟排序没有反序为止。n个元素需要进行n-1趟排序,第0趟排序需要进行n-1次相邻元素的两两比较,第i 趟排序需要进行n-1-i次的相邻元素的两两比较。

void swap(int *a,int *b)//交换两个数
{
    int temp=*a;
	*a=*b;
	*b=temp;
}

void Sort_Bubble(int a[],int n)//冒泡排序
{

    bool flag=true;//设立一个标志位,保证在一某一趟排序没有逆序的时候停止排序
	for(int i=0;i<n-1&&flag;i++)
	{
	    flag=false;
		for(int j=n-2;j>=i;j--)//此种冒泡排序其实是最正宗的冒泡排序,从最小的数开始一个一个从底下往上冒,像冒泡一样,轻的泡泡一个一个往上冒
		{
		    if(a[j]>a[j+1])
			{
			
			     flag=true;
				 int temp=a[j];
				 a[j]=a[j+1];
				 a[j+1]=temp;
			}
		}
	}
}
/*通过交换使相邻的两个数变成小数在前大数在后,这样每次遍历后,最大的数就“沉”到最后面了。重复N-1次即可以使数组有序*/
void Bubble_Sort(int a[],int n)//冒泡排序,
{
     bool flag=true;
	 for(int i=0;i<n-1&&flag;i++)
	 {
	     flag=false;
		 for(int j=0;j<n-1-i;j++)//此种排序第一趟排序将最大的数沉到最底下。
		 {
		     if(a[j]>a[j+1])
			 {
				  flag=true;
			      swap(a[j],a[j+1]);
			 }
		 }
	 }
}
简单选择排序是一种 不稳定的排序方法,其时间复杂度为O(n^2) 空间复杂度为O(1) 基本思想: 将待排序的数组分成两部分,有序区和无序区,初始时整个数组都是无序区,然后每次从 无序区中选取一个最小的元素放到有序区的最后,直到数组变成有序区。n个元素需要进行n-1趟选择排序,第一趟选择排序需要对第一个元素之后的n-1个元素进行n-1选择比较。

void swap(int &a,int &b)
{
   int temp;
   temp=a;
   a=b;
   b=temp;
}
void Select_Sort(int a[],int n)//简单选择排序
{
    for(int i=0;i<n-1;i++)//n个元素需要进行n-1趟选择排序
	{
	   int min_index=i;
	   for(int j=i+1;j<n;j++)//向后在无序区中寻找最小的元素
	   {
	       if(a[j]<a[min_index])
		   {
		      min_index=j;
		   }
	   }
	   if(i!=min_index)
	   {
	      swap(a[i],a[min_index]);
	   }
	}
	
}
直接插入排序是一种稳定的排序方法,其时间复杂度为O(n^2)   其基本思想:将待排序数组分成两部分,已排好序部分,和未排序部分,开始时,第一个元素在已排序好序部分中,其余元素在未排序部分,然后依次从未排序部分中取出第一个元素,从后向前与排好序部分的元素进行比较,并将其插入到已排好序部分的正确位置,直到所有元素排好序。

插入排序和打扑克牌时,抓牌整理牌很相似。再抓第一张牌时,不需要进行任何整理牌的动作只需抓在手上即可,抓第二张牌时需要和第一张牌比大小。大的话直接放在第一张牌后面,小的话需插到第一张牌前面。后面抓牌时依次与前面排好序的牌比较找到合适的位置插入即可。出第一个元素,从后向

void insert_sort(int a[],int len)//插入排序

{

    /*n个元素需要进行n-1趟插入排序 第一个元素a[0]相当于已经排好序的一个元素(抓第一张牌时是不需要做插入操作的),插入操作从第二个元素a[1]开始*/
   for(int i=1 ;i<len;i++)
	{
		if(a[i]<a[i-1])//a[i]相当与新抓的一张牌需要和前一张排好序的最大的牌a[i-1]比较大小  如果新抓的牌a[i]比排好序中最大的牌a[i-1]还大,则不用进行插入操作,直接抓下一张牌
		{
		    int temp=a[i];//相当与挖了个坑,腾出一个位置可以让a[i-1]移到这个位置
			int j;
			for( j=i-1;j>=0&&a[j]>temp;j--)//从有序区的最后一个元素a[i-1]开始比较从后往前找合适的插入位置,找出第一个比temp小的位置时结束for循环
			{
			     a[j+1]=a[j];
			}  
			a[j+1]=temp;//找到了合适的位置将新抓的牌temp 插入进去完成一趟插入排序.其实插入排序有点像快速排序中挖坑填数
		}
		

	}
}

void InsertSort(int a[],int n)
{
	for(int i=1;i<n;i++)//第一个元素a[0]默认归到已排好序部分
	{
		int insertValue=a[i];//待插入的元素
		int index=i-1;//insertValue准备和前一个数比较
		while(index>=0&&insertValue<a[index])//当向前找到比InsertValue小的数时停止
		{
			a[index+1]=a[index];
			index--;
		}
		a[index+1]=insertValue;//将insertValue 插入到合适的位置
	}
}


快速排序是一种不稳定的排序方法,其平均时间复杂为O(nlogn),空间复杂度为O(logn)快速排序被称为排序之王,目前被认为是内部排序方法中
最好的方法。

快速排序的基本思想:从待排序的序列中选择一个基准数,将待排序序列分割成两部分,一部分比枢纽元素小,另一部分比枢纽元素大,其中比基准数大的放基准数右边,比基准数小的放基准数左边,再对分割后的两部分,重复之前的选取基准数分割数组的步骤直到每部分只有一个元素为止;


快速排序,总体是基于分治法的。分治法的基本思想:将一个规模较大的问题,分解成一些规模较小的子问题,这些子
问题相互独立且与原问题性质相同,先求出子问题的解从而得到原问题的解。分治法的在每层递归上的3个步骤: 1分解: 将原问题分解成一系列子问题 2求解:递归的求解各个子问题若子问题足够小,则直接求解。
3合并:将子问题的解合并成原问题的解。  
准确的说,快速排序可以用分治法+挖坑填数来描述
对挖坑填数的描述  
1,i =L; j = R; 将基准数(枢纽元素)挖出形成第一个坑a[i]  //  基准数默认选第一个数,与基准数比较先从后往前比
2,j--由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中,同时形成新的坑a[j]  
3,i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中,又形成新的坑a[i]。  

4,再重复执行2,3二步,直到i==j,将基准数填入a[i]中  

//编程记忆:基准数默认选第一个数,设置两个游标i,j 和基准数比较,先从游标j开始从后往前与基准数比较,再从游标i从前往后和基准数比较,当i==j时完成一次分割数组的步骤

#include<iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
void swap(int &a,int &b)
{
    int temp=a;
	a=b;
	b=temp;
}
//Partition函数即实现了从待排序的序列中选择一个枢纽元素,将待排序序列分割成两部分,一部分比枢纽元素小,另一部分比枢纽元素大	
//Partition函数的实现的思路是就就是挖坑填数
int Partition(int a[],int start,int end)
{
   int i=start,j=end;
  // int mid=(i+j)/2;
//swap(a[i],a[mid]);//将中间这个数与第一个数交换使得枢纽元素的的选择更加合理
   int key=a[i];//将基准数挖出形成第一个坑。
   while(i<j)
   {
       while(i<j&&a[j]>key)//从后向前找第一个小于或等于基准数key的数  
	   {
	       j--;
	   }
	   if(i<j)
	   {
	     a[i]=a[j];//填掉之前的坑形成了新的坑a[j]
		 i++;
	   }
	   while(i<j&&a[i]<key)//从前向后找第一个大于基准数key的数 
	   {
	      i++;
	   }
	   if(i<j)
	   {
	     a[j]=a[i];//填掉之前的坑又形成新的坑a[i]
		 j--;
	   }
   }
   a[i]=key;// 将枢纽元素放到正确的位置
   return i;//返回枢纽元素的位置
}
void quick_sort(int *numbers,int start,int end)//快速排序总体也是基于分治法的。
{
	
	if(start<end)
	{
	    int index=Partition(numbers,start,end);//从待排序的序列中选择一个枢纽元素,将待排序序列分割成两部分,一部分比枢纽元素小,另一部分比枢纽元素大
		quick_sort(numbers,start,index-1);//  对分割后的两部分,重复之前的步骤直到每部分只有一个元素为止
		quick_sort(numbers,index+1,end);
	}
    
}
void quick_sort(int *numbers,int length)
{
    if(numbers==NULL||length<=0)
	{
	   return;
	}
	int start=0;
	int end=length-1;
	quick_sort(numbers,start,end);
}  
void quick_sort2(int s[],int L,int R)
{
      //swap(s[L],s[(L+R)/2]);//将中间这个数与第一个数交换使得枢纽元素的的选择更加合理
	if(L<R)
	{
		int i=L,j=R,key;
		key=s[L];//将基准数挖出形成第一个坑。
		while(i<j)
		{
		   while(i<j&&s[j]>key)//从右向左找第一个小于或等于基准数key的数  
		   {
			   j--;
		   }
	       if(i<j)
		   {
		       s[i]=s[j];//填掉之前的坑形成了新的坑a[j]
			   i++;
		   }
		   while(i<j&&s[i]<=key)//从左向右找第一个大于基准数key的数  
		   {
		       i++;
		   }
		   if(i<j)
		   {
		       s[j]=s[i];//填掉之前的坑形成新的坑a[i]
			   j--;
		   }
		}
		s[i]=key;//将枢纽元素放到正确的位置
		quick_sort2(s,L,i-1);//递归调用
		quick_sort2(s,i+1,R);
	}
}
int main()
{
	int a[]={2,3,4,0,9,8,6,7,121,12};
	quick_sort(a,10);
	for(int i=0;i<10;i++)
	{
	  cout<<a[i]<<"  ";
	}
   return 0;
}


 

归并排序: 和快速排序一样,归并排序总体也是基于分治法的。归并排序是一种稳定的排序方法,其时间复杂度为O(nlogn) 空间复杂度为O(n) 归并排序需要O(n)的辅助空间

所谓归并是指将两个或两个以上的有序序列归并为一个新的有序序列      归并排序的基本思想:将含有n个元素的序列,看成是由n个长度为1的有序子序列组成,然后进行两两归并,得到【n/2】(向上取整) 个长度为2或1的有序序列,再两两归并,如此重复直到所有元素有序为止。

两路归并排序的核心操作时将一维数组中前后相邻的两个有序序列归并为一个有序序列

归并排序算法完全按照分治法的三个步骤进行。

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

求解: 用归并排序对两个子序列递归的排序。 

合并:合并两个已经排好序的子序列以得到排序结果

// 此函数的写法和快速排序很像是 基于分治法实现的。
void merge_sort(int a[],int start,int end,int temp[])//归并排序需要O(n) 的辅助空间
{
     if(start<end)//当只有一个元素时,这个元素相当与有序
	 {
	     int mid=(start+end)/2;//分解: 将n个元素分成各含n/2个元素的子序列
		 merge_sort(a,start,mid,temp);//求解: 用归并排序对两个子序列递归的排序
		 merge_sort(a,mid+1,end,temp);// 求解:用归并排序对两个子序列递归的排序
		 merge_array(a,start,mid,end,temp);//合并: 合并两个已经排好序的子序列以得到排序结果
	 }
}
/*将有序的a[first...mid]和a[mid+1...last] 归并为一个有序的a[first...last]  分治法的合并会用到这个函数*/
void merge_array(int a[],int first,int mid,int last,int temp[])//temp[]为辅助空间这个函数的时间复杂度为O(n)
{
       int i=first,j=mid+1,k=0;
	   while(i<=mid&&j<=last)
	   {
	       if(a[i]<a[j])
		   {
		       temp[k++]=a[i++];
		   }
		   else
		   {
		       temp[k++]=a[j++];
		   }
	   }
	   while(i<=mid)
	   {
	        temp[k++]=a[i++];
	   }
	   while(j<=last)
	   {
	        temp[k++]=a[j++];
	   }
	   for(i=0;i<k;i++)//将排好序的temp里的元素复制给原数组
	   {
	      a[first+i]=temp[i];
	   }
}

bool MergeSort(int a[],int n)//提供一个简单的接口函数
{
    int *temp=new int[n]();
	if(temp==NULL)
	{
	   return false;
	}
	merge_sort(a,0,n-1,temp);
	delete[] temp;
	return true;
}
int main()
{
	int a[]={2,3,4,0,9,8,6,7,121,12};
	//quick_sort(a,10);
	MergeSort(a,10);
	for(int i=0;i<10;i++)
	{
	  cout<<a[i]<<"  ";
	}
   return 0;
}









  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值