排序算法--快速排序

一、三色旗问题

问题描述

有一个数组是只由0,1,2三种元素构成的整数数组,请使用交换、原地排序而不是计数进行排序:

解题思路

1.定义两个变量,i和j(下标),从index=0开始遍历

2.如果a[index]==0,swap(a[index++],a[++i]);这里index++是因为index左边不可能有==2的数据了

3.如果a[index]==1,index++;

4.如果a[index]==2,swap(a[index],a[--j]);这里index没有++是因为在右侧不知道交换过来的数据是大于1还是小于1还是等于1,留到下一轮继续判断

5.i的含义:<=i 的部分都是比1小的。所以i的初始值为-1

6.j的含义:>=j 的部分都是比1大的。所以j的初始值为n

列举实例

int a[]={1,1,2,0,1,1,0,2,0,1,2};

cpp 复制代码
#include <vector>
#include <iostream>
using namespace std;

void quick(vector<int>& v)
{
    int n=v.size();
    int i=-1,j=n;
    int idx=0;
    int key=1;
    while(idx<j)
    {
        if(v[idx]==key) idx++;
        else if(v[idx]<tmp) swap(v[++i],v[idx++]);
        else if(v[idx]>tmp) swap(v[--j],v[idx]);   
    }
}
int main()
{
    int n;cin>>n;
    vector<int> v(n);
    for(int i=0;i<n;i++){
        cin>>v[i];
    }
    quick(v);
    for(auto e : v){
        cout<<e<<" ";
    }cout<<endl;
    return 0
}

二、分治思想

假设你是农场主,有一小块土地。你要将这块土地均匀地分成方块,且分出的方块要尽可能地大。显然一下的分法都不符合要求:

如何将一块地均匀地分成方块,并确保分出的方块是最大地呢?使用分而治之地策略!

分而治之 算法是递归的。过程包括两个步骤:

第一步:找出基线条件,这种条件必须尽可能简单。

第二步:不断将问题分解成解法相同的两个问题(不断缩小问题规模,直至找到基线条件)

能使用的最大方块有多大呢》首先找出基线条件。最容易处理的情况是,一条边的长度是另一条边的整数倍。

如果一条边长为25m,另一边长为50m,那么可使用地最大方块就是25*25的。换言之,可以将这块地分成两个这样的方块。

每次递归调用都必须缩小问题的规模。如何缩小前述问题的规模呢?我们首先找出这块地可容纳的最大方块。

可以从这块地中画出两个640*640的方块,同时余下一小块地。现在是顿悟时刻:为何不对余下的那一小块地使用同样的算法呢?

最初要划分的土地尺寸为1680*640,而现在要划分的土地更小,为640*400.适用于这小块地的最大方块,也是适用于整块地的最大方块。换言之,你将均匀划分1680*640土地的问题,简化成了640*400土地的问题

下面再次使用同样的算法。对于640*400的土地,可以从中划分出的最大方块为400*400。这将余下一块更小的土地:400*240。

可以从这块土地中划分出最大的方块余下一块更小的土地,尺寸为240*160

接下来继续划分最终余下:160*80的土地。余下的这块土地,满足基线条件,因为160是80的整数倍2倍,将这两块土地划分为两个方块后,将不会余下任何土地。

因此对最初那块地的划分,适用的最大方块为80*80

这里重申一下分治的工作原理

1.找出简单的基线条件。

2.确定如何缩小问题的规模,使其符合基线条件。

三、快速排序

对于排序算法来说,最简单的数组是什么样子的呢?其实就是根本不需要排序的数组。

也就是空数组或者只包含一个元素的数组。因此基线条件为空数组或单元素的数组。在这种情况下,只需要返回数组--根本不用排序。

cpp 复制代码
void QuickSort(int arr[],int n){
    if(n<2) return;
}

下面我们看一下更长的数组:双元素数组。对包含两个元素的数组进行排序也很容易:

