常见排序算法

 

从大的方面来说,排序可以分成内排序和外排序——内排序是外排序的基础。我们常用的内排序又可以粗略分成下面的类型:

    1.经典算法:如冒泡排序;

    2.插入排序及希尔排序;

    3.选择交换排序;

    4.堆排序;

    5.归并排序;

    6.快速排序。

别看排序有那么多种类型,但它们都离不开这样的核心思想:

|有序序列区|无序序列区|

一个待排序列总是被不断从无序序列转变为有序序列。

 

 

一。经典算法:冒泡排序

基本思想  

每一趟找出当前无序子数组最大的值,并放于有序子数组的前一位置,共需要n-1趟。

冒泡排序(BubbleSort)的基本概念是:依次比较相邻的两个数,将小数放在前面,大数放在后面。即在第一趟:首先比较第1个和第2个数,将小数放前,大数放后。然后比较第2个数和第3个数,将小数放前,大数放后,如此继续,直至比较最后两个数,将小数放前,大数放后。至此第一趟结束,将最大的数放到了最后。在第二趟:仍从第一对数开始比较(因为可能由于第2个数和第3个数的交换,使得第1个数不再小于第2个数),将小数放前,大数放后,一直比较到倒数第二个数(倒数第一的位置上已经是最大的),第二趟结束,在倒数第二的位置上得到一个新的最大数(其实在整个数列中是第二大的数)。如此下去,重复以上过程,直至最终完成排序。

由于在排序过程中总是小数往前放,大数往后放,相当于气泡往上升,所以称作冒泡排序。

二重循环实现,外循环变量设为i,内循环变量设为j。外循环重复9次,内循环依次重复9,8,...,1次。每次进行比较的两个元素都是与内循环j有关的,它们可以分别用a[j]和a[j+1]标识,i的值依次为1,2,...,9,对于每一个i, j的值依次为1,2,...10-i。

一个实现:

//bubble sorting
template<typename T>
void BubbleSort(T arr[],int n)
{
	T tmp;
	for(int i=0;i<n-1;++i)
	{
		for(int j=0;j<n-1-i;++j)
		{
			if(arr[j]>arr[j+1])
			{
				tmp=arr[j];
				arr[j]=arr[j+1];
				arr[j+1]=tmp;
			}
		}
	}
}

 

二。插入排序及希尔排序:

基本思想

每一趟处理一个元素,将该元素放于该元素之前的子数组(有序)中的正确位置,共需n-1趟。

插入排序算法的一个实现:

//insertion sorting
template<typename T>
void InsertSort(T arr[],int n)
{
	T tmp;
	int i,j;
	for(i=1;i<n;++i)
	{ 
		tmp=arr[i];
		for(j=i;j>0;--j)
		{
			if(arr[j-1]>tmp)
			{
				arr[j]=arr[j-1];
			}
			else
			{
				break;
			}
		}
		arr[j]=tmp;
	}
}

 

另外,插入算法也可以使用递归方法实现:

//insertion sorting:recursive edition
template<typename T>
void InsertSortRecur(T arr[],int n)
{
	if(n>1)
	{
		InsertSortRecur(arr,n-1);
		int key=arr[n-1];
		int i;
		for(i=n-1;i>0;i--)
		{
			if(arr[i-1]>key)
			{
				arr[i]=arr[i-1];
			}
			else
			{
				break;
			}
		}
		arr[i]=key;
	}
}


 


希尔排序的一个实现:

//shell sorting
template<typename T>
void ShellSort(T arr[],int n)
{
	int i,j;
	T tmp;

	for(int inc=n/2;inc>0;inc/=2)
	{
		for(i=inc;i<n;++i)
		{
			tmp=arr[i];
			for(j=i;j>=inc;j-=inc)
			{
				if(arr[j-inc]>tmp)
				{
					arr[j]=arr[j-inc];
				}
				else
				{
					break;
				}
			}
			arr[j]=tmp;
		}
	}
}


 

