二路归并排序
归并排序是一种基于“归并”思想的排序方法。二路归并的原理是,将序列两两分组,将序列归并,组内单独排序,然后以此类推,直到只剩下一个组为止。
举例:序列{66,12,33,57,64,27,18}进行二路归并排序。
第一趟:两两分组,得到四组:{66,12},{33,57},{64,27},{18},组内单独排序得到{{12,66},{33,57},{27,64},{18}};
第二趟:四个组继续两两分组,得到两组:{12,66,33,57},{27,64,18},组内单独排序得到{{12,33,57,66},{18,27,64}};
第三趟:两个组继续两两分组得到一组:{12,33,57,66,18,27,64},组内单独排序得到{12,18,27,33,57,64,66},排序结束。
c++代码实现:
①递归
思想:反复将当前区间`[left,right]`分成两半,对两个子区间`[left,mid]` 和`[mid+1,right]`分别递归进行归并排序,然后将两个已经有序的子区间合并为有序序列即可。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100;
// A :[L1,R1] [L2,R2]
void merge(int A[], int L1, int R1, int L2, int R2) {
int i = L1, j = L2;
int temp[maxn], index = 0;
while (i <= R1 && j <= R2) {
if (A[i] <= A[j]) {
temp[index++] = A[i++];
} else {
temp[index++] = A[j++];
}
}
while (i <= R1) {
temp[index++] = A[i++];
}
while (j <= R2) {
temp[index++] = A[j++];
}
for (i = 0; i < index; i++) {
A[L1 + i] = temp[i];
}
}
void merge_sort(int A[], int left, int right) {
if (left < right) {
int mid = (left + mid) / 2;
merge_sort(A, left, mid);
merge_sort(A, mid + 1, right);
merge(A, left, mid, mid + 1, right);
}
}
②非递归
思想:我们观察到,每次分组时组内元素的个数上限是2的幂次。所以我们可以令步长step的初值为2,然后数组中每step个元素作为一组,将其内部进行排序(即把左`step/2`个元素与右`step/2`个元素合并,而若元素个数不超过`step/2`,则不操作);再令step乘2,重复上述操作,直到`step/2`超过元素个数n。
#include <bits/stdc++.h>
using namespace std;
void merge(int A[], int L1, int R1, int L2, int R2) {
int i = L1, j = L2;
int temp[maxn], index = 0;
while (i <= R1 && j <= R2) {
if (A[i] <= A[j]) {
temp[index++] = A[i++];
} else {
temp[index++] = A[j++];
}
}
while (i <= R1) {
temp[index++] = A[i++];
}
while (j <= R2) {
temp[index++] = A[j++];
}
for (i = 0; i < index; i++) {
A[L1 + i] = temp[i];
}
}
void merge_sort(int A[]) {
for (int step = 2; step / 2 <= n; step *= 2) {
for (int i = 1; i <= n; i += step) {
int mid = i + step / 2 - 1;
if (mid + 1 <= n) {
merge(A, i, mid, mid + 1, min(i + step - 1, n));
}
}
}
}
快速排序
对于序列A[1]、A[2]、...、A[n],调整序列中元素的位置,使得A[1]的左侧所有元素都不超过A[1],右侧所有的元素都大于A[1]。时间复杂度为O(nlogn)。
思想
①首先将A[1]存入一个临时变量temp,并令两个下标分别指向序列首尾,即left=1,right=n;
②只要right指向的元素A[right]大于temp,就将right不断左移,直到A[right]≤temp时,将元素A[right]挪到left指向的元素A[left]处;
③只要left指向的元素A[left]不超过temp,就将left不断右移,直到A[left]>temp时,将A[left]挪到right指向的元素A[right ]处;
④重复②③,直到left和right相遇,把temp放到相遇的地方。
举个例子说明:序列A={35,18,16,72,24,65,12,88,46,28,55}
首先取得A[1]=35,使得左边的所有元素不大于35,右边的所有元素都大于35。
①将A[1]=35存入临时变量temp,并令下标指向序列首尾(left=1,right=11)
temp 35
1 2 3 4 5 6 7 8 9 10 11
35 | 18 | 16 | 72 | 24 | 65 | 12 | 88 | 46 | 28 | 55 |
left right
②只要A[right]>35,就把right不断左移,直到right=10时满足A[right]=28<35,right停止左移,之后把A[right]移到A[left]处。
temp 35
1 2 3 4 5 6 7 8 9 10 11
28 | 18 | 16 | 72 | 24 | 65 | 12 | 88 | 46 | 55 |
left right
③只要A[left]≤35,就把left不断右移,直到left=4时满足A[left]=72>35,left停止右移,之后把A[left]移到A[right]处。
temp 35
1 2 3 4 5 6 7 8 9 10 11
28 | 18 | 16 | 24 | 65 | 12 | 88 | 46 | 72 | 55 |
left right
④只要A[right]>35,就把right不断左移,直到right=7时满足A[right]=12<35,right停止左移,之后把A[right]移到A[left]处。
temp 35
1 2 3 4 5 6 7 8 9 10 11
28 | 18 | 16 | 12 | 24 | 65 | 88 | 46 | 72 | 55 |
left right
⑤只要A[left]≤35,就把left不断右移,直到left=6时满足A[left]=65>35,left停止右移,之后把A[left]移到A[right]处。
temp 35
1 2 3 4 5 6 7 8 9 10 11
28 | 18 | 16 | 12 | 24 | 65 | 88 | 46 | 72 | 55 |
left right
⑥只要A[right]>35,就把right不断左移,left与right在A[6]处相遇,之后把temp=35移到A[6]处。
temp 35
1 2 3 4 5 6 7 8 9 10 11
28 | 18 | 16 | 12 | 24 | 35 | 65 | 88 | 46 | 72 | 55 |
left right
一次排序结束,随后对左边右边元素递归。
C++代码:
#include <bits/stdc++.h>
using namespace std;
int Partition(int A[], int left, int right) {
int temp = A[left];
while (left < right) {
while (left < right && A[right] > temp) {
right--;
}
A[left] = A[right];
while (left < right && A[left] > temp) {
left++;
}
A[right] = A[left];
}
A[left] = temp;
return left;
}
void quick_sort(int A[], int left, int right) {
if (left < right) {
int pos = Partition(A, left, right);
quick_sort(A, left, pos - 1);
quick_sort(A, pos + 1, right);
}
}
当然,需要考虑的问题是temp的选择,不能每次都选择第一个元素,这样当元素排列比较随机时效率较高,当元素有序时,会达到最坏时间复杂度O(n^2)。应该随机选择元素作为temp!!