冒泡排序,插入排序,选择排序,希尔排序

         在另外的文章中介绍了快速,堆,归并排序,这三个排序都是高效的排序,下面介绍一些时间复杂度为O(n^2)的排序。        

       冒泡排序,顾名思义,就是将小的元素往上冒,大的元素往下沉,实际在变成的时候用的是大的元素往下沉的方法。每一趟过后,都有一个最大的元素被沉到底。下一次排序时数组规模减少了1。主要应用到了相邻两个元素互相比较的方法,是一个稳定的排序,但是无论数组是否有序,都要比较n^2次。是属于蛮力法的一个应用。代码如下:

void SortNamesSpace::bubbleSort(int *pData, size_t uiLength)
{
	if ( NULL == pData || 1 == uiLength )
	{
		return;
	}

	for ( size_t i = 0; i < uiLength - 1; ++i)
	{
		for ( size_t j = 0; j < uiLength - 1 - i; ++j )//每趟都减少一个元素
		{
			if ( pData[j] > pData[j+1])
			{
				swap(pData[j],pData[j+1]);//交换相邻元素
			}
		}
	}
}

(引用Guo_guo博客分析)其特点如下:

  (1)稳定的,即如果有 [...,5,5...]这样的序列,排完序后,这两个5的顺序一定不会改变,这在一般情况下是没有意义的,但当 5这个节点不仅仅是一个数值,是一个结构体或者类实例,而结构体有附带数据的时候,这两个节点的顺序往往是有意义的,如果能够稳定有时候是关键的,因为如果不稳定则可能破坏附带数据的顺序结构。

(2)比较次数恒定,总是比较 n²/2次,哪怕数据已经完全有序了

(3)最好的情况下,一个数据也不用移动,冒泡排序的最好情况是:【数据已经有序】

(4)最坏的情况下,移动 n²/2次数据,冒泡排序的最坏情况是:【数据是逆序的】。

        插入排序:把一个元素插入到一个已经有序的序列中。插入排序的代码页很简洁易懂,如下所示:

//插入排序
void SortNamesSpace::insertSort(int *pData, size_t uiLength)
{
	if ( NULL == pData || 1 == uiLength )
	{
		return;
	}
	int  iCurrentElenment = 0;
	size_t i = 1;
	size_t j = 0;
	for ( ; i < uiLength; ++i)
	{
		for ( iCurrentElenment = pData[i], j = i; j > 0 && iCurrentElenment < pData[j-1]; --j )
		{
			pData[j] = pData[j-1];  //将大的元素往后移
		}

		pData[j] = iCurrentElenment;//最后才将要插入的元素插入到最终位置
	}
}
(引用 Guo_guo博客分析 )其特点如下:

1)稳定的,这点不多做解释,参见冒泡排序的说明

2)最好情况下,只做 n-1次比较,不做任何移动,比如 [ 1, 2, 3, 4, 5 ]这个序列,算法a.检查2能否插入1==>不能;b.检查3能否插入到2==>不能;...以此类推,只需做完 n-1 次比较就完成排序,0次移动数据操作。直接插入排序的最好情况是【数据完全有序】

3)最坏情况下,做 n²/2次比较,做 n²/2 次移动数据操作,比如 [ 5, 4, 3, 2, 1 ]这个序列,4需要插入到5前,3需要插入到4,5前,...1需要插入到2,3,4,5前,同样由等差数列求和公式,可得比较次数和移动次数都是n(n-1)/2,简记为n²/2。直接插入排序的最好情况是【数据完全逆序】

4)有人说直接插入排序是在序列越有序表现越好,数据越逆序表现越差,其实这种说法是错误的。举个例子说明,序列a [ 6,1,2,3,4,0 ],数据其实已经基本有序,只是0,6的位置不对,简单0,6交换即可得到正确序列,但插入排序会把 1,2,3,4以此插入到6前,在把0插入到1,2,3,4,6前,几乎有2n次移动操作。可见直接插入排序要想达到高效率,要求的有序不是基本有序,而前半部分完全有序,只有尾部有部分数据无序,例如[0,1,2,3,4,5,5,6,7,8,9,........,107,99,96,101] 对这样一个只有尾部有部分数据无序,且尾部数据不会干扰到序列首部的 [0,1,2,3,4....]的位置时,直接插入排序是其他任何算法都无法匹敌的。

    选择排序:在待排序序列中选择一个最小的元素插入到前面的有序序列的后面的位置。代码如下:

