归并排序
归并排序大体思路就是:将排序数组左半边排序,右半边排序,在将两部分进行merge的过程。其中左半边的排序,和右半边的排序和整体排序的套路一致,因此子过程递归,就可以完成
merge的过程
merge 的过程就是两个分别有序的数组,合成一个数组,合并的过程中,谁的元素小就把谁放到前面。不过这里不申请额外空间,会比较复杂。标准做法都是申请一个和原来数据等长的数组
void merge(vector<int> & p,int left,int mid,int right) {
vector<int> help(right - left + 1);
int i = 0;
int p1 = left;
int p2 = mid+1;
while(p1 <= mid && p2 <=right) {
help[i++] = p[p1] <= p[p2] ? p[p1++]:p[p2++];
}
while (p1 < mid) {
help[i++] = p[p1++];
}
while (p2 < right) {
help[i++] = p[p2++];
}
for(int j = 0;j<help.size();j++) {
p[left+j] = help[j]
}
}
由此完成merge的过程
递归版的归并排序的实现
void mergeSort(vector<int> & p,int left,int right) {
if (left == right) {
return;
}
int mid = left + (right-left) >> 1;
mergeSort(p,left,mid);
mergeSrot(p,mid+1,right);
merge(p,left,mid,right)
}
归并排序子过程结束两种情况
- left == right
- merge后
子过程结束,弹栈
归并排序非递归 – 手写栈实现
上述递归排序过程,并不是以子过程结束而结束,子过程(左子过程,右子过程)结束,并且merge完成之后才结束。因此手写的栈里面,除了写入左边界和右边界信息外,还需要写 左子过程的状态和右子过程的状态
void changePop(vector<int> & next) {
// 子过程结束(左子过程先结束,右子过程后结束),修改栈顶元素,
// next[2] 左子过程结束标识 next[3] 右子过程结束标识
if (next[2]) {
next[3] = true;
} else {
next[2] = true;
}
}
/**
* 大体思路 :
* 归并排序子过程有两种情况结束:
* 1)左边界和右边界相等
* 2)merge完之后,
* 子过程结束,弹栈。手动实现的话,还需要标识出是左半部分子过程还是右半部分子过程
* 参照归并排序的递归版实现,设计栈存储的元素为:左边界,右边界,左半部分子过程结束标志 右半部分子过程结束标志,
* 伪代码:
* if l == r:
* 弹栈;
* 修改新的栈顶元素(左有序标识变为true,如果左已经为true,修改右有序标识为true);
* continue;
* member = stack.top()
* if !member.leftFlag:
* // 左半部分无序
* stack.push(l,l+(r-l)>>2,false,false);
* continue;
* if !member.rightFlag():
* // 左半部分有序,右半部分无序
* stack.push(l,l+(r-l)>>2,false,false);
* continue;
* // 左半部分有序 右半部分有序
* merge(l,l+(r-l)>>2,r);
* 弹栈
* 修改新的栈顶元素(左有序标识变为true,如果左已经为true,修改右有序标识为true;
*/
void mergeSort2(vector<int> &p, int r) {
if (r <= 1) {
return;
}
stack<vector<int>> stack;
// 栈里面存储 左边界 右边界 左半部分是否完成 右半部分是否完成
stack.push({0, r, false, false});
while (!stack.empty()) {
vector<int> & member = stack.top();
// 如果只有一个元素,弹出栈,并修改新的栈顶元素的顺序标识,归并排序都是先排序左半部分,在排序右半部分,
if (member[0] == member[1]) {
// 子过程结束情况1
stack.pop();
changePop(stack.top());
continue;
}
if (!member[2]) {
// 左半部分无序
stack.push({member[0], member[0] + (member[1] - member[0]) / 2, false, false});
continue;
}
if (!member[3]) {
// 左有序 右无序
stack.push({member[0] + 1 + (member[1] - member[0]) / 2, member[1], false, false});
continue;
}
// 左右都有序 子过程结束情况2
merge(p, member[0], member[0] + (member[1] - member[0]) / 2, member[1]);
stack.pop();
if (!stack.empty()) {
changePop(stack.top());
}
}
}
由此就完成了一个非尾递归用栈操作的方式改成 非递归的形式
归并排序非递归 – 修改步长
/** mergeSort
* 非递归版:
* 一步是一个组,两个组之间merge
* 二步是一个组,两个组之间merge
* 四步是一个组,两个组之间merge
* 八步一个组,两个组之间merge
* 以此类推
*
**/
void mergeSort(vector<int> p) {
// 如果这里的边界为 i <= p.size(),也可以,只是会对一次merge的过程
for(int i=1;i<p.size();i*=2){
// 这个循环跳出前,i的值肯定>= p.size() / 2
// 循环的边界条件 j+i-1 < p.size() 是要合并的两个组,第一个组的最后一个元素可以取到
for(int j=0;j+i-1 < p.size();j+=i*2) {
int mid = j+i-1;
int right = j+2*i-1 < p.size() ? j+2*i-1:p.size() -1;
merge(p,j,mid,right);
}
}
}