C++抽象编程——算法分析(4)——合并排序算法

来源:互联网 发布:sql外键级联删除 编辑:程序博客网 时间:2024/06/05 15:19

然而,对大型vectory要求的排序,选择排序算法显然不符合任务,因为运行时间与输入大小的平方成比例增加,对于以线性顺序处理向量的元素的大多数排序算法也是如此。 所以要采用定性不同的方法来开发更好的排序算法。

强大的分治法

奇怪的是,找到更好的排序策略的关键在于认识到选择排序算法的二次行为具有隐藏的好处。二次复杂度的基本特征是,随着问题的大小加倍,运行时间增加了四倍。 然而,相反的情况也是如此。 如果将二次问题的大小除以2,则可以将运行时间减少相同的四倍。 这个事实表明,将向量除以一半,然后应用递归的方法可能会减少所需的排序时间。
为了使这个想法更具体,假设你有一个大的vector需要排序。如果将向量分成两半,然后使用选择排序算法对这些片段进行排序,会发生什么? 因为选择排序是的复杂度是二次的,每个较小的vector需要原始时间的四分之一。 当然,你需要对这两半进行排序,但是排序两个较小vector所需的总时间仍然是排序原始vector所需的时间的一半。如果分开一个vector的两半可以简化整个向量排序的问题,你将能够大大减少总时间。更重要的是,一旦发现如何在一个级别提高性能,就可以使用相同的算法递归地对每一个进行排序。
为了确定分治法是否适用于这个排序问题,你需要回答一个问题,即将vector分为两个较小的vector,然后对每个vector进行排序是否有助于解决一般问题。假设你从一个包含以下八个元素的vector开始:

如果你将8个元素的vector划分为长度为4的两个vector,然后对每个较小的vector进行排序,请记住,leap of faith意味着我们可以假设递归调用正常工作,所以会得到以下情况: 每个较小的vector被排序:

这种分解有多有用? 记住,你的目标是从这些较小的vector中取出值,并将它们以正确的顺序放回到原始vector中。如何使这些较小排序的矢量来帮助我们实现这一目标?

合并两个vector

正如它发生的那样,从较小的排序vector重组成完整的vector比排序本身要简单得多。这种技术我们称为合并(merging)。即完整排序中的第一个元素必须是v1中的第一个元素或v2中的第一个元素,以较小者为准。 在这个例子中,你要在新组成的的vector中的第一个元素是第二个v2的第一个元素。将该元素添加到空的向量vec,此时我们把v2的19叉掉,表示已经取出,我们下图的结果:

再一次次,下一个元素只能是两个较小向量之一中的第一个未取出的元素。你可以比较v1中的25与v2中的30,并选择前者:

您可以轻松地继续重复此过程,从v1或v2中选择较小的值,直到重构整个vector。

合并排序算法(The merge sort algorithm)

合并操作与递归分解相结合,产生了一种称为合并排序的新的排序算法,可以直接实现。 算法的基本思想可以概括如下:

  1. 检查vector是否为空或只有一个元素。如果是这样,它肯定已经被排序。此条件用于定义递归的simple case。
  2. 将向量分成两个较小的向量,每个向量的大小是前者的一半(意味着,不是值分成两个vector,而是每个分开的vector还可以继续分,重复这个过程
  3. 递归地对每个较小的vector进行排序。
  4. 清除原始的vector,使其再次为空。(用来储存新的排序好的数字
  5. 将两个排序好的vector合并回原来的vector。

合并算法的代码如下:

#include <iostream>#include <vector>using namespace std;/*函数原型*/ void sort(vector<int> & vec);void merge(vector<int> & vec,vector<int> & v1,vector<int> & v2);/*主函数*/ int main(){    vector<int> vec;    for(int i = 0; i < 8; i++){        int n;         cin >> n;        vec.push_back(n);     }    sort(vec);    for(int k = 0; k < vec.size(); k++){        cout << vec[k] << " ";    }    return 0;}/* *函数:sort *用法:sort(vec) *--------------- *该函数使用合并排序算法对向量的元素进行升序排序,包括以下步骤: *1.将vector分成两半 *2.递归地对每个较小的vector进行排序 *3.将两个排序好的vector合并回原来的vector。 */ void sort(vector<int> & vec){    int n = vec.size();    if(n <= 1) return; //当n<=1 的时候,为simple case,直接返回     vector<int> v1,v2; //将vector分成两半,然后分别装进v1,v2中     for(int i = 0; i < n; i++){        if(i < n/2){            v1.push_back(vec[i]);        }else{            v2.push_back(vec[i]);        }    }    //递归调用     sort(v1);     sort(v2);    //清除vec     vec.clear();    //合并     merge(vec,v1,v2);}/* *函数:merge *用法:merge(vec,v1,v2); *------------------------ *此函数将两个排序的向量v1和v2合并到向量vec中, *该向量vec在此操作之前应为空。因为输入向量已经被排序。  *所以函数可以总是在其中一个输入向量中选择第一个未使用的元素来填充下一个位置。  */ void merge(vector<int> & vec,vector<int> & v1,vector<int> & v2){    int n1 = v1.size();     int n2 = v2.size();    int p1 = 0;    int p2 = 0;    /*下面为什么是v1[p1++],而不是v1[p1]?自行补习i++的特性*/     while(p1 < n1 && p2 < n2){        if(v1[p1] < v2[p2]){            vec.push_back(v1[p1++]);        }else{            vec.push_back(v2[p2++]);        }    }    /*     *当上面的代码执行完后,如果还有vector的大小为偶数,那么肯定有一个vector     *比较长,合并后剩下还有元素未合并进去原始的vector(至多一个元素),所以我们     *还要添加下面的判断,针对它所处的不同的位置采取不同的操作     */     while(p1 < n1){        vec.push_back(v1[p1++]);    }    while(p2 < n2){        vec.push_back(v2[p2++]);    }}

测试的结果如下:

合并排序算法的代码整齐地分为两个函数:排序和合并。 排序代码直接来自算法的轮廓。在检查特殊情况后,算法将原始vector分为两个较小的v1和v2。一旦sort代码将所有元素复制到v1或v2中,v1,V2就已经被创建,其余的函数会递归地排序这些vector,最后清除原始vector,然后调用merge来重新组合vector,从而实现合并排序。
大部分的工作是通过合并函数完成的,该函数采用目标向量vec,以及较小的向量v1和v2。指标p1和p2标记跟踪每一个vector的下标。 在循环的每个循环中,该函数从v1或v2选择一个元素取较小者,并将该值添加到vec的结尾。一旦两个较小的vector中的任何一个的元素被取尽,该函数可以简单地从另一个vector中直接复制元素而不用麻烦来测试它们。实际上,因为这些向量中的其中一个已经在第一个while循环退出时已经耗尽,所以该函数可以简单地将每个向量的其余部分复制到vec。 这些向量之一将为空,因此相应的while循环将完全不执行。

这里还是忍不住提示一下,v1[p1++],其实我们都知道 i++返回的 i的值是自增1的。但是这个运算符返回的是自增前的值。也就是说比如 i = 2,执行

i++;

之后,就是 i = 3,但是 (i++)这个整体的值就还是 2(可以写个程序试试)。
所以说v1[p1++]这句代码等价于:

v1[p1];p1 ++;

下一篇的文章我们就去分析一下这个算法的复杂度。

0 0
原创粉丝点击