合并排序和快速排序的平均时间复杂度都是O(nlogn)的原因:它们都采用了分治策略,将问题分解成子问题并递归地求解。合并排序算法将待排序序列不断划分成两个子序列,直到每个子序列只有一个元素;快速排序算法每次分割操作将数组分成两个长度较为相等的部分。
一、合并排序和快速排序的平均时间复杂度都是O(nlogn)的原因
合并排序和快速排序的平均时间复杂度都是 O(nlogn),是因为它们都采用了分治策略,将问题分解成子问题并递归地求解。
合并排序
当我们计算合并排序的时间复杂度时,需要考虑拆分和合并这两部分。在每一次拆分中,我们将输入数组划分成两个子数组,其中每个子数组的长度为n/2,因此我们需要做log(n)轮拆分。每一轮拆分的时间复杂度为O(n),因此平均时间复杂度为O(nlogn)。
快速排序
快速排序算法中每次分割操作将数组分成了两个长度相等的部分,因此需要做log(n)次分割。在每个分割操作中,需要扫描数组一次找到最终位置的关键值,并将其与数组中的其他值分成两个部分。因此,每个分割操作的时间复杂度为O(n),平均时间复杂度为O(nlogn)。
二、合并排序简介
合并排序是采用分治策略实现对N个元素进行排序的算法,是分治法的一个典型应用和完美体现,它是一种平衡,简单的二分分治策略,计算过程分为三步:
- 分解:将待排序元素分成大小大致相同的两个子序列。
- 求解子问题:用合并排序法分别对两个子序列递归地进行排序。
- 合并:将排好序的有序子序列进行合并,得到符合要求的有序序列。
算法步骤
- 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
- 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
- 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
- 重复步骤 3 直到某一指针达到序列尾;
- 将另一序列剩下的所有元素直接复制到合并序列尾。
代码
int min(int x, int y) {
return x < y ? x : y;
}
void merge_sort(int arr[], int len) {
int* a = arr;
int* b = (int*) malloc(len * sizeof(int));
int seg, start;
for (seg = 1; seg < len; seg += seg) {
for (start = 0; start < len; start += seg + seg) {
int low = start, mid = min(start + seg, len), high = min(start + seg + seg, len);
int k = low;
int start1 = low, end1 = mid;
int start2 = mid, end2 = high;
while (start1 < end1 && start2 < end2)
b[k++] = a[start1] < a[start2] ? a[start1++] : a[start2++];
while (start1 < end1)
b[k++] = a[start1++];
while (start2 < end2)
b[k++] = a[start2++];
}
int* temp = a;
a = b;
b = temp;
}
if (a != arr) {
int i;
for (i = 0; i < len; i++)
b[i] = a[i];
b = a;
}
free(b);
}
三、快速排序简介
快速排序的基本思想是随机找出一个数,可以随机取,也可以取固定位置,一般是取第一个或最后一个称为基准,然后就是比基准小的放在左边,比基准大的放到右边。如何放呢?就是和基准进行交换,这样交换完左边都是比基准小的,右边都是比较基准大的,这样就将一个数组分成了两个子数组,然后再按照同样的方法把子数组再分成更小的子数组,直到不能分解为止。快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。
算法步骤
- 从数列中挑出一个元素,称为 “基准”(pivot)。
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
代码
typedef struct _Range {
int start, end;
} Range;
Range new_Range(int s, int e) {
Range r;
r.start = s;
r.end = e;
return r;
}
void swap(int *x, int *y) {
int t = *x;
*x = *y;
*y = t;
}
void quick_sort(int arr[], const int len) {
if (len <= 0)
return; //避免len等于负值时错误
//r[]模拟堆疊,p为数量,r[p++]为push,r[--p]为pop且取得元素
Range r[len];
int p = 0;
r[p++] = new_Range(0, len - 1);
while (p) {
Range range = r[--p];
if (range.start >= range.end)
continue;
int mid = arr[range.end];
int left = range.start, right = range.end - 1;
while (left < right) {
while (arr[left] < mid && left < right)
left++;
while (arr[right] >= mid && left < right)
right--;
swap(&arr[left], &arr[right]);
}
if (arr[left] >= arr[range.end])
swap(&arr[left], &arr[range.end]);
else
left++;
r[p++] = new_Range(range.start, left - 1);
r[p++] = new_Range(left + 1, range.end);
}
}
延伸阅读
选择排序算法简介
选择排序(Select Sort)是直观的排序,通过确定一个 Key 最大或最小值,再从带排序的的数中找出最大或最小的交换到对应位置。再选择次之。双重循环时间复杂度为 O(n^2)。算法描述为:
- 在一个长度为 N 的无序数组中,第一次遍历 n-1 个数找到最小的和第一个数交换。
- 第二次从下一个数开始遍历 n-2 个数,找到最小的数和第二个数交换。
- 重复以上操作直到第 n-1 次遍历最小的数和第 n-1 个数交换,排序完成。
文章标题:为什么合并排序和快速排序的平均时间复杂度都是O(nlogn),发布者:Z, ZLW,转载请注明出处:https://worktile.com/kb/p/49352