三。选择交换排序:

基本思想

每一个趟选择当前无序子数组中的最小值,并与当前有序子数组的后一位交换位置,共需n-1趟。

①初始状态:无序区为R[1..n],有序区为空。

②第1趟排序

  在无序区R[1..n]中选出关键字最小的记录R[k],将它与无序区的第1个记录R[1]交换,使R[1..1]和R[2..n]分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。

……

③第i趟排序

  第i趟排序开始时,当前有序区和无序区分别为R[1..i-1]和R(1≤i≤n-1)。该趟排序从当前无序区中选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。

  这样,n个记录的文件的直接选择排序可经过n-1趟直接选择排序得到有序结果。

  常见的选择排序细分为简单选择排序、树形选择排序(锦标赛排序)、堆排序。上述算法仅是简单选择排序的步骤。

 

//selection sorting
template<typename T>
void SelectSort(T arr[],int n)
{
	T minv;
	int index;
	for(int i=0;i<n-1;++i)
	{
		minv=arr[i];
		index=i;
		for(int j=i+1;j<n;++j)
		{
			if(arr[j]<minv)
			{
				index=j;
				minv=arr[j];
			}
		}
		if(index!=i)
		{
			arr[index]=arr[i];
			arr[i]=minv;
		}
	}
}


 

四。堆排序:

基本思想

主要是利用max堆即优先队列来实现排序。

第一步:以线性时间建立一个max堆;

第二步:执行n-1次deletemax操作来实现排序:将堆的最后元素与第一个元素交换(将无序子数组的最大值放于有序子数组的前一位置),并将堆的大小减少1后进行下滤。

算法终止时,数组以升序的顺序包含这些元素。

 

堆排序的一个实现:

//for heap sorting: the index for heap begins from 0, and therefore, the index of the leftnode and right node for node i is 2i+1
//and 2i+2, respectively.
int LeftChild(const int i)
{
	return 2*i+1;
}

//for heap sorting:swap two variables
template<typename T>
void Swap(T* l,T* r)
{
	T tmp=*l;
	*l=*r;
	*r=tmp;
}

//for heap sorting:percolate down(下滤)
//对节点i,将其放在其所有子节点中的正确位置,并假设其子节点已经是有序的。
template<typename T>
void PercDown(T arr[],int i,int n)
{
	T tmp=arr[i];
	int j,nc;
	for(j=i;LeftChild(j)<n;j=nc)
	{
		nc=LeftChild(j);
		if(nc<n-1&&arr[nc+1]>arr[nc])
		{
			nc++;
		}
        if(arr[nc]>tmp)
		{
			arr[j]=arr[nc];
		}
		else
		{
			break;
		}
	}
	arr[j]=tmp;
}

//heap sorting
template<typename T>
void HeapSort(T arr[],int n)
{
	//build heap
	for(int i=n/2;i>=0;--i)
	{
		PercDown(arr,i,n);
	}

	//heap sorting
	for(int i=n-1;i>0;--i)
	{
		Swap(&arr[0],&arr[i]);//delete max
		PercDown(arr,0,i);//percolate down
	}
}


五。归并排序:

基本思想

归并排序是利用递归和分而治之的技术将数据序列划分成为越来越小的半子表,再对半子表排序,最后再用递归步骤将排好序的半子表合并成为越来越大的有序序列,归并排序包括两个步骤,分别为:

      1)划分子表:以数组中间位置划分,递归处理

      2)合并半子表 :合并算法

归并排序在最坏情形以O(NlogN)时间运行。

 

归并排序的一个实现:

 