检查第一个元素是否比第二个元素小,如果不比第二个小,就交换它们的位置

那么接下来就是三元素数组。对包含三个元素的数组进行排序:

我们理所应当的想到了一开始提到的三色旗问题,和分而治之的思想。

工作原理

第一步:从数组中选取一个元素,这个元素被称为基准值。

我们暂时将数组的第一个元素用作基准值。

第二步:找出比基准值小的元素以及比基准值大的元素。

小的放到基准值左边,大的放到基准值右边。

现在数组被我们分成了三个部分:

一部分为小于基准值的所有元素

基准值(此时,基准值已经被放在了它所应在的位置)

一部分为大于基准值的所有元素

这里只是进行了分区,得到的两个子数组是无序的。单如果这两个数组是有序的,对整个数组进行排序非常容易。

如果子数组是有序的,就可以像下面这样合并并得到一个有序的数组:左边的数组+基准值+右边的数组。在这里,就是【10,15】+【33】+【 】,结果为有序数组【10,15,33】.

如何对子数组进行排序呢?

对于包含两个元素的数组(左子数组)以及空数组(右子数组),快速排序直到如何将它们排序,因此只要对这两个子数组进行快速排序,再合并结果,就得到了一个有序数组!

代码实现

cpp 复制代码
#include <bits/stdc++>
using namespace std;

void quickSort(vector<int>& v,int L,int R)
{
    if(L>=R) return;
    int key_val=v[L];
    int i=L-1,j=R+1;
    int idx=L;
    while(idx<L){
        if(v[idx]==key_val) idx++;
        else if(v[idx]<key_val) swap(v[++i],v[idx++]);
        else if(v[idx]>key_val) swap(v[--j],v[idx]);
    }
    
    quickSort(v,L,i);//将左子数组进行快排
    quickSort(v,j,R);//将右子数组进行快排
    //直到L==R,结束递归,每次递归都会将L-R的基准值放到合适的位置
    //递归结束后所有元素都已经排好序了
}

算法分析

时间复杂度:O(nlogn)

对于快排而言,最优的情况就是,每次划分的都很均匀,假设要排序n个元素,第一次划分的时候,需要对整个数组扫描一下,做n次比较的时间为T(n)。然后将数组一分为二,那么各自还需要T(n/2)的时间(注意是最好的情况,所以平分一半)。于是不断地划分辖区,我们就有了下面地不等式推断:

cpp 复制代码
T(1) == 0
T(n) <= 2*T(n/2) + n 
T(n) <= 2*(2*T(n/4)+n/2) + n == 4*T(n/4)+2*n
T(n) <= 4*(2*T(n/8)+n/4) + 2*n ==8*T(n/8)+3*n
...
T(n) <= nT(1) + (logn)*n == O(nlogn) 

稳定性:不稳定


感谢大家!

相关推荐
郭wes代码3 分钟前
Cmd命令大全(万字详细版)
python·算法·小程序
scan72418 分钟前
LILAC采样算法
人工智能·算法·机器学习
菌菌的快乐生活38 分钟前
理解支持向量机
算法·机器学习·支持向量机
大山同学43 分钟前
第三章线性判别函数(二)
线性代数·算法·机器学习
axxy20001 小时前
leetcode之hot100---240搜索二维矩阵II(C++)
数据结构·算法
黑客Ash1 小时前
安全算法基础(一)
算法·安全
AI莫大猫2 小时前
(6)YOLOv4算法基本原理以及和YOLOv3 的差异
算法·yolo
taoyong0012 小时前
代码随想录算法训练营第十一天-239.滑动窗口最大值
c++·算法
Uu_05kkq2 小时前
【C语言1】C语言常见概念(总结复习篇)——库函数、ASCII码、转义字符
c语言·数据结构·算法
清梦20203 小时前
经典问题---跳跃游戏II(贪心算法)
算法·游戏·贪心算法