相信我们在没接触过排序知识之前,一定会觉得快速排序非常具有魅力,不因别的单纯快排这个名字就让人不明觉厉,但是了解一个算法不应该只知道code,了解思想,应用非常重要。
我先问出我心中非常好奇的问题:
- 快排为啥叫快排,快排是所有排序里面性能最好的吗?
- 快排适合什么情况呢,还是无论什么情况下快排总是最好的(显然× ?
- 快排算法的思想是什么?其性能优良的原因是依赖于算法中的哪个部分?
首先回答前两个问题:
快排的性能在所有排序算法里面是最好的,数据规模越大快速排序的性能越优。快排在极端情况下会退化成
![equation?tex=O%28n%5E%7B2%7D%29](https://i-blog.csdnimg.cn/blog_migrate/745be8c743daa10d28c8edf5dca060b9.png)
下面回答第三个问题:
- Quick Sort
![e270f20f7a56f2c11c02393211c4d81f.gif](https://i-blog.csdnimg.cn/blog_migrate/77480f46fee89497e8a89abb0261664a.gif)
显然,我们从图中可以看出快排运用了二分的思想,首先选择一个基准,定义左右两端指针,先从左到右进行扫描直到,R[hi] < temp,将R[hi]移动至lo所在位置
![equation?tex=%5CRightarrow](https://i-blog.csdnimg.cn/blog_migrate/b3f14e4173a5e71af2b3788193853c8b.png)
总结下来(转, 自己懒得敲了:
- 选择A中的任意一个元素pivot,该元素作为基准
- 将小于基准的元素移到左边,大于基准的元素移到右边(分区操作)
- A被pivot分为两部分,继续对剩下的两部分做同样的处理
- 直到所有子集元素不再需要进行上述步骤
void QuickSort(R[], int lo, int hi)
{
将lo, hi用i, j两个副本进行拷贝(**
声明基准;
if(划分长度大于等于2,即hi > lo)
{
取基准为左端端点;//都可
当hi 不等于 lo时循环;
当hi > lo且R[hi] > temp时循环;
hi--(继续往中间靠;
R[lo] = R[hi];//发现小于基准的替换将其替换到左半区
当lo < hi且R[lo] < temp时循环;
lo++(继续往中间靠;
R[hi] = R[lo];//发现大于基准的将其替换到右半区
在hi,lo重合处放基准;//数据平衡,循环里有一个数据是直接被覆盖的,就是基准,现在将其添加回数组中
QuickSort(R[], lo, i - 1);
QuickSort(R[], i + 1, hi);//递归继续进行快排
//要是前面没有进行副本拷贝在这里进行递归操作时就不太好判断边界了。
}
}
![equation?tex=Q%3A+](https://i-blog.csdnimg.cn/blog_migrate/03b71c0a9a00cc2650f78f189c128af4.png)
现在回答一下,为什么快速排序是平均性能最好的排序算法,其优渥之处体现在哪?
- 首先,如果我们已经知道
,那么之后
就无需再比较了,假如再进行比较就是
- 第二,假如我们已知,
,那么
就不需要进行比较了,再进行比较就算是
就是递归排序的基准,因此
就只会在自己的区间进行排序,不会出现冗余排序了。
因此,我们可以了解到,快速排序的优越性体现在他没有多余的比较上,对于初学者,我们可以较为简单的认为,快排所需要的指令数会比较少。
算法分析:
- 最差情况:
最差情况是,每次我们在划分时,所取的基准总是数组中最小的,因此我们总共会进行
![equation?tex=n-1](https://i-blog.csdnimg.cn/blog_migrate/737e9d31ad1acdc495b205e4eb76c632.png)
![equation?tex=i](https://i-blog.csdnimg.cn/blog_migrate/b1677f9423a31be742701330c190cf05.png)
![equation?tex=n+-+i%2B1](https://i-blog.csdnimg.cn/blog_migrate/73334b9d93dfc02090e5fca8a0bc6ca1.png)
![equation?tex=n-i](https://i-blog.csdnimg.cn/blog_migrate/7198320c10d2def0656204182526d648.png)
故:
![equation?tex=C_%7Bmax%7D%3D%5Csum_%7Bi+%3D1%7D%5E%7Bn-1%7D%7Bn-i%7D%3D%5Cfrac%7Bn%28n-1%29%7D%7B2%7D%3DO%28n%5E%7B2%7D%29](https://i-blog.csdnimg.cn/blog_migrate/07afbe06e1bab2e0ea3562eea4cba4ed.png)
- 最好情况:
最好的情况是,每次所取的基准就是该数组的中点,因此一共需要进行n次划分,对于 长度为
![equation?tex=n](https://i-blog.csdnimg.cn/blog_migrate/ce59f55893efd960f1dbdfcd5c192a96.png)
![equation?tex=n-1](https://i-blog.csdnimg.cn/blog_migrate/737e9d31ad1acdc495b205e4eb76c632.png)
![equation?tex=2C%28%5Cfrac%7Bn%7D%7B2%7D%29](https://i-blog.csdnimg.cn/blog_migrate/49a127590e1018167674241aa307fe17.png)
![equation?tex=n+%3D+2%5E%7Bk%7D](https://i-blog.csdnimg.cn/blog_migrate/ce59f55893efd960f1dbdfcd5c192a96.png+%3D+2%5E%7Bk%7D)
![equation?tex=C%28n%29%5Cleq+n%2B2C%28%5Cfrac%7Bn%7D%7B2%7D%29](https://i-blog.csdnimg.cn/blog_migrate/081e7c70ecbeb45d824406fbf7e4abc5.png)
![equation?tex=%5Cleq+n%2B2%28%5Cfrac%7Bn%7D%7B2%7D%2B2C%28%5Cfrac%7Bn%7D%7B2%5E%7B2%7D%7D%29%29%3D2n%2B4C%28%5Cfrac%7Bn%7D%7B2%5E%7B2%7D%7D%29](https://i-blog.csdnimg.cn/blog_migrate/70e514ed546bb04fb01d8f04ea86ddd4.png)
![equation?tex=%5Cleq+kn%2BnC%281%29+%3D+%5Cleq+n%5C%3Blog_%7B2%7Dn%5C%3B%2B%5C%3BnC%281%29%3DO%28nlog_%7B2%7Dn%29](https://i-blog.csdnimg.cn/blog_migrate/009a8dd6780b8b18f9d7b1f1238ab3fa.png)
- 平均情况:
用递归算(下次补。。。):
随机进行切割,最好算出复杂度还是
![equation?tex=O%28nlog_%7B2%7Dn%29](https://i-blog.csdnimg.cn/blog_migrate/dd5d613865a012446e9867a917e10327.png)
void QuickSort(int R[], int lo, int hi){
int i = lo, j = hi;
int temp;
if(i < j){
temp = R[i];
while (i != j)
{
while(j > i && R[j] > temp)-- j;
R[i] = R[j];
while(i < j && R[i] < temp)++ i;
R[j] = R[i];
}
R[i] = temp;
QuickSort(R, lo, i - 1);
QuickSort(R, i + 1, hi);
}
}
![equation?tex=Talk%5C%3Bis%5C%3B+easy%2C%5C%3Bshow%5C%3Bme%5C%3Bthe%5C%3Bcode.%5Cquad+XD](https://i-blog.csdnimg.cn/blog_migrate/be7b09f972a9f57191db9cad78040a80.png)