常用排序算法

虽说是常用,也只是一些我自己比较经常看到的算法,像折半插入排序、多路归并排序、基数排序桶排序之类的还是点链接看看吧,这里就不写了。
以下主要是多篇博客的总结
十大经典排序算法(动图演示)
常见的7种排序算法
[算法总结] 十大排序算法

1、冒泡排序

冒泡排序主要是把通过比较,把小的元素从下面交换到上面,因此称为“冒泡”(从小到大排)。

算法描述:

  • 比较相邻的元素,如果前面的比后面的大,就交换它们两个;
  • 对每一对相邻元素作同样的工作,从结尾的最后一对到开始第一对,这样在最前面的元素应该会是最小的数;
  • 针对所有的元素重复以上的步骤,除了第一个;
  • 重复步骤1~3,直到排序完成。

记得大一上软件工程导论课老师让我们写个冒泡排序,就说我们很多同学写的都是把大的沉下去(包括我自己),这不叫冒泡,这里比较注意这一点。

/*
* array 为待排序数组
* n为数组元素个数
*/
void bubbleSort(int* array, int n)
{
	bool needSwap = false;
	for (int i = 0; i < n; i++)
	{
		needSwap = false;
		//这趟循环把最小的“泡”“冒”到前面
		for (int j = n-1; j > i; j--)
		{
			if (array[j-1] > array[j])
			{
				int temp = array[j-1];
				array[j-1]  = array[j];
				array[j] = temp;
				needSwap = true;
			}
		}
		//如果比较中没有发生交换,那么就说明已经有序
		if (!needSwap)
			break;
	}
}

时间复杂度平均为O(n^2),最好的情况为O(n)(已经有序,一轮比较中没有发生交换),
最坏的情况为O(n^2)(逆序)。
空间复杂度为O(1)。
算法稳定。

2、简单选择排序

在无序的序列中,先选出最小值(升序排列),放到第一个位置,然后对剩下的元素做同样操作,选出最小值,放到第二个位置,以此类推。

n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下:

  • 初始状态:无序区为R[1…n],有序区为空;
  • 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R(i…n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1…i]和R[i+1…n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
  • n-1趟结束,数组有序化了。
void selectionSort(int *array, int n)
{
	for (int i = 0; i < n; i++)
	{
		int min_index = i;
		for (int j = i+1; j < n; j++)
		{
			if (array[min_index ]> array[j])
			{
				min_index = j;
			}
		}
		//将第i趟比较中最小的元素与下标i所在元素交换
		if (min_index != i)
		{
			int temp = array[i];
			array[i] = array[min_index];
			array[min_index] = temp;
		}
	}
}

时间复杂度平均为O(n^2),
最好的情况和最坏情况都是O(n^ 2)。
空间复杂度为O(1)。
使用例子{2’,2,1,3}测试可以发现算法不稳定。

3、直接插入排序

就像我们打扑克牌时,把每一张牌插到他应该在的位置;插入排序也把序列分成有序和无序两部分,将无序的元素一个个插入到有序序列中应该在的位置。
插入排序还有折半插入和希尔插入,直接插入是对有序部分顺序查找插入,而折半插入则是二分查找插入;希尔排序则是先将序列分组有序,再合并序列,分组有序的方法选择直接插入排序。

相关博客:
理解希尔排序的排序过程

算法描述:

  • 从第一个元素开始,该元素可以认为已经被排序;
  • 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  • 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  • 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  • 将新元素插入到该位置后;
  • 重复步骤2~5。
void  insertSort(int* array, int n)
{
	for (int i = 1; i < n; i++)
	{
	   int key = array[i];
	   int j = i-1;
	   for (; array[j] > key && j >= 0; j--)
	   {
	       array[j+1] = array[j];
       }
       array[j+1] = key;
    }
}

时间复杂度平均为O(n^2),最好的情况O(n)(已经有序),
最坏情况是O(n^ 2)(逆序)。
空间复杂度为O(1)。
算法稳定。

以上算法比较适用于中小规模的数据集,其中插入排序最快,选择排序其次,冒泡最慢。

4、二路归并排序

归并算法一般是递归形式,将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。

算法描述:

  • 把长度为n的输入序列分成两个长度为n/2的子序列;
  • 对这两个子序列分别采用归并排序;
  • 将两个排序好的子序列合并成一个最终的排序序列。
/*
*	@start为待排序区间的开始
*	@ end为待排序区间的结束位置(最后一个元素所在下标,不同迭代器的end)
*	@ array为待排序整个序列
*	@ temp为辅助空间
*/
void mergeSort(int start, int end, int* array, int* temp)
{
    if (start < end)
    {
        int mid = (start+end)/2;
        
        mergeSort(start, mid, array, temp);
        mergeSort(mid+1, end, array, temp);
        
        //左半部分
        int pre_start = start;
        int pre_end = mid;
        //右半部分
        int lat_start = mid+1;
        int lat_end = end;
        //辅助数组的下标
        int index = 0;
        
        //将两个有序序列合并
        while (pre_start <= pre_end && lat_start <= lat_end)
        {
            if (array[pre_start] <= array[lat_start])
            {
                temp[index++] = array[pre_start++];
            } 
            else 
            {
                temp[index++] = array[lat_start++];
            }
        }
        
        //余下的单数个元素
        while (pre_start <= pre_end)
        {
            temp[index++] = array[pre_start++];
        }
        
        while (lat_start <= lat_end)
        {
            temp[index++] = array[lat_start++];
        }
        
        //合并到原始序列数组中
        for (int i = 0; i < index; i++)
        {
            array[start + i] = temp[i];
        }
    }
}

每一趟归并的时间复杂度为O(n),共需要进行 logN 趟排序,所以时间复杂度为 O(NlogN),最好的和最坏情况都是O(nlogn)。
空间复杂度为O(n)。
算法稳定。

5、快速排序

快排的思想是先选取序列中一个元素,然后将序列中比这个元素小的放它的左边,比它大的放在右边(升序排列);然后再分别对这两个区间做相同操作,直到区间只有一个元素。
快速排序的关键点在于用作比较的元素的值的选取,一般可以取首位,或末尾,或中间值,较好的取法是三位取中和随机选取。
这里介绍的是取首位的方法。

相关博客:
数据结构-快速排序
快速排序

算法描述:

  • 从数列中挑出一个元素,称为 “基准”(pivot);
  • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  • 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
// 返回选取元素所在的下标,用于左右两边元素后面分区
int partition(int* array, int start, int end)
{
    int i = start;
    int j = end;
    int key = array[start];
    while (i < j)
    {
        while (i < j && array[j] >= key)
            j--;
        
        array[i] = array[j];
        
        while (i < j && array[i] <= key)
            i++;
        
        array[j] = array[i];
    }
    array[i] = key;
    return i;
}
 
void qSort(int* array, int start, int end)
{
    if (start >= end)
        return;
    
    int pivot = partition(array, start, end);
    qSort(array, start, pivot - 1);
    qSort(array, pivot + 1, end);
}

时间复杂度:最好情况为O(nlogn)(每次都能分成对等的两部分,栈空间为O(logn)),
最坏情况为O(n^2)(当序列已有序,每次只能分为长度为0和n-1的区,此时栈空间为O(n))。平均时间复杂度为 O(NlogN)。
这是对于每次选取首位作为pivot的情况,使用随机选取pivot的方法能使最坏情况得到改善,若排序前先对序列乱序,算法表现更好,但是开销较大。
空间复杂度为O(1)。

有关时间复杂度的讨论可以看看这篇:
快速排序时间复杂度为O(n×log(n))的证明

使用例子{1,3, 2’,2}测试可以发现算法不稳定。

6、堆排序

堆排序对于我来说是最难理解的一种排序算法,它把数组看成一个二叉堆(基于完全二叉树,即给每个元素自上而下自左向右编号,得到的编号与满二叉树一致),基于大顶堆(父节点大于等于左右节点的值)和小顶堆(父节点小于等于左右节点的值)分别可以将序列变成升序序列和降序序列。
基于大顶堆,大顶堆的根节点(下标为0)为堆的最大值,先将序列调整成大顶堆,然后将根节点与末尾元素交换,再将除末尾,剩下的元素重新调整为大顶堆,再把根节点和新末尾元素交换…

过程重点在于如何调整,调整算法基于完全二叉树的性质:
若当前元素标号为i(从0开始算),则其左孩子标号为2 x i+1,右孩子标号为2 x i+2。

相关博客:
图解排序算法(三)之堆排序
漫画:什么是堆排序?

算法描述:

  • 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
  • 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
  • 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。
  • 不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
void heapAdjust(int* array, int parentIndex, int num)
{
    int key = array[parentIndex];
    
    for (int k = 2*parentIndex+1; k < num; k = 2*k+1)
    {
        if (k+1 < num && (array[k+1] > array[k]))
        {
            k++;
        }
        
        if (key >= array[k])
            break;
        
        array[parentIndex] = array[k];
        parentIndex = k;
    }
    
    array[parentIndex] = key;
}


void heapSort(int* array, int num)
{
    for (int i = num/2 - 1; i >= 0; i--)
    {
        heapAdjust(array, i, num);
    }
    
    for (int i = num-1; i > 0; i--)
    {
        int temp = array[i];
        array[i] = array[0];
        array[0] = temp;
        heapAdjust(array, 0, i);
    }
}

时间复杂度为O(nlogn),最好和最坏情况都为O(nlogn),
空间复杂度为O(1)。
使用例子{3,2’,2,1}测试可发现算法不稳定。

各大常见排序算法性能对比

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值