以下内容摘自《啊哈,算法》 讲解的通俗易懂,大赞作者的文字功底!
一、什么是排序算法
举个例子: 现有一组数据 6 1 2 7 9 3 4 5 采用快速排序,具体步骤如下:
首先让变量i、j分别指向最左端和最右端,即i指向6所在的位置,j指向5所在位置。
首先让j开始移动,因为此处设置的基准数是最左边的数,所以需要让哨兵j 先出动(如果不这样做的话,按照下面的步骤,得不到预期效果)
哨兵j 一步一步地向左挪动(即j),直到找到一个小于6 的数停下来。接下来哨兵i 再一步一步向右挪动(即i++),直到找到一个大于6
的数停下来。最后哨兵j 停在了数字5 面前,哨兵i 停在了数字7 面前。
交换前
交换后
到此,第一次交换结束。接下来哨兵j 继续向左挪动(再次友情提醒,每次必须是哨兵j 先出发)。他发现了4(比基准数6 要小,满足要求)之后停了下来。哨兵i 也继续向右挪
动,他发现了9(比基准数6 要大,满足要求)之后停了下来。此时再次进行交换,交换之后的序列如下:
交换前:
交换后:
第二次交换结束,“探测”继续。哨兵j 继续向左挪动,他发现了3(比基准数6 要小,满足要求)之后又停了下来。哨兵i 继续向右移动,糟啦!此时哨兵i 和哨兵j 相遇了,哨兵i 和哨兵j 都走到3 面前。说明此时“探测”结束。我们将基准数6 和3 进行交换。执行过程如下:
i,j相遇:
准备交换:
交换后:
(注意:此时i,j都指向6的位置)
到此第一轮“探测”真正结束。此时以基准数6 为分界点,6 左边的数都小于等于6,6右边的数都大于等于6。回顾一下刚才的过程,其实哨兵j 的使命就是要找小于基准数的数,
而哨兵i 的使命就是要找大于基准数的数,直到i 和j 碰头为止。
现在基准数6已经归位,刚好处于序列的第6个位置,在基准数6的左边全部小于6,在基准数的右边全部大于6.现在以6为界将序列分为左右两部分,分别对左右两部分递归执行
以上算法,直到最终只有一个数字为止(只剩下一个数字也就不用比较啦)
快速排序的每一轮处理其实就是将这一轮的基准数归位,直到所有的数都归位为止,排序就结束了
整个排序过程如下:
二、算法分析
快速排序之所以比较快,是因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全
部放到基准点的右边。这样在每次交换的时候就不会像冒泡排序一样只能在相邻的数之间进行交换,交换的距离就大得多了。因此总的比较和交换次数就少了,速度自然就提高了。
当然在最坏的情况下,仍可能是相邻的两个数进行了交换。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
以下是一个博客详细分析的复杂度,原文地址:http://blog.csdn.net/yuzhihui_no1/article/details/44198701
时间复杂度
最优情况下时间复杂度
令:n = n/2 = 2 { 2 T[n/4] + (n/2) } + n ----------------第二次递归
= 2^2 T[ n/ (2^2) ] + 2n
令:n = n/(2^2) = 2^2 { 2 T[n/ (2^3) ] + n/(2^2)} + 2n ----------------第三次递归
= 2^3 T[ n/ (2^3) ] + 3n
......................................................................................
令:n = n/( 2^(m-1) ) = 2^m T[1] + mn ----------------第m次递归(m次后结束)
当最后平分的不能再平分时,也就是说把公式一直往下跌倒,到最后得到T[1]时,说明这个公式已经迭代完了(T[1]是常量了)。
得到:T[n/ (2^m) ] = T[1] ===>> n = 2^m ====>> m = logn;
T[n] = 2^m T[1] + mn ;其中m = logn;
T[n] = 2^(logn) T[1] + nlogn = n T[1] + nlogn = n + nlogn ;其中n为元素个数
又因为当n >= 2时:nlogn >= n (也就是logn > 1),所以取后面的 nlogn;
综上所述:快速排序最优的情况下时间复杂度为:O( nlogn )
最差情况下时间复杂度
最差的情况就是每一次取到的元素就是数组中最小/最大的,这种情况其实就是冒泡排序了(每一次都排好一个元素的顺序)
这种情况时间复杂度就好计算了,就是冒泡排序的时间复杂度:T[n] = n * (n-1) = n^2 + n;
综上所述:快速排序最差的情况下时间复杂度为:O( n^2 )
平均时间复杂度
空间复杂度
#include<stdio.h> #include<string.h> #define N 100 void quickSort(int array[],int left,int right) { int i = left; int j = right; int value = array[left],temp; // 得到哨兵元素 // 异常处理,保证right >= left if(left>right) return; while(i < j) { // 从右向左找一个比基准数小的 while(value<=array[j] && i<j) j--; // 从左到右找一个比基数大的 while(value>=array[i] && i<j) i++; // 当i,j没相遇时 交换两者位置 if(i < j) { temp = array[j]; array[j] = array[i]; array[i] = temp; } } // 最终基准归位 if(i == j) { array[left] = array[i]; array[i] = value; } // 递归调用基准数划分的左右两组序列 quickSort(array,left,i-1); quickSort(array,i+1,right); } void print(int array[],int len) { int i; for(i=0;i<len;++i) printf("%d ",array[i]); } int main() { int n,i,array[N]; // n表示要输入排序元素的个数 scanf("%d",&n); for(i=0;i<n;++i) { scanf("%d",&array[i]); } // 调用排序函数 quickSort(array,0,n-1); //打印函数 print(array,n); printf(" "); return 0; }
测试用例如下: