分治(Divide and Conquer)的思想,平均、最好、最坏,时间效率都是O(n log n)。
有递归和迭代两种实现方法。对于顺序存储如数组,一般为O(n)的空间;list的空间直接为O(1)。
空间上可以使用原地归并避免O(n)的空间。但较麻烦,且需要多次移动,除非有特殊说明或空间非常宝贵,否则不建议原地归并。
void merge_sort(vector<int>& arr){
if(arr.empty()) return;
vector<int> ans(arr.size());
merge_sort_re(arr, ans, 0, arr.size());//1.递归
//merge_sort_it(arr, arr.size());//2.迭代
}
1.递归
void merge_sort_re(vector<int>& arr, vector<int>& ans, int beg, int end){
if(end - beg <= 1)return;
int mid = beg + (end-beg)/2;
merge_sort_re(arr, ans, beg, mid);
merge_sort_re(arr, ans, mid, end);
merge(arr, ans, beg, end);//a.归并, O(n)的空间
//merge_inplace(arr, beg, end);//b.原地归并, O(1)的空间
}
a.归并, O(n)的空间
void merge(vector<int>& arr, vector<int>& ans, int beg, int end){
int mid = beg + (end-beg)/2;
int i = beg, j = mid, k = beg;
while(i<mid && j<end) ans[k++] = arr[i]>arr[j] ? arr[j++] : arr[i++];
while(i<mid) ans[k++] = arr[i++];
while(j<end) ans[k++] = arr[j++];
for(k = beg; k<end; ++k) arr[k] = ans[k];
}
b. 原地归并, O(1)的空间
merge_inplace(arr, beg, end),归并[beg,end)范围内的数。其中,[beg,mid)、[mid,end)分别有序。
算法流程:
(0)变量i、j初值分别为beg、mid = beg + (end-beg)/2。
(1)首先自增i,在arr前半部找到第一个比arr[j]大的数,为arr[i];
中间变量k保存此时j的值;
(2)然后自增j,在arr后半部找到第一个比arr[i]大的数,为arr[j];
(3)此时,arr[i]前的数不需要处理;arr[x] (x的范围为[i,k) )中的全部数(记为A_x)大于arr[y](y的范围为[k,j) )中的全部数(记为A_y);记移动前,t = arr[j],现在只需要通过 move(arr, i, k, j) 将A_y 中的全部数移至A_x中的全部数前,即可保证arr在[beg, end)范围内,移动后数t所在的位置之前(不包括数t所在的位置)的全部数都有序;
(3)更新i的值为移动后arr[i]所在位置,即i自增区间[k,j)的长度 i+=j-k,重新执行(1)~(3),直到i、j满足循环退出条件。
时间分析:最坏O(n^2),因此除非有特殊说明或空间非常宝贵,否则不建议原地归并。
//将arr中[k,j)范围内的数移至[i,k)范围前
void move(vector<int>& arr, int i, int k, int j){
reverse(arr.begin()+i, arr.begin()+k);
reverse(arr.begin()+k, arr.begin()+j);
reverse(arr.begin()+i, arr.begin()+j);
}
//原地归并, O(1)的空间
void merge_inplace(vector<int>& arr, int beg, int end){
int mid = beg + (end-beg)/2;
int i = beg, j = mid;
while(i<j && j<end){
int k = j;
while(i<j && arr[i]<=arr[j]) ++i;
while(j<end && arr[j]<=arr[i]) ++j;
move(arr, i, k, j);
i+=j-k;
}
}
reference: blog
2.迭代
void merge_sort_it(vector<int>& arr, int len){
vector<int> ans(len);
for(int seg = 1; seg < len; seg+=seg){
for(int beg = 0; beg < len; beg+=seg+seg){
int low = beg, mid = std::min(beg+seg, len), high = std::min(beg+seg+seg, len);
int i = low, j = mid, k = low;
while(i<mid && j < high) ans[k++] = arr[i]>arr[j] ? arr[j++] : arr[i++];
while(i<mid) ans[k++] = arr[i++];
while(j<high) ans[k++] = arr[j++];
for(k = beg; k < high; ++k) arr[k] = ans[k];
}
}
}
reference: wiki