您的位置:首页 > 其它

排序算法(六)、归并排序

2018-03-28 22:27 239 查看
1、二路归并排序
“归并”即“合并”,是指将两个或者两个以上有序表组合成一个有序表。假如待排序表含有 n 个记录,即可以视为 n 个有序的子表。每个子表长度为1,然后两两归并,得到 n/2 个长度为 2 或者 1 的有序表,然后,再两两归并,。。。。如此重复,直到合并成一个长度为 n 的有序表为止。这种排序方法称为“二路归并排序”。
递归形式的二路归并算法,主要包含两个步骤:
1)、分解:将长度为 n 的待排序表分解成两个 n/2 大小的子表,然后,递归地对两个子表进行排序。。
2)、合并:将子表合并。
(1)部分代码如下: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);

}
}(2)部分代码如下:// MergeSort 归并排序

void Merge(int a[], int low, int mid, int high)
{
int i = low, j = mid + 1, k = 0; // j 是从 mid + 1 开始的。
int *b = new(nothrow) int[high - low + 1];
if (!b)
{
cout << "分配失败" << endl;
return;
}

while (i <= mid && j <= high) // 分两路进行比较,把数据较小的放到中介数组
{
if (a[i] <= a[j])
b[k++] = a[i++];
else
b[k++] = a[j++];
}

while (i <= mid) // 若某个区间的数据仍然有剩余,就直接把剩余的数据复制到中介数组
b[k++] = a[i++];// 这两个循环只会执行其中一个。。因为肯定不会两边都有剩余
while (j <= high)
b[k++] = a[j++];

for (int i = low,k=0; i <= high; i++,k++) // 将 b 数组的元素复制到原数组
a[i] = b[k];

delete[]b;

}
测试:int main()
{
int a[] = { 1, 5, 3, 4, 12, 35, 21, 9 };
mergesort(a,0,7);

for (int i = 0; i < 8; i++)
cout << a[i] << endl;
return 0;
}对于非递归的形式,只需要更改 merge() 即可。void MergeSort(int arr[], int n)//n代表数组中元素个数,数组最大下标是n-1
{
int size = 1, low, mid, high;
while (size <= n - 1) // 使用步长来控制
{
low = 0;
while (low + size <= n - 1)
{
mid = low + size - 1;
high = mid + size;
if (high>n - 1)//第二个序列个数不足size
high = n - 1;
Merge(arr, low, mid, high);//调用归并子函数
//cout << "low:" << low << " mid:" << mid << " high:" << high << endl;//打印出每次归并的区间
low = high + 1;//下一次归并是第一关序列的下界
}
size *= 2;//范围扩大一倍
}
}举例说明一下:
// 非递归版本的 MergeSort
// 使用一个例子来解释这个排序,假设有 8 个数据,[48],[38],[65],[97],[76],[13],[27],[33],元素个数 n = 8
// 初始时,size = 1,此时,size <= 7,
// 第一次内循环时,即 low + size <= n - 1,因为每次 low 都会递增,所以要保证不越界
// 一开始,low = 0,mid = 0,high = 1,这可以看成是元素的下标。调用归并函数,将 [48],[38]变成 [38,48].
// low = high +1 = 2,mid = 2,high = 3,调用归并函数,将 [69],[57]变成 [57,69].
//low = high +1 = 4,mid = 4,high = 5,调用归并函数,将 [76],[13]变成 [13,76].
//low = high +1 = 6,mid = 6,high = 7,调用归并函数,将 [27],[33]变成 [27,33].,这样,序列变成了 [38,48],[57,69],[13,76],[27,33]

// 第二次,size = 2,size <=7
//一开始,low = 0,mid = 1,high = 3,[38,48],[57,69]变成了 [38,48,57,69]
//然后,low = 3+1 = 4, mid = 5, high = 7, [13,76],[27,33]变成了[13,27,33,76]

// 第三次,size = 4,size <=7
//一开始,low = 0,mid = 3,high = 7,这样 [38,48,57,69],[13,27,33,76] 就变成了 [13,27,33,38,48,57,69,76]

性能分析:
空间复杂度:上面的 merge () 函数需要申请辅助单元, n 个单元。但是,每一趟归并以后,这些空间就被释放了,所以时间复杂度为 O(n)
时间复杂度: 每一趟归并的复杂度为 O(n),总共进行 log2 (n) 趟归并。所以时间复杂度为 O(nlog2 (n))。

升级——原地归并排序
既然 merge() 函数需要辅助单元,那么,不使用额外的存储空间可以吗?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: