归并排序(分治法)

借鉴---白话经典算法系列之五 归并排序的实现

和这篇博客https://www.cnblogs.com/chengxiao/p/6194356.html

这里我们分两块来看,先看第一块。

1.  这篇博客的图很好,有助于理解。

图解排序算法(四)之归并排序

基本思想

  归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。

分而治之

可以看到这种结构很像一棵完全二叉树,本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。

合并相邻有序子序列

  再来看看阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤。

相信到这里大家都明白这个流程是怎样的了,接下来看下下一篇博客。

 

2.  讲的通俗易懂,更有利于加深印象,而且这个代码挺好记的。

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可

//将有序数组a[]和b[]合并到c[]中  
void MemeryArray(int a[], int n, int b[], int m, int c[])  
{  
    int i, j, k;  
  
    i = j = k = 0;  
    while (i < n && j < m)  
    {  
        if (a[i] < b[j])  
            c[k++] = a[i++];  
        else  
            c[k++] = b[j++];   
    }  
  
    while (i < n)  
        c[k++] = a[i++];  
  
    while (j < m)  
        c[k++] = b[j++];  
}

可以看出合并有序数列的效率是比较高的,可以达到O(n)。

解决了上面的合并有序数列问题,再来看归并排序,其的基本思路就是将数组分成二组A,B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。如何让这二组组内数据有序了?

可以将A,B组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。

#include<bits/stdc++.h>
using namespace std;
void mergearray(int a[],int first,int mid,int last,int temp[])	//将两个有序数组合并排序 
{
	int i=first,j=mid+1;
	int m=mid,n=last;
	int k=0;
	while(i<=m&&j<=n)
	{
		if(a[i]<a[j])
			temp[k++]=a[i++];
		else
			temp[k++]=a[j++];
	}
	while(i<=m)
		temp[k++]=a[i++];
	while(j<=n)
		temp[k++]=a[j++];
	for(i=0;i<k;i++)
		a[first+i]=temp[i];
}

void mergesort(int a[],int first,int last,int temp[])	//将两个任意数组合并排序 
{
	if(first<last)
	{
		int mid=(first+last)/2;
		mergesort(a,first,mid,temp);	//左边有序 
		mergesort(a,mid+1,last,temp);	//右边有序 
		mergearray(a,first,mid,last,temp);	//再将两个有序数组合并 
	}
}

int main()
{
	int a[1005];
	int temp[1005];
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++)
		scanf("%d",&a[i]);
	mergesort(a,0,n-1,temp);
	for(int i=0;i<n;i++)
		printf("%d ",a[i]);
}

归并排序的效率是比较高的,设数列长为N,将数列分开成小数列一共要logN步,每步都是一个合并有序数列的过程,时间复杂度可以记为O(N),故一共为O(N*logN)。因为归并排序每次都是在相邻的数据中进行操作,所以归并排序在O(N*logN)的几种排序方法(快速排序,归并排序,希尔排序,堆排序)也是效率比较高的。

 

在本人电脑上对冒泡排序,直接插入排序,归并排序及直接使用系统的qsort()进行比较(均在Release版本下)

对20000个随机数据进行测试

1339667485_3225.jpg

对50000个随机数据进行测试:

1339667579_6014.jpg

再对200000个随机数据进行测试:

1339667519_5952.jpg

 

注:有的书上是在mergearray()合并有序数列时分配临时数组,但是过多的new操作会非常费时。因此作了下小小的变化。只在MergeSort()中new一个临时数组,后面的操作都共用这一个临时数组。

优化后的代码为:

#include<bits/stdc++.h>
using namespace std;
void mergearray(int a[],int first,int mid,int last,int temp[])	//将两个有序数组合并排序 
{
	int i=first,j=mid+1;
	int m=mid,n=last;
	int k=0;
	while(i<=m&&j<=n)
	{
		if(a[i]<a[j])
			temp[k++]=a[i++];
		else
			temp[k++]=a[j++];
	}
	while(i<=m)
		temp[k++]=a[i++];
	while(j<=n)
		temp[k++]=a[j++];
	for(i=0;i<k;i++)
		a[first+i]=temp[i];
}
 
void mergesort(int a[],int first,int last,int temp[])	//将两个任意数组合并排序 
{
	if(first<last)
	{
		int mid=(first+last)/2;
		mergesort(a,first,mid,temp);	//左边有序 
		mergesort(a,mid+1,last,temp);	//右边有序 
		mergearray(a,first,mid,last,temp);	//再将两个有序数组合并 
	}
}
 
bool MergeSort(int a[], int n)  
{  
    int *p = new int[n];  //分配一个有n个int型元素的数组所占空间,并将该数组的第一个元素的地址赋给int *型指针p。
    if (p == NULL)  
        return false;  
    mergesort(a, 0, n - 1, p);  
    delete[] p;  
    return true;  
} 
 
int main()
{
	int a[1005];
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++)
		scanf("%d",&a[i]);
	MergeSort(a,n);
	for(int i=0;i<n;i++)
		printf("%d ",a[i]);
} 

 

©️2020 CSDN 皮肤主题: 点我我会动 设计师:上身试试 返回首页