//选择排序
void SortNamesSpace::selectSort(int *pData, size_t uiLength)
{
	if ( NULL == pData || 1 == uiLength )
	{
		return;
	}
	int iMin = 0;
	size_t i = 0;
	size_t j = 0;
	size_t iIndexMinElement = 0;//记录的最小元素的索引
	for ( ; i < uiLength - 1; ++i) //需要(n - 1)趟
	{
		for ( iIndexMinElement = i,j = i + 1; j < uiLength; ++j) //需要(n - 1 - i)
		{
			if ( pData[j] < pData[iIndexMinElement])
			{
				iIndexMinElement = j;  //最小元素的索引
			}
		}
		swap(pData[iIndexMinElement],pData[i]);
	}
}
         其特点如下:选择排序是不稳定的算法,最坏情况是逆序和有大量相同的元素。最好情况是数组基本有序。
            希尔排序时改进版的插入排序,插入排序的增量为0,而希尔排序的增量可以自己设定,插入的位置是由增量算出的位置。代码如下:

//希尔排序
void SortNamesSpace::shellSort( int *pData,size_t uiLength )
{
	if ( NULL == pData || 1 == uiLength )
	{
		return;
	}
	int  iIncrement = 0;
	int  iCurrentElenment = 0;
	size_t i = 0;
	size_t j = 0;

	for ( iIncrement = uiLength/2; iIncrement > 0; iIncrement /= 2)//每次的增量变更
	{
		for ( i = iIncrement; i < uiLength; ++i)
		{
			for ( iCurrentElenment = pData[i],j = i; j >= iIncrement && iCurrentElenment < pData[j-iIncrement]; j -= iIncrement)
			{
				pData[j] = pData[j-iIncrement];
			}
			pData[j] = iCurrentElenment;
		}
	}
}
       (引用 Guo_guo博客分析 )其特点如下:

        希尔排序最有趣的地方在于她的步长序列选择上,步长序列选择的好坏直接决定了算法的效率,这也是为什么希尔排序效率是一个n²/2 ~nlog²n的原因,纠正一下传说来自《大话数据结构》的表中将希尔排序记作了n²/2 ~nlogn,这是不对的,目前的理论研究证明的希尔排序最好效率是nlog²n,这个logn上的平方是不能少的,差距很大的。上面的希尔排序中使用一个特殊的序列,是Marcin Ciura发布的研究报告中得到的目前已知最好序列,在使用这个特别的步长序列时,希尔排序的效率是nlog²n那么希尔排序有哪些特点呢?

1)希尔排序是不稳定的

2)希尔排序特别适合于,大部分数据基本有序,只有少量数据无序的情况下,如 [ 6,1,2,3,4,5,0 ]希尔排序能迅速定位到无序数据,从而迅速完成排序

3希尔排序的步长序列,无论如何选择最后一个必须是1因为希尔排序的最后一步本质上就是直接插入排序,只是通过前面的步长排序,将序列尽量调整到直接插入排序的最高效状态;希尔排序可以理解为特殊的直接插入排序,区别在于步长的选择上,一个默认的为1,一个是人为设置的。

4)研究表明优良的步长序列选择下,在中小规模数据排序时,希尔排序是可以快过快速排序的。因为希尔排序的最佳步长下效率是 n*logn*logn*a(非常小常数因子),而快速排序的效率是 n*logn*b(小常数因子),在 n小于一定规模时,logn*a是可能小于b的,比如 a=0.25b=4n = 65535;此时logn*a<4b=4

综合上述,得到的各种情况下的最优排序分别是:

1)序列完全有序,或者序列只有尾部部分无序,且无序数据都是比较大的值时,【直接插入排序】最佳(哪怕数据量巨大,这种情形下也比其他任何算法快)

2)数据基本有序,只有少量的无序数据零散分布在序列中时,【希尔排序】最佳

3)数据基本逆序,或者完全逆序时,【希尔排序】最佳(哪怕是数据量巨大,希尔排序处理逆序数列,始终是最好的,当然三数取中优化的快速排序也工作良好)

4)数据包含大量重复值,【希尔排序】最佳(来自实验测试,直接插入排序也表现得很好)

5)数据量比较大或者巨大,单线程排序,且较小几率出现基本有序和基本逆序时,【快速排序】最佳

6)数据量巨大,单线程排序,且需要保证最坏情形下也工作良好,【堆排序】最佳

7)数据量巨大,可多线程排序,不在乎空间复杂度时,【归并排序】最佳






       

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值