数据结构 快速排序
思想
对于一般的排序算法,复杂度几乎都是o(n^2),排序算法需要遍历一遍整个数组,时间复杂度是从o(n)开始的,这一遍遍历几乎无法优化,优化的办法在于嵌套的第二层遍历。
快速排序由C. A. R.Hoare在1962年提出,它是一种基于二叉树结构的交换排序算法,它采用了一种分治的策略,通常称其为分治法。
该方法的基本思想是:先从数列中取出一个数作为基准数,然后将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边,
再对左右区间重复第二步,直到各区间只有一个数。快速排序是一种不稳定的排序方法,但是他的效率非常快,比肩希尔和堆排序也略胜一筹。
快速排序是一种分而治之的思想,借助一个基准,将数组的所有数据拆分成2组,拆分的2组数据个数如果相同,那么对这两个小组的排序时间复杂度就会变成o(n/2),
反之拆分2组数据个数是1:n-1,那么对这两个小组排序的时间复杂度就是o(n-1),接近于o(n)。由此可知快速排序的时间复杂度跟基数的选取有莫大关系。
源码
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <time.h> #define GTC_ARRAY_SIZE 10 static void build(int* arr, int num); static void show(int* arr, int num); static void quick_sort(int* arr, int num); void test() { // 定义数组 int arr[GTC_ARRAY_SIZE]; // 构造随机数组元素 build(arr, GTC_ARRAY_SIZE); // 打印数组信息 show(arr, GTC_ARRAY_SIZE); // 快速排序 quick_sort(arr, GTC_ARRAY_SIZE); // 打印数组信息 show(arr, GTC_ARRAY_SIZE); } int main(int argc,char*argv[]) { test(); return 0; } static void show(int* arr, int num) { int i = 0; for (i = 0; i < num; i++) { printf("%5d", *(arr + i)); } printf("\n"); } static void build(int* arr, int num) { time_t ts; int i; srand((unsigned int)time(&ts)); for (i = 0; i < 10; i++) { arr[i] = (int)(rand() % 100); } } static void quick_sort(int* arr, int num) { int pivot, i, last, idx; int ele; if (num < 2) { return; } idx = 0; last = num - 1; pivot = arr[last]; // 遍历时,刨除最后一个元素 for (i = 0; i < last; i++) { if (arr[i] <= pivot) { // 交换位置 ele = arr[idx]; arr[idx] = arr[i]; arr[i] = ele; idx++; } } // 更新基准值 ele = arr[idx]; arr[idx] = pivot; arr[last] = ele; quick_sort(arr, idx); quick_sort(arr + idx + 1, num - idx - 1); }
关于快速排序,首先是按照一个基准值,将数组中的元素拆分成两部分
为了节省内存,打算使用同一块内存来完成该功能
基准值为啥需要放在两部分中间位置?
如果基准值不移动,假如选中的基准值本来就是最小的(或者最大的),就会陷入死循环
对于基准值的选择
a.基准值选择下标0
当基准值使用arr[0]时,arr[0]的值是无效的,最终进行基准值替换时,使用大于基准值的数据替换arr[0]是有问题的
b.基准值选择中间下标
问题同上
c.基准值选择末尾
顺序遍历,那么无效值位置一直在末尾,移动元素的过程中也不会涉及该元素,
分组完成后,做替换操作将会很容易
static void quick_sort(int* arr, int num) { int i, j, pivot; int temp; int first, last; first = 0; last = num - 1; if (first >= last) { return; } pivot = first; i = first; j = last; while (i < j) { // arr[pivot]的位置实际上不会发生变化 while (arr[i] <= arr[pivot] && i < last) i++; while (arr[j] > arr[pivot]) j--; if (i < j) { temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } // 至此,arr[j]是小于或者等于arr[pivot] // 这一步交换位置才是精华 temp = arr[pivot]; arr[pivot] = arr[j]; arr[j] = temp; quick_sort(arr, j); quick_sort(arr + j + 1, num - j - 1); }
对于基准值的选择
a.基准值选择下标0
这里只能选择arr[0],因为选择arr[0]才能保证pivot是固定且唯一的,选择其他位置pivot都可能发生变化
a.基准值选择末尾
这里选择末尾,那么每一轮排序实际上就是选择出最大的元素作为基准值,跟冒泡排序没有区别