排序算法(三)快速排序
--转自啊哈磊【坐在马桶上看算法】算法3:最常用的排序——快速排序
冒泡排序可以说是我们学习第一个真正的排序算法,并且解决了桶排序浪费空间的问题,但在算法的执行效率上却牺牲了很多,它的时间复杂度达到了O(N2)。假如我们的计算机每秒钟可以运行10亿次,那么对1亿个数进行排序,桶排序则只需要0.1秒,而冒泡排序则需要1千万秒,达到115天之久,是不是很吓人。那有没有既不浪费空间又可以快一点的排序算法呢?那就是“快速排序”啦!光听这个名字是不是就觉得很高端呢。
假设我们现在对“6 1 2 7 9 3 4 5 10 8”这个10个数进行排序。首先在这个序列中随便找一个数作为基准数(不要被这个名词吓到了,就是一个用来参照的数,待会你就知道它用来做啥的了)。为了方便,就让第一个数6作为基准数吧。接下来,需要将这个序列中所有比基准数大的数放在6的右边,比基准数小的数放在6的左边,类似下面这种排列。

首先哨兵j开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵j先出动,这一点非常重要(请自己想一想为什么)。哨兵j一步一步地向左挪动(即j--),直到找到一个小于6的数停下来。接下来哨兵i再一步一步向右挪动(即i++),直到找到一个数大于6的数停下来。最后哨兵j停在了数字5面前,哨兵i停在了数字7面前。
现在交换哨兵i和哨兵j所指向的元素的值。交换之后的序列如下。
6 1 2 5 9 3 4 7 10 8
到此,第一次交换结束。
接下来开始哨兵j继续向左挪动(再友情提醒,每次必须是哨兵j先出发)。他发现了4(比基准数6要小,满足要求)之后停了下来。哨兵i也继续向右挪动的,他发现了9(比基准数6要大,满足要求)之后停了下来。



交换之后的序列如下。
到此第一轮“探测”真正结束。此时以基准数6为分界点,6左边的数都小于等于6,6右边的数都大于等于6。回顾一下刚才的过程,其实哨兵j的使命就是要找小于基准数的数,而哨兵i的使命就是要找大于基准数的数,直到i和j碰头为止。

快速排序之所比较快,因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全部放到基准点的右边。这样在每次交换的时候就不会像冒泡排序一样每次只能在相邻的数之间进行交换,交换的距离就大的多了。因此总的比较和交换次数就少了,速度自然就提高了。当然在最坏的情况下,仍可能是相邻的两个数进行了交换。因此快速排序的最差时间复杂度和冒泡排序是一样的都是O(N2),它的平均时间复杂度为O(NlogN)。其实快速排序是基于一种叫做“二分”的思想。我们后面还会遇到“二分”思想,到时候再聊。
代码:
1 /* 2 Author:Mengmeng 3 Time:2016-6-28 11:19:08 4 Decription: 5 使用快速排序法对输入的一串数字进行排序 6 快速排序法是一种基于二分法的思想 7 使用递归实现 8 */ 9 #include <iostream> 10 using namespace std; 11 void quickSort(int data[],int left, int right) 12 { 13 int sentry_left, sentry_right;//定义左哨兵和右哨兵 14 int base;//基准数 15 int temp;//临时变量 16 if (left > right)//递归结束条件 17 return; 18 base = data[left];//总是将左哨兵所对应的数作为基准数 19 sentry_left = left; 20 sentry_right = right; 21 while (sentry_left != sentry_right)//当左哨兵和右哨兵未相遇时 22 { 23 //顺序很重要,要从右边开始找 24 while (data[sentry_right]>=base && sentry_left < sentry_right)//直到找到一个小于基准数的值才停止while循环 25 sentry_right--; 26 27 //再找左边的 28 while (data[sentry_left] <= base && sentry_left < sentry_right)//直到找到一个大于基准数的值才停止while循环 29 sentry_left++; 30 31 //交换两个数在数组中的位置 32 if (sentry_left < sentry_right) 33 { 34 temp = data[sentry_left]; 35 data[sentry_left] = data[sentry_right]; 36 data[sentry_right] = temp; 37 } 38 39 } 40 //相遇后说明该轮无法再继续调整。然后将基准数归位 41 data[left] = data[sentry_left]; 42 data[sentry_left] = base; 43 44 quickSort(data, left, sentry_left - 1);//继续处理左边的,这是一个递归过程 45 /* 46 当sentry_left为数组第二个时,sentry_left-1变为数组第一个,此时因为sentry_left-1和left相同, 47 即此时基准数和左哨兵对应的数相同,说明序列只有一个数,无需调整,直接归位 48 执行完这一次后,sentry_left-1变为比left更小的数,此时应该结束,所以递归条件设置为(left>right) 49 */ 50 quickSort(data, sentry_right + 1, right);//继续处理右边的,这是一个递归过程 51 /* 52 当sentry_right为数组的倒数第二个时,则sentry_right+1变为最后一个,此时因为sentry_right+1和right相同, 53 即此时基准数和右哨兵对应的数相同,说明序列只有一个数,无需调整,直接归位 54 执行完这一次后,sentry_right+1变为比right更大的数,此时应该结束,所以递归条件设置为(left>right) 55 */ 56 57 } 58 int main(void) 59 { 60 int len;//要排序数组的长度 61 //读入长度 62 cout << "要排序数组的长度为:" << endl; 63 cin >> len; 64 int* data = (int*)malloc((len)*(sizeof(int)));//动态分配大小为len的数组 65 memset(data, 0, (len)*sizeof(int));//初始化数组的值为0,否则会出错 66 //读入数据 67 cout << "请输入" << len << "个数据:" << endl; 68 for (int i = 0; i < len; i++) 69 cin >> data[i]; 70 71 //开始排序 72 quickSort(data, 0, len - 1);//数组下标从0开始,到len-1结束 73 74 //输出排序后的结果 75 cout << "快速排序后的结果:" << endl; 76 for (int i = 0; i < len; i++) 77 cout << data[i] << " "; 78 return 0; 79 80 }
运行结果: