面试官都在问 | 快速排序C++实现

比较类排序系列-快速排序

1. 原理

快排是C. A. R. Hoare在1960提出的一种排序算法,这是一种采用分治思想的排序算法,大致分为三个步骤。

  1. 定基准——首先选择一个元素作为基准值
  2. 划分区——所有比基准小的元素置于基准左侧,比基准大的元素置于右侧,构成左右两个子序列
  3. 递归调用——递归地调用此切分过程,切分其子序列,直到子序列只含有一个值时停止递归

如下图所示
在这里插入图片描述

上图只完整的演示了三次划分的过程,第一次选取3为基准值,进行一次划分,得到了第二行的序列,此时得到两个子序列,如第三行和第五行所示。对第三行的子序列继续进行划分,得到第四行的划分结果。对第五行的子序列继续进行划分,得到第六行的划分结果。后面会继续对第四行和第六行划分出的新的子序列继续相同的过程,最后会得到一个完整的有序序列。

1.1基准值

基准值的选取有多种方法。但是不同的选取方法对排序的性能会有比较大的影响。

  • 序列的第一个值:最简单的选取方式,但是某些情况下,会导致划分的子序列非常不均衡。比如数据有序时,每次只能划分出一个子序列,而不是两个。
  • 几数取中:从几个数中选取一个基准值,选取的基准值是这几个数中的中间值。此种方式可以避免第一种方式中的极端情况出现,会让划分比较均衡。
  • 随机选取:随机选一个数作为基准值,此种方式也会避免极端情况的出现。
1.2划分

划分是把数据分成两部分,一部分小于基准值,一部分大于基准值,划分过程大致如下:

  1. 从当前序列从后往前找到第一个小于基准的数。
  2. 从当前序列从前往后找到第一个大于基准的数。
  3. 交换找到的这两个数。继续执行1,2步,直到1,2步遍历的位置相遇则结束。
  4. 基准值与相遇位置的数据进行交换,完成当前序列的划分,此时所有小于基准的数据在左,大于等于基准的数据在右。

2. 代码

void swap(int* array, int i, int j)
{
	int tmp = array[i];
	array[i] = array[j];
	array[j] = tmp;
}

int partion(int* array, int begin, int end)
{
	//第一个数据作为基准值
	int key = array[begin];
	int start = begin;
	while (begin < end)
	{
		//从后向前找小
		while (begin < end && array[end] >= key)
			--end;
		//从前向后找大
		while (begin < end && array[begin] <= key)
			++begin;
		//可能找到了两个数据, 交换
		swap(array, begin, end);
	}
	//交换基准值和相遇位置的值
	swap(array, start, begin);
	//返回基准值位置
	return begin;
}

void quickSort(int* array, int begin, int end)
{
	if (begin >= end)
		return;
    //划分当前区间
	int mid = partion(array, begin, end);
	//划分小区间
	quickSort(array, begin, mid - 1);
	quickSort(array, mid + 1, end); 
	
}
public static void swap(int[] arr, int i, int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

public static int partion(int[] arr, int left, int right){
        //第一个数据作为基准值
        int key = arr[left];
        int start = left;
        while(left < right){
            //从后向前找小
            while(left < right && arr[right] >= key)
                --right;
            //从前向后找大
            while(left < right &&arr[left] <= key)
                ++left;
            //交换
            swap(arr, left, right);
        }
        //交换基准值和相遇位置的值
        swap(arr, left, start);
        return left;
    }

public static void quickSort(int[] arr, int left, int right){
        if(left < right){
            //划分当前区间
            int mid = partion(arr, left, right);

            //划分小区间
            quickSort(arr, left, mid - 1);
            quickSort(arr, mid + 1, right);
        }
    }

3. 时间空间复杂度

3.1 时间复杂度

快速排序的过程就是不断的进行子序列的划分,直到子序列中只包含一个值,此时排序也就结束了。

  • 最坏时间复杂度:如果数据是有序的,则每一次划分之后,只会得到一个子序列,子序列的元素比其父序列少一个,故每次需要划分子序列大小是一个等差数列,故:1 + 2 + 3 + … + n - 1 = n(n - 1)/2 = O(n^2)。
  • 最好时间复杂度:最理想情况下,每次划分都可以得到元素个数相等的两个子序列,设T(n)为n个元素的时间复杂度,则T(n) = T(n/2) + T(n / 2) + O(n) = 2*T(n/2) + O(n),可以看到每次都是二分的关系,递归的层数为logn,每一层需要给n的元素进行划分,故时间复杂度为O(nlogn)。
  • 平均时间复杂度:O(nlogn)。
3.2 空间复杂度

快速排序过程中,需要进行递归调用,所以需要进行函数压栈操作,每一个函数栈中占用常数空间,最好情况下,最大的递归调用深度为logn, 最坏情况下,最大递归调用深度为n, 故最好情况下为O(logn), 最坏为O(n)。

总结

实际中,普通的排序场景,都是采用快速排序,因为其实现容易,且时间和空间的消耗都比较小,排序比较快。

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值