快速排序算法从入门到提高
这篇文章酝酿了好几天,今天终于开始敲击键盘写出来。之前学习数据结构与算法是大一下期的一门课程,但是那个们课老师的水平实在不敢恭维,讲的数据结构和算法都是在很浅的层次,仅此是PPT课,后来由于自己对算法这一块比较感兴趣,所以就自己自学了一些内容,今天想在这里写一篇博客全当是抛砖引玉吧,希望大家不吝赐教!
快速排序算法其实很简单,但是应用起来又不是那么简单,为什么这样说呢?因为单纯的想要理解算法的原理,是很简单的,不过就三个步骤:①分解②解决③合并,写出来代码也是相对简单的一件事情。
但是!!在待排序的数组内的数据不同的情况下,它的性能也不尽相同,比较好的情况下,复杂度正比于 Nlg(n),但是在坏的情况下,复杂度会退化到N^2级别。
所以本文就先说明快速排序的基本原理,然后再逐步写出它的改进版的算法和应用的情况。
首先快速排序可以由三个步骤组成:
①分解: 分解就是 把整个数组分解为两个,例如 A[p...r]分解为 两个(可能为空)的子数组 A[p...q-1]和A[q+1...r]使的A[p...q-1]中的每个元素都小于A[q],而A[q+1...r]中的每个元素都大于A[q]。
②解决: 通过递归调用快速排序,对数组A[p...q-1]和A[q+1...r]进行排序。
③合并:因为子数组都是原址排序的,所以不需要合并操作,数组A[p...r]已经有序了。
其实无非就是,找到一个固定的元素(一般都选第一个或者最后一个,但是也有随机选择的),然后扫描整个数组,最后的状态就是比这个固定元素小的元素都在它左边,比它大的元素都在他右边。等于的它元素要看自己的代码怎么写了,可能在左也可能在右。(从这里其实可以看出来,快速排序不是一种稳定的排序方法)
一:第一种版本的快速排序(这是最简单的一种版本的,它是单向扫描的,所以权且成为单描版)
结合着算法导论的图片更容易理解:
等于是两个指针一起扫描,遇到比目标元素小的就交换到左边,遇到大于等于目标元素的就保持在两个指针之间。
以下是代码:
void quick_sort(int A[],int q,int r)
{
if(p < r)
{
q = partition(A,p,r);
quick_sort(A,0,q-1);
quick_sort(A,q+1,r);
}
}
上面是递归调用的算法。
int partition(int A[],int p,int r)//r其实为数组最边那个元素的下标,也就是n-1位置
{
int x = A[r];
int i = p - 1;
for(int j = p;j <= r - 1;j++)
{
if(A[j] <= x)
{
i++;
swap(A[i],A[j]);
}
}
swap(A[i+1],A[r]);
return i+1;
}
此代码是从左往右单向扫描的(也可以改为从右往左,纯粹是个人喜好)
上面的代码有什么毛病?其实毛病是大大滴!理想情况下,它的复杂度应该是Nlg(n),可如果待排序的数组本来就是有序的或者说是接近于有序,它就会退化到n^2级别,为什么?
想象下,1 2 3 4 5 6 7 8 9 10 这几个有序的数字。
假设选择第一个元素 1 作为固定元素。第一遍扫描结束后数组分为 1和2 3 4 5 6 7 8 9 10,两部分,然后递归调用,第三次分为 2和3 4 5 6 7 8 9 10 两部分,依次类推。
那么有没有方法能够克服呢?
二 双向的快速排序
那就要看接下来这个版本的快速排序了,它采用双向排序的思想,从左到右,从右到左都扫描。
int partition(int l,int u)
{
int t = A[l];
int i = l;
int j = u;
while(i < j)
{
//从右向左,找小于t的数
while(i < j&&A[j] >= t)
{
j--;
}
if(i < j) //说明从右向左找到了一个数小于t,于是把这个数放到左边的坑里
{
A[i] = A[j];
i++; //i++ 说明这个坑的元素已经找到放好,然后找未知区域
}
//从左到右去找大于t的数字
while(i < j&&A[i] <t)
{
i++;
}
if(i < j)//说明从左到右找到了一个数大于t,于是把这个数字放到右边的坑里
{
A[j] = A[i];
j--;//j-- 说明这个坑的元素已经放好,然后找未知区域
}
}
//退出时 已经保证t左边的元素都小于t,t右边的元素都大于t,此时i=j
A[i] = t;
return i;
}
编程珠玑上还讲了一种建议,就是在数组较短的情况下,插入排序的性能更优,所以还有一种思路就是在递归前判断数组的长度,如果不算太长就调用插入排序。
但是什么样的长度才算不太长?在《算法》中作者的建议是15左右包括15在内的数组,用插入排序更加适合。
还有第三种快速排序的算法:
三, 三向排序
示意图如图所示:
它的思路其实只要你理解了上面的两种,他是很简单的。
关键就是再一个过程种让数组满足一下三个条件:
①对于某个j a[j]已经排定。
②a[lo] 到a[j-1]中所有的元素都不大于a[j]
③a[j+1] 到 a[hi]中所有的元素都不小于a[j]
代码:
void sort(int []a,int lo,int hi)
{
if(hi <= lo)
return;
int lt = lo,i = lo + 1,gt = hi;//lt,i,gt相当于三个游动的指针
v = a[lo];
while(i <= gt)
{
if(a[i] < v)
{
swap(a[lt],a[i]);
i++;
lt++;
}
else if(a[i] > v)
{
swap(a[gt],a[i]);
gt--;
}
else if(a[i] == v)
{
i++;
}
}
sort(a,lo,lt-1);
sort(a,gt+1,hi);
}
好了,上面就是我对快速排序算法的一些浅薄的见解和学习,希望能对你有用,也更希望有大佬能够批评指正。
参考资料:《编程珠玑》
《算法导论》
《算法》