文章目录
- 一、归并排序的基本思想及递归实现
- 二、归并排序的非递归实现
一、归并排序的基本思想
归并排序算法是用分治策略实现对n个元素进行排序地算法。其基本思想是:将待排序元素分为大致相等的两个子集合,分别对两个子集合进行排序,最终将排好序的子集合合并为要求的集合。
12 | 18 | 23 | 34 | 45 | 25 | 48 | 67 | 78 | 85 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
如图,我们用left,mid,right分别表示数组第一个,中间的以及最后一个元素,数组从left到mid从小到大有序,mid+1到right从小到大有序,我们只需把其合并到另一个缓冲区中从小到大有序就完成了。思路和实现非常简单,代码如下:
void Merge(int* dest, int* src, int left, int mid, int right)
{
int i = left, j = mid + 1;
int k = left;
while (i <= mid && j <= right)
{
dest[k++] = src[i] < src[j] ? src[i++] : src[j++];
}
while (i <= mid)
{
dest[k++] = src[i++];
}
while (j <= right)
{
dest[k++] = src[j++];
}
}
那么问题来了,如果子集合中数组无序怎么办呢?这时就要用到分治策略了。
56 | 23 | 78 | 45 | 90 | 89 | 12 | 34 | 89 | 67 | 100 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
如图,我们发现分割后的数组依然无序,满足不了合并的条件,那好我们按照上面方法接着分,不断减小问题规模,最坏情况无非是分成一堆就剩一个元素的数组,那你总该有序了,这时我们在合并就完事了。代码实现如下:
void Merge(int* dest, int* src, int left, int mid, int right)
{
int i = left, j = mid + 1;
int k = left;
while (i <= mid && j <= right)
{
dest[k++] = src[i] < src[j] ? src[i++] : src[j++];
}
while (i <= mid)
{
dest[k++] = src[i++];
}
while (j <= right)
{
dest[k++] = src[j++];
}
}
void Copy(int* dest, int* src, int left, int right)
{
for (int i = left; i <= right; ++i)
{
dest[i] = src[i];
}
}
void MergePass(int* tmp, int* nums, int left, int right)
{
if (left < right)
{
int mid = (left + right) / 2;
MergePass(tmp, nums, left, mid);
MergePass(tmp, nums, mid + 1, right);
Merge(tmp, nums, left, mid, right);
Copy(nums, tmp, left, right);
}
}
void MergeSort(int* nums, int n)
{
if (nullptr == nums || n < 2) return;
int* tmp = new int[n];
MergePass(tmp, nums, 0, n - 1);
delete []tmp;
}
int main()
{
int ar[] = { 56,23,78,45,90,89,12,34,89,67,100 };
int n = sizeof(ar) / sizeof(ar[0]);
MergeSort(ar, n);
for (auto x : ar)
{
cout << x << " ";
}
cout << endl;
return 0;
}
运行结果:
二、归并排序的非递归实现
通过上一篇快速排序的博客我们知道(快速排序算法_阿强O.o的博客-CSDN博客),非递归快排我们是借助栈或队列来实现的,那么这种方法是否适用于这里的非递归归并呢?很明显是不行的,因为我们存储是先存储0到n-1,然后再存储left到mid,mid+1到right,和快排不同,这里数组不一定有序,不能直接取出,需要不断进行存储,实现起来太麻烦。
s=1
56 | 23 | 78 | 45 | 90 | 89 | 12 | 34 | 89 | 67 | 100 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
s为我们每次需要合并的个数,当s=1时,我们调动合并函数void Merge(int* dest, int* src, int left, int mid, int right)。第一次传入参数left=0,mid=0,right=1,第二次传入参数left=2,mid=2,right=3,第三次传入参数left=4,mid=4,right=5,第四次传入参数left=6,mid=6,right=7,第五次传入参数left=8,mid=8,right=9。可以看出,10号数据没有元素与其进行合并,这是因为我们元素的个数并不是2的幂次方,最后只需将其拷贝下来就行。然后让s+=s,直到s大于数组长度,说明数组已排序完成。
void MergeSort(int* nums, int n)
{
int* tmp = new int[n];
int s = 1;
while (s < n)
{
MergePass(tmp, nums, n, s);
s += s;
MergePass(nums, tmp, n, s);
s += s;
}
delete[]tmp;
}
s=2
56 | 23 | 78 | 45 | 90 | 89 | 12 | 34 | 89 | 67 | 100 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
以s=2为例,s为每一次合并块的大小,所以i每次走的都是2倍的s的长度,每次调用合并函数(即上面的Merge函数)传入的mid值即为i+s-1,right为i+2*s-1。当我们最后一次调用Merge函数时,理想中的right为8+2*2-1=11,超出了数组长度,故退出。
再接着,最后一次合并,我们并不能确定是像s=1那样没有数据与剩余元素合并,还是像s=2一样,是两个数据与一个数据进行合并。所以我们需要进行判断,数组末尾元素是否超出一个合并块的大小(即一个s),如果没有超出,我们只需拷贝即可,如果超出了,那么我们还要在进行一次合并。
void MergePass(int* dest, int* src, int n, int s)
{
int i = 0;
while (i + 2 * s - 1 <= n - 1)
{
Merge(dest, src, i, i + s - 1, i + 2 * s - 1);
i = i + 2 * s;
}
if (n - 1 >= i + s)
{
Merge(dest, src, i, i + s - 1, n - 1);
}
else
{
for (int j = i; j < n; ++j)
{
dest[j] = src[j];
}
}
}
s=4
56 | 23 | 78 | 45 | 90 | 89 | 12 | 34 | 89 | 67 | 100 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
s=8
56 | 23 | 78 | 45 | 90 | 89 | 12 | 34 | 89 | 67 | 100 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
最终代码如下:
void Merge(int* dest, int* src, int left, int mid, int right)
{
int i = left, j = mid + 1;
int k = left;
while (i <= mid && j <= right)
{
dest[k++] = src[i] < src[j] ? src[i++] : src[j++];
}
while (i <= mid)
{
dest[k++] = src[i++];
}
while (j <= right)
{
dest[k++] = src[j++];
}
}
void MergePass(int* dest, int* src, int n, int s)
{
int i = 0;
while (i + 2 * s - 1 <= n - 1)
{
Merge(dest, src, i, i + s - 1, i + 2 * s - 1);
i = i + 2 * s;
}
if (n - 1 >= i + s)
{
Merge(dest, src, i, i + s - 1, n - 1);
}
else
{
for (int j = i; j < n; ++j)
{
dest[j] = src[j];
}
}
}
void MergeSort(int* nums, int n)
{
int* tmp = new int[n];
int s = 1;
while (s < n)
{
MergePass(tmp, nums, n, s);
s += s;
MergePass(nums, tmp, n, s);
s += s;
}
delete[]tmp;
}
感谢观看!