思想:将两个或多个已经有序的子序列合并为一个整体有序的序列
一.概念引入
1.二路归并
(由小到大)
准备三个指针和一个空表
int i, j, k;
int n = 12;
int *b = (int*)malloc(n * sizeof(int));
对比i和j元素大小, 选择小的放入新表。此处选择7,j,k指针后移
对比当前i和j所指元素的大小,将10放入,j,k后移
直到两个相同的24,任意选一个放入新表,这里选i指向的
再将另一个24放入新表
33放入,37放入
j超出下标范围,将i及其后面的元素依次放入新表
二路归并每次对比一次关键字就能选择出一个最小的元素放入正确位置
2.四路归并
p1和p2选出来一个小的,和p3比,选出来小的,和p4比,通过3次比较即可选出最小
因此四路归并比较3次
因此m路归并,每选出一个元素需要对比关键字m-1次
二.基于二路归并的归并排序
(一)基本步骤
初始将每个元素都看成是一个已经有序的部分,两两归并
即
再将新的几个部分两两归并
再将新的几个部分两两归并
(二)代码实现
定义指针low,high,mid
初始low指向0号位,high指向6号位,由主函数传值
mid = (low + high) / 2 (默认向下取整)
递归的对左右两个子表再分割
void MergeSort(int a[], int low, int high)
{
if (low < high)
{
MergeSort(a, low, mid);//a为图中的A[]数组
MergeSort(a, mid + 1, high);
}
}
对左子表的分割如下
low指向0,high指向3,mid指向1
low指向0,high指向1,mid指向0
low指向0,high指向0,不满足low<high,完成分割
此时每一个元素是一个小的部分,将low、mid、high传入Merge(a, low, mid, high)进行排序
Merge函数:将每个部分按顺序复制到新表,然后按照二路归并的原理移动指针,依次选择更小的元素放回原数组。
可以看出,Merge函数是对两个已经有序的序列进行归并,下面举例说明代码过程
Merge函数接收从MergeSort传入的a, low, mid, high,对两部分归并
创建新表(数组)
int n = 7;
int* b = (int*)malloc(n * sizeof(int));
设置指针与复制元素
int i, j, k;
for (int k = low; k <= high; k++)
b[k]=a[k];
指针赋值
i = low, j = mid + 1, k = i
注:因新表是两个表按顺序复制来的,为保证稳定性,两个24需先选择左边的(i指向的)24放回原表
排序结果
运行条件(直到某一方结束)
i <= mid && j <= high
比较与放回原表
if (b[i] <= b[j])
a[k] = b[i++];
else
a[k] = b[j++];
剩余元素处理
while (i <= mid) a[k++] = b[i++];//i部分剩余
while (j <= high) a[k++] = b[j++];//j部分剩余
完整代码
#include<stdint.h>
#include<iostream>
using namespace std;
int n = 5;
int *b = (int*)malloc(n * sizeof(int));
void Merge(int a[],int low,int mid,int high)
{
int i, j, k;
for (int k = low; k <= high; k++)
b[k]=a[k];
for (i = low, j = mid + 1, k = i; i <= mid && j <= high; k++)
{
if (b[i] <= b[j]) a[k] = b[i++];
else a[k] = b[j++];
}
while (i <= mid) a[k++] = b[i++];
while (j <= high) a[k++] = b[j++];
}
void MergeSort(int a[], int low, int high)
{
if (low < high)
{
int mid = (low + high) / 2;
MergeSort(a, low, mid);
MergeSort(a, mid + 1, high);
Merge(a, low, mid, high);
}
}
int main()
{
int a[5] = { 431,264,46,822,9645 };
MergeSort(a, 0, 4);
for (int i = 0; i < 5; i++)
{
cout << a[i] << " ";
}
}
三.效率分析
1.整个过程可以看成是一棵倒着的树,设树高是h,一共归并了h-1趟。第h层最多
2
h
−
1
2^{h-1}
2h−1个结点,初始序列作为树的最深层,元素个数n一定≤
2
h
−
1
2^{h-1}
2h−1,因此h-1≥
l
o
g
2
n
log_2n
log2n。当结点数为
2
h
−
1
2^{h-1}
2h−1时,h-1=⌈
l
o
g
2
n
log_2n
log2n⌉=归并排序的趟数。每一趟的归并中,每对比一次都可选择一个元素放到正确位置,因此最多对比关键字n-1次,时间复杂度为O(n)。因此总的时间复杂度为O(n
l
o
g
2
n
log_2n
log2n)
2.空间复杂度=递归调用栈(=h-1=归并趟数)+辅助数组=O(
l
o
g
2
n
log_2n
log2n)+O(n)=O(n)
3.稳定性:当出现关键字相等时选择i指向的元素优先放回,因此是稳定的
四.总结