//for merge sorting:merge algorithm
//first mid last are index starting from 0. The subarray arr[first,mid) and arr[mid,last] are both sorted subarrays.
template<typename T>
void Merge(T arr[],int first,int mid,int last)
{
	//设置indexA,并扫描subArray1 [first,mid)
	//设置indexB,并扫描subArray2 [mid,last]
	int inda=first,
		indb=mid,
		noe=last-first+1;//number of elements

	T *tmparr=(T*)malloc(noe*sizeof(T));
	int indtmp=0;
    
	//main loop
	while(inda<mid&&indb<=last)
	{
		if(arr[inda]<=arr[indb])
		{
			tmparr[indtmp++]=arr[inda++];
		}
		else
		{
			tmparr[indtmp++]=arr[indb++];
		}
	}

	//if some elements of the first subarray left.
	while(inda<mid)
	{
		tmparr[indtmp++]=arr[inda++];
	}
    
	//if some elements of the second subarray left.
	while(indb<=last)
	{
		tmparr[indtmp++]=arr[indb++];
	}

    //copy the sorted array back to the input memeory.
	inda=first;
	indtmp=0;
	for(int i=0;i<noe;++i)
	{
		arr[inda++]=tmparr[indtmp++];
	}

	//free temporary memeory.
	free(tmparr);
}

//merge sorting

template<typename T>
void MSort(T arr[],int first,int last)
{
	if(first<last-1)//at least two elements
	{
		int mid=(first+last)/2;
		MSort(arr,first,mid-1);
		MSort(arr,mid,last);
		Merge(arr,first,mid,last);
	}
}

template<typename T>
void MergeSort(T arr[],int n)
{
	MSort(arr,0,n-1);
}


六。快速排序:

基本思想

主要步骤:

1.选取数组S中任意一个元素,作为枢纽元(pivot);

2.将数组中所有元素分割为两部分S1, S2:S1中所有元素均小于等于枢纽元,S2中所有元素均大于等于枢纽元;

   一趟快速排序的算法是:

1)设置两个变量i, j,排序开始的时候:i=first,j=last;

2)以数组元素中某一元素作为枢纽元,即 pivot=Median3(...);

3)从j开始向前搜索,即由后开始向前搜索(j=j-1),找到第一个小于pivot的值A[j],并与A[i]交换;

4)从i开始向后搜索,即由前开始向后搜索(i=i+1),找到第一个大于pivot的A[I],与A[j]交换;

5)重复第3、4、5步,直到 i>=j.  

3.递归处理S1和S2两部分。

 

快速排序的平均运行时间为O(NlogN),特别对于大数组,速度很快。但对于小数组,反而速度不如插入排序、选择排序好。

快速排序的一个实现:

 

//for quick sorting: find the pivot, sort the first mid and last elements; save the pivot in last-1 position
template<typename T>
T Median3(T arr[],int left,int right)
{
	int mid=(left+right)/2;
	if(arr[left]>arr[mid])
	{
		Swap(&arr[left],&arr[right]);
	}
	if(arr[left]>arr[right])
	{
		Swap(&arr[left],&arr[right]);
	}
	if(arr[mid]>arr[right])
	{
		Swap(&arr[mid],&arr[right]);
	}

	//hide pivot
    Swap(&arr[mid],&arr[right-1]);
	//return pivot
	return arr[right-1];
}

//quick sorting
template<typename T>
void QSort(T arr[],int first,int last)
{
	const int cutoff=3;
    
	if(first+cutoff<last)
	{
		T pivot=Median3(arr,first,last);
        int i=first,j=last-1;//i,j初始化为比它们的正确值超出1.
        for(;;)
		{
			while(arr[++i]<pivot){}
			while(arr[--j]>pivot){}
			if(i<j)
			{
				Swap(&arr[i],&arr[j]);
			}
			else
			{
				break;
			}
		}
		//restore pivot
		Swap(&arr[i],&arr[last-1]);
		QSort(arr,first,i-1);
		QSort(arr,i+1,last);
	}
	else
	{
		InsertSort(arr+first,last-first+1);
		//or
		//SelectSort(arr+left,last-left+1);
	}
}

//quicksorting
template<typename T>
void QuickSort(T arr[],int n)
{
	QSort(arr,0,n-1);
}


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值