赞
踩
归并排序的核心思想:如果要排序一个数组,我们先把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分合并在一起。
归并排序使用的是分治思想,即将一个大问题分解成小的子问题来解决。因此,这里我们用递归代码来实现归并排序。
归并排序递推公式:
merge_sort(p…r) = merge(merge_sort(p…q), merge_sort(q+1…r))
终止条件:
p >= r 不用再继续分解
merge_sort(p…r) 表示,给下标从 p 到 r 之间的数组排序。我们将这个排序问题转化为了两个子问题,merge_sort(p…q) 和 merge_sort(q+1…r),其中下标 q 等于 p 和 r 的中间位置,也就是 (p+r)/2。当下标从 p 到 q 和从 q+1 到 r 这两个子数组都排好序之后,我们再将两个有序的子数组合并在一起,这样下标从 p 到 r 之间的数据就也排好序了。
归并代码:
#include<stdio.h> #include<stdlib.h> #include<time.h> #define N 100 void merge_sort(int a[], int n); void merge_sort_c(int a[], int p, int r); void merge(int a[], int p, int q, int r); void print(int* a, int n); int main() { srand((unsigned)(time(NULL))); // 添加随机种子 int a[N]; for (int i = 0; i < N; ++i) { a[i] = rand() % 100; } printf("数组a未进行归并排序前:"); print(a, N); printf("\n数组a进行归并排序后:"); merge_sort(a, N); print(a, N); return 0; } void merge_sort(int a[], int n) { merge_sort_c(a, 0, n - 1); } // 递归排序 void merge_sort_c(int a[], int p, int r) { // 递归终止条件 if (p >= r) return ; // 取p到r之间的中间位置q int q = (p + r) / 2; // 分治递归 merge_sort_c(a, p, q); merge_sort_c(a, q + 1, r); // 将a[p...q]和a[q + 1...r]合并为a[p...r] merge(a, p, q, r); // merge函数只负责将已经排好序的两个数组组合成一个新的数组 } void merge(int a[], int p, int q, int r) { int i = p, j = q + 1, k = 0; // 初始化变量i,j,k // 申请一个跟a[p...r]一样大小的临时数组 int* tmp = (int*)malloc(sizeof(a[0]) * N); while (i <= q && j <= r) { if (a[i] <= a[j]) { // 这里体现了归并排序算法的稳定性 // 即经过排序后,值相同元素,先后顺序不变 tmp[k++] = a[i++]; } else { tmp[k++] = a[j++]; } } // 判断哪个子数组中有剩余的数据 int start = i, end = q; if (j <= r) { start = j; end = r; } // 将剩余的数组拷贝到临时数组tmp while (start <= end) { tmp[k++] = a[start++]; } // 将tmp中的数组拷贝回a[p...r] for (i = 0; i <= r - p; i++) { a[p + i] = tmp[i]; } free(tmp); } void print(int* a, int n) { for (int i = 0; i < n; ++i) { printf("%d ", a[i]); } }
运行结果:
根据递归求解问题的思想:一个问题a可以分解为多个问题 b 和 c,求解问题 a 就可以分解为求解问题 b 和 c,把 b,c 解决之后,我们再把 b、c 的结果合并成 a 的结果。
我们定义求解问题a的时间为T(a),求解问题 b、c 的时间为T(b)和T©,于是可以得到如下的递推公式:
T(a) = T(b) + T(c) + K
假设归并排序需要的时间为T(n),分解成两个子数组排序的时间都是T(n/2),又因为 merge() 函数合并两个有序数组的时间复杂度是 O(n),所以套用上面的公式,归并排序的时间复杂度计算公式就是:
T(1) = C; n=1时,只需要常量级的执行时间,所以表示为C。
T(n) = 2*T(n/2) + n; n>1
进一步分析:
T(n) = 2*T(n/2) + n
= 2*(2*T(n/4) + n/2) + n = 4*T(n/4) + 2*n
= 4*(2*T(n/8) + n/4) + 2*n = 8*T(n/8) + 3*n
= 8*(2*T(n/16) + n/8) + 3*n = 16*T(n/16) + 4*n
......
= 2^k * T(n/2^k) + k * n
......
通过这样一步一步分解推导,我们可以得到 T(n) = 2^k * T(n/2^k) + k * n。当T(n/2^k) = T(1)时,也就是 n/2^k=1,我们得到 k=log2n 。我们将 k 值代入上面的公式,得到 T(n)=Cn+nlog2n 。如果我们用大 O 标记法来表示的话,T(n) 就等于 O(nlogn)。所以归并排序的时间复杂度是 O(nlogn)。
T(n/2^k)中 n/2^k 表示分解后的数据规模,2^k 中k表示把数据规模分解到1时的分解次数,故当算法完成,数据规模分解到1,此时n/2^k=1
递归代码的空间复杂度并不能像时间复杂度那样增加,尽管每次的合并操作都需要申请额外的内存空间,但是这个临时内存空间最大不会超过n个数据规模的大小,所以空间复杂度是O(n)。
快速排序的主要思想是:如果要排序数组中 p 到 r 之间的一组数据,我们选择 p 到 r 之间的任意一个数据作为 pivot(分区点)。遍历 p 到 r 之间的数据,将小于 pivot 的放到左边,将大于 pivot 的放到右边,将 pivot 放到中间。根据分治、递归的处理思想,用递归排序下标从 p 到 q-1 之间的数据和下标从 q+1 到 r 之间的数据,当区间缩小为 1时即排序完成。
递推公式:
quick_sort(p…r) = quick_sort(p…q-1) + quick_sort(q+1… r)
终止条件:
p >= r
快排代码:
#include<stdio.h> #include<stdlib.h> #define N 100 // 此处分区函数的实现使得快速排序占用更少的内存空间 int partition(int* a, int p, int r) { int pivot = a[r]; int i = p; for (int j = p; j < r; ++j) { if (a[j] < pivot) { int tmp1 = a[j]; a[j] = a[i]; a[i] = tmp1; i++; } } int tmp2 = a[r]; a[r] = a[i]; a[i] = tmp2; return i; } // 快速排序递归函数,p,r为下标 void quick_sort_c(int* a, int p, int r) { if (p >= r) return; int q = partition(a, p, r); // 获取分区点 quick_sort_c(a, p, q - 1); quick_sort_c(a, q + 1, r); } void quick_sort(int* a, int n) { quick_sort_c(a, 0, n - 1); } void print(int* a, int n) { for (int i = 0; i < n; ++i) { printf("%d ", a[i]); } } int main() { srand((unsigned)(time(NULL))); // 添加随机种子 int a[N]; for (int i = 0; i < N; ++i) { a[i] = rand() % 100; } printf("数组a未进行快速排序前:"); print(a, N); printf("\n数组a进行快速排序后:"); quick_sort(a, N); print(a, N); return 0; }
运行结果:
由于快速排序过程中有数据的交换,所以快速排序不是一个稳定的排序算法。
快速排序在大部分情况下时间复杂度都能可以达到O(nlogn),只有在极端情况下才会退化为O(n^2)
如果数组中的数据原来已经是有序的了,比如 1,3,5,6,8。如果我们每次选择最后一个元素作为 pivot,那每次分区得到的两个区间都是不均等的。我们需要进行大约 n 次分区操作,才能完成快排的整个过程。每次分区我们平均要扫描大约 n/2 个元素,这种情况下,快排的时间复杂度就从 O(nlogn) 退化成了 O(n2)。
由于partition()函数只涉及元素的交换,所以快排的空间复杂度为O(1)。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。