在C ++中将多个排序序列合并为一个排序序列的算法

时间:2014-02-26 23:07:32

标签: c++ algorithm merge mergesort sorted

我正在寻找一种算法来合并多个排序的序列,让我们说X排序的序列有n个元素,在c ++中用一个排序的序列,你能提供一些例子吗?

注意:我不想使用任何库

5 个答案:

答案 0 :(得分:12)

有三种方法可以进行合并: -

假设您正在将m listsn elements each

合并

算法1: -

合并列表一次两个。使用合并排序(如合并例程)进行合并,因为列表已排序。没有任何库,这很容易实现。但如果m不大,则需要时间O(m^2*n),这足够小。

算法2: -

这是对1的改进,其中我们总是合并列表,这是剩余列表中最小的两个。使用priority queue执行此操作并选择最小的两个列表并合并它们并将新列表添加到队列。这样做直到只留下一个列表,这将是你的答案。此技术用于huffman coding并生成optimal merge pattern。这需要O(m*n*logm)。此外,对于类似大小的列表,可以将其设为parallel,因为我们可以选择一对列表并并行合并。假设您有m processors,那么理想情况下算法可以在O(n*logm)而不是O(m*n*logm)中运行

算法3: -

这是最有效的算法,其中为所有列表的第一个元素维护priority queue并提取min以获取新元素也维护列表min元素所属的索引,以便您可以添加下一个元素名单。这需要O(s*logm),其中s是所有列表中的总元素。

答案 1 :(得分:5)

假设

以下方法适用于任何容器,如数组,向量,列表等。我假设我们正在使用列表。

假设我们有m个排序列表,我们要合并。

n表示所有列表中元素的总数。

结果列表中的第一个元素必须是列表中所有头部集合中的最小元素。

这个想法非常简单。只需选择最小的头并将其从原始列表移动到结果。您希望在至少有一个非空列表时重复该例程。 这里至关重要的是快速选择最小的头部。

如果m很小

头部的线性扫描O(m),导致O(m * n)总时间,如果m是一个小常数,那就没问题。

如果m不是那么小

然后我们可以通过使用优先级队列来做得更好,例如。这里的不变量是堆中的最小元素始终是当前头中的最小元素。

查找最小元素是一个堆O(1),如果堆中有O(log m)个元素,则删除最小值为m,并且将一个元素插入堆中也是{{} 1}}。

总之,对于每个O(log m)元素,我们将其插入堆中一次并从那里删除一次。堆的总复杂度为n,如果O(n log m)不是一个小常数,则明显快于O(n * m)

摘要

哪种方法更快取决于我们要合并的列表数量。如果m较小,则选择线性扫描,而在另一种情况下,使用优先级队列实施。有时很难判断m是否很小,在这种情况下,某些实验会有所帮助。

答案 2 :(得分:1)

我假设没有merger的库。否则,您必须编写自己的linked list(这可能是转发,或normal list)。休息一样。简单示例(两个列表):

#include <list>
#include <iostream>

using namespace std;

int main(void)
 {
  list<int> a = { 1, 3, 5, 7, 9}, b = { 2, 4 , 6, 8, 9, 10}, c; //c is out
  for(auto it1 = begin(a), it2 = begin(b); it1 != end(a) || it2 != end(b);)
   if(it1 != end(a) && (it2 == end(b) || *it1 < *it2)) {
      c.push_back(*it1);
      ++it1;
    }
   else {
     c.push_back(*it2);
     ++it2;
    }
  for(auto x : c)
   cout<<x<<' ';
  cout<<'\n';
 }

结果:

  

1 2 3 4 5 6 7 8 9 9 10

注意!您必须使用标志-std = c ++ 11(或其他c ++ 11)进行编译。例如:

  

g ++ -std = c ++ 11 -Wall -pedantic -Wextra -O2 d.cpp -o program.out

复杂性:Θ(n)

记忆:Θ(n)

不难看出,每个元素在 O(1)中只被评估一次,我们有n个元素,所以它是Θ(n)

内存复杂性显而易见。值得一提的是,如果不再需要这两个列表,则可以在没有额外分配的情况下完成(常量内存)。

算法本身已经described这么多次以至于不能再写一次了。

在主要问题中,我们有很多序列,但想法是一样的。在这里你有丰富的例子:

int main(void)
 {
  vector<vector<int> > in{{ 1, 3, 5, 7, 9}, { 2, 4 , 6, 8, 9, 10}, {2,5,7,12,10,11,18}};
  vector<int> out;
  typedef tuple<int, vector<int>::iterator, vector<int>::iterator> element;
  priority_queue<element, vector<element>, greater<element> >  least;
  for(auto& x : in) //Adding iterators to the beginning of (no empty) lists
   if(!x.empty())   //and other parts of the element (value and end of vector)
    least.emplace(x.front(),begin(x),end(x));

  while(!least.empty()) {            //Solving
    auto temp = least.top(); least.pop();
    out.push_back(get<0>(temp));     //Add the smallest at the end of out
    ++get<1>(temp);
    if(get<1>(temp) != get<2>(temp)){//If this is not the end
      get<0>(temp) = *get<1>(temp);
      least.push(temp);              //Update queue
     }
   }

  for(const auto& x : out) //Print solution
   cout<<x<<' ';
  cout<<'\n';
 }

复杂性:Θ(n log k)

记忆:Θ(n)

弹出和插入操作在 O(log k),我们执行n次,所以它是 O(n log k)

记忆仍然很明显,我们总是在priority_queue中有k个元素,在out序列中总是有 O(n)

答案 3 :(得分:0)

这个代码可能类似于基于指针和计数的合并排序,首先为每个序列创建指针和计数的“源”数组,然后分配第二个“目标”数组来合并“源”数组指针和计数。该算法的每次传递根据从“源”数组到“目标”数组的序列合并指针和计数对,从而将阵列中的条目数减少约1/2。然后交换指向“源”和“目标”数组的指针,并重复合并过程,直到指针和计数数组只有一个条目。

答案 4 :(得分:-1)

C ++标准库包含std::merge

std::vector<int> v1 { 1,2,5,7 }, 
                 v2 { 3,6,9 }, 
                 out;

std::merge(v1.begin(), v1.end(), 
           v2.begin(), v2.end(), 
           std::back_inserter(out));

http://en.cppreference.com/w/cpp/algorithm/merge