归并排序(Merge_Sort)

基本思想

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

算法原理

 归并操作指的是将两个已经排序的序列合并成一个序列的操作,归并操作步骤如下:

申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列

设定两个指针,最初位置分别为两个已经排序序列的起始位置

比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置

重复步骤3直到某一指针到达序列尾

将另一序列剩下的所有元素直接复制到合并序列尾

特点

 数据结构:数组

 稳定性:稳定

过程

https://blog.csdn.net/weixin_43272781/article/details/85013461          

    排序过程            宏观过程

复杂度与辅助空间

 最差时间复杂度:O(nlogn)

 最优时间复杂度:O(nlogn)

 平均时间复杂度:O(nlogn)

 所需辅助空间:O(n)

源程序

//将有二个有序数列a[first...mid]和a[mid...last]合并。  
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];  
    if (p == NULL)  
        return false;  
    mergesort(a, 0, n - 1, p);  
    delete[] p;  
    return true;  
}  

一、圆周率π计算 

  1.  
    /*
  2.  
    *@Author: STZG
  3.  
    *@Language: C++
  4.  
    */
  5.  
    #include <bits/stdc++.h>
  6.  
    using namespace std;
  7.  
    long a=10000,b,c=56000,d,e,f[56001],g;
  8.  
    int main(){
  9.  
    for(;b-c ; )f[b++]=a/5;
  10.  
    for(;d=0,g=c*2;c-=14,printf("%.4d",e+d/a),e=d%a)
  11.  
    for(b=c;d+=f[b]*a,f[b]=d%--g,d/=g--,--b;d*=b);
  12.  
    //cout << "Hello world!" << endl;
  13.  
    return 0;
  14.  
    }

二、数学公式

π = 2 + 1/3 * (2 + 2/5 * (2 + 3/7 * (2 + ...  (2 + k/2k+1 * (2 + ... ))...)))

三、分析

  1.  
    #include <stdio.h>
  2.  
    int a=10000,b,c=2800,d,e,f[2801],g;
  3.  
    main() {
  4.  
    int i;
  5.  
    for(i=0;i<c;i++)
  6.  
         f[i]=a/5;
  7.  
    while(c!=0)
  8.  
         {
  9.  
             d=0;
  10.  
             g=c*2;
  11.  
             b=c;
  12.  
             while(1)
  13.  
                {
  14.  
                    d=d+f[b]*a;
  15.  
                    g--;
  16.  
                    f[b]=d%g;
  17.  
                    d=d/g;
  18.  
                    g--;
  19.  
                    b--;
  20.  
                    if(b==0break;
  21.  
                    d=d*b;
  22.  
                }
  23.  
             c=c-14;
  24.  
             printf("%.4d",e+d/a);
  25.  
             e=d%a;
  26.  
        }
  27.  
    }

要想计算出无限精度的PI,我们需要上述的迭代公式运行无数次,并且其中每个分数也是完全精确的,这在计算机中自然是无法实现的。那么基本实现思想就是迭代足够多次,并且每个分数也足够精确,这样就能够计算出PI的前n位来。上面这个程序计算800位,迭代公式一共迭代2800次。

int a=10000,b,c=2800,d,e,f[2801],g;


这句话中的2800就是迭代次数。


由于float或者double的精度远远不够,因此程序中使用整数类型(实际是长整型),分段运算(每次计算4位)。我们可以看到输出语句printf("%.4d",e+d/a); 其中%.4就是把计算出来的4位输出,我们看到c每次减少14( c=c-14;),而c的初始大小为2800,因此一共就分了200段运算,并且每次输出4位,所以一共输出了800位。

由于使用整型数运算,因此有必要乘上一个系数,在这个程序中系数为1000,也就是说,公式如下:

1000*pi = 2K+ 1/3 * (2K+ 2/5 * (2K+ 3/7 * (2K+ ... (2K+ k/2k+1 * (2K+ ... ))...)))

这里的2K表示2000,也就是f[2801]数组初始化以后的数据,a=10000,a/5=2000,所以下面的程序把f中的每个元素都赋值为2000:
for(i=0;i<c;i++)
f[i]=a/5;

你可能会觉得奇怪,为什么这里要把一个常数储存到数组中去,请继续往下看。我们先来跟踪一下程序的运行:

  1.  
    while(c!=0)  //假设这是第一次运行,c=2800,为迭代次数
  2.  
         {
  3.  
             d=0;
  4.  
             g=c*2;  //这里的g是用来做k/(2k+1)中的分子
  5.  
             b=c;  //这里的b是用来做k/(2k+1)中的分子
  6.  
             while(1)
  7.  
             while(1)
  8.  
                {
  9.  
                    d=d+f[b]*a;  //f中的所有的值都为2000,这里在计算时又把系数扩大了a=10000倍。这样做的目的稍候介绍,你可以看到输出的时候是d/a,所以这不影响计算
  10.  
                    g--;
  11.  
                    f[b]=d%g;  //先不管这一行
  12.  
                    d=d/g;  //第一次运行的g为2*2799+1,你可以看到g做了分母
  13.  
                    g--;
  14.  
                    b--;
  15.  
                    if(b==0break;
  16.  
                    d=d*b;  //这里的b为2799,可以看到b做了分子。
  17.  
                }
  18.  
             c=c-14;
  19.  
             printf("%.4d",e+d/a);
  20.  
             e=d%a;
  21.  
        }
  22.  
     


只需要粗略的看看上面的程序,我们就大概知道它的确是使用的那个迭代公式来计算Pi的了,不过不知道到现在为止你是否明白了f数组的用处。如果没有明白,请继续阅读。
d=d/g,这一行的目的是除以2k+1,我们知道之所以程序无法精确计算的原因就是这个除法。即使用浮点数,答案也是不够精确的,因此直接用来计算800位的Pi是不可能的。那么不精确的成分在哪里?很明显:就是那个余数d%g。程序用f数组把这个误差储存起来,在下次计算的时候使用。现在你也应该知道为什么d=d+f[b]*a;中间需要乘上a了吧。把分子扩大之后,才好把误差精确的算出来。
d如果不乘10000这个系数,则其值为2000,那么运行d=d/g;则是2000/(2*2799+1),这种整数的除法答案为0,根本无法迭代下去了。
现在我们知道程序就是把余数储存起来,作为下次迭代的时候的参数,那么为什么这么做就可以使得下次迭代出来的结果为接下来的数字呢?
这实际上和我们在纸上作除法很类似:

   0142
  /———
 7/ 1
   10
    7
——————
    30
    28
——————
    20
    14
——————
     6
.....

我们可以发现,在做除法的时候,我们通常把余数扩大之后再来计算,f中既然储存的是余数,而f[b]*a;则正好把这个余数扩大了a倍,然后如此循环下去,可以计算到任意精度。
这里要说明的是,事实上每次计算出来的d并不一定只有4位数,例如第一次计算的时候,d的值为31415926,输出4位时候,把低四位的值储存在e中间,e=d%a,也就是5926。

最后,这个c=c-14不太好理解。事实上没有这条语句,程序计算出来的仍然正确。只是因为如果迭代2800次,无论分数如何精确,最后Pi的精度只能够达到800。
你可以把程序改为如下形式尝试一下:

  1.  
    for(i=0;i<800;i++)
  2.  
         {
  3.  
         {
  4.  
             d=0;
  5.  
             g=c*2;
  6.  
             b=c;
  7.  
             while(1)
  8.  
                {
  9.  
                    d=d+f[b]*a;
  10.  
                    g--;
  11.  
                    f[b]=d%g;
  12.  
                    d=d/g;
  13.  
                    g--;
  14.  
                    b--;
  15.  
                    if(b==0break;
  16.  
                    d=d*b;
  17.  
                }
  18.  
           //c=c-14;  //不要这句话。
  19.  
             printf("%.4d",e+d/a);
  20.  
             e=d%a;
  21.  
        }

最后的答案仍然正确。
不过我们可以看到内循环的次数是c次,也就是说每次迭代计算c次。而每次计算后续位数的时候,迭代次数减少14,而不影响精度。

原文地址:https://www.cnblogs.com/DWVictor/p/10282775.html