【问题标题】:Merging K- Sorted Lists using Priority Queue使用优先队列合并 K 排序列表
【发布时间】:2013-10-28 18:55:20
【问题描述】:

我在我的算法课上被要求制作一个 K 路合并算法,它是O(nlogk) 搜索后我发现它可以通过制作一个 k 长度优先级队列并将其与每个列表的第一个元素排队来完成。提取最小值,将其附加到结果并从已提取元素的列表中入队。 我很困惑:

  1. 它如何知道特定列表何时用尽,假设一个 列表中的元素比其他列表中的所有其他元素都小?

  2. 它如何知道元素属于哪个列表(如果不使用结构来定义)?

  3. O(nlogk)的时间复杂度如何?

编辑:

如果有人可以逐步写下算法会更有帮助,因为我读过的都是句子,很难理解它的方式,如果有人可以写下算法可能会有所帮助理解。

【问题讨论】:

  • i) 您的代码可以检查列表是否已用尽,也可以尝试删除元素但失败 ii) 与元素的来源无关。如果您在意,可以在将每个元素插入堆时用一些列表标识符包装每个元素 iii) 因为有 n 元素,并且每个元素都插入/从 k-size 堆中删除。算法中的所有其他工作都被这些步骤“吸收”
  • 您也可以在没有堆的情况下进行 K 路合并。我在这里解释一下:stackoverflow.com/a/18984961/56778。我还没有进行性能测试来确定哪种方法(有或没有堆)更快。两者都是 O(n log k),但一个可能比另一个做的工作多一点。

标签: arrays algorithm merge


【解决方案1】:

当没有更多元素时,1 个列表已用尽

2 您需要跟踪最小元素 com 来自哪个列表

将每个元素放入一个大小为 k 的最小堆中,该堆需要 logk,因此您有 n 倍 logk

【讨论】:

  • 是的,这就是我要问的。当提取数组的最后一个元素时,我们如何检查是否没有更多元素。我们如何跟踪 min 元素的来源。您能否详细说明第三点?如果我理解正确的话。从 k 大小的堆中提取所有元素是 log k,我们反复推送 n 个元素(从所有列表中),所以它需要 n*logk?
【解决方案2】:

这里有一些用于合并的 Python 2 代码。

import heapq

def addtoheap(h, i, it):
    try:
        heapq.heappush(h, (next(it), i))
    except StopIteration:
        pass

def mergek(*lists):
    its = map(iter, lists)
    h = []
    for i, it in enumerate(its):
        addtoheap(h, i, it)
    while h:
        v, i = heapq.heappop(h)
        addtoheap(h, i, its[i])
        yield v

for x in mergek([1, 3, 5], [2, 4, 6], [7, 8, 9], [10]):
    print x

为什么是 O(n log k)?那么对于每个删除的值,都有一个堆弹出和可能的堆推送(两者都是 O(log k))。由于我们删除了 n 个元素,所以它是 O(n log k)。

【讨论】:

【解决方案3】:

不要简单地将每个列表的第一个元素存储在优先级队列中,而是将其包装在这样的结构中;

struct wrapper
{
    int list_number;
    int element;
}

然后,当您将元素推入优先级队列时,只需将列表编号形式添加到它所在的位置。这样,当最小元素被弹出时,您将通过检查 popped_element.list_number 知道应该从哪个列表中推送下一个要推送到队列中的元素。

为了确定您的列表是否为空,您应该添加一个函数empty,如果列表没有更多元素则返回true,否则返回false。该功能将很容易实现。只需检查大小是否为零然后列表为空,否则它有一个或多个元素。

根据您的问题,我假设使用二进制堆来实现优先级队列。二叉堆中的插入操作需要O(lg k) 时间,而extract-min 操作也需要O(lg k) 时间,其中k 是堆的大小(在您的情况下为列表数)。现在,如果您拥有的元素总数为n,则处理所有元素的总时间将为O(n lg k)

【讨论】:

    【解决方案4】:

    几年前,在讨论对大型文本文件进行排序时,我写了一系列关于此的文章。这个想法是你放在堆上的项目不仅包含值,还包含值来自的列表。或者,您可以将列表引用放在堆上,并让比较函数与特定列表中的第一项进行比较。

    请参阅http://www.informit.com/guides/content.aspx?g=dotnet&seqNum=676http://www.informit.com/guides/content.aspx?g=dotnet&seqNum=677,了解使用顺序列表代替堆的基本算法的说明。有关使用堆的改进版本,请参阅 http://www.informit.com/guides/content.aspx?g=dotnet&seqNum=680

    正如我在评论中所说,您也可以在没有堆的情况下进行合并。有关说明,请参阅 https://stackoverflow.com/a/18984961/56778

    【讨论】:

      【解决方案5】:

      您不应该在这里表​​示所有列表中的“n”节点总数,而不仅仅是一个列表。在这种情况下,解决方案是 O(n logk)。如果我们的意思是我们在每个列表中平均有 n 个节点(总共 k 个列表),那么它将是 O(nk logk)

      Here 是带有一些代码的深入解释

      【讨论】:

        【解决方案6】:

        Paul Hankin 的解决方案是正确的,但它有点难以阅读,尤其是您想在 c++ 或 java 中实现。我的解决方案类似于保罗的。如果你用 c++ 或 java 编写,你可能需要一个额外的数据结构来存储元素的值、第 k 个数组中元素的索引和列表中数组的索引。

        Element{
            int value;
            int idInArray,
            int idInList
        }
        

        但在 python 中,我只是存储在一个元组中 (value, idInArray, idInList)

        def mergeKArray(*lists):
            # implemented by min heap
            h = []
            r = []
            for k, arr in enumerate(lists):
                heapq.heappush(h, (arr[0], 0, k))
            while h:
                # min is the minimum element
                # i is the index of the min in the k-th array
                # k is the index of array in the list
                min, i, k = heapq.heappop(h)
                r.append(min)
                if i < len(lists[k]) - 1:
                    i += 1
                    heapq.heappush(h, (lists[k][i], i, k))
            return r
        

        因为我只需要维护一个包含 k 个元素的最小堆,所以弹出或注入堆的时间是 O(log k)。我还需要扫描所有 n 个元素,每个元素花费 2*log(k) 时间来注入和弹出。因此,大 O 为 O(n*log k)。

        【讨论】:

          【解决方案7】:

          这是我使用 c++ stl 的代码

          #include<iostream>
          #include<vector>
          #include<climits>
          #include<queue>
          #define ROWS 4
          #define COLS 8
          using namespace std;
          
          struct node
          {
              int ele;
              int arr_no;
              int next_index; 
          };
          
          void printVector(vector<int> v)
          {
              for(unsigned int i=0;i<v.size();i++)
                  cout<<v[i]<<" ";
          }
          
          // THIS IS THE BASIS ON WHICH THE ELEMENTS OF A PRIORITY QUEUE ARE SORTED AND 
          // KEPT IN THE QUEUE, HERE THE CRITERIA IS THAT THE NODE WITH SMALLER ELEMENT SHOULD
          // COME ABOVE THE ONE WITH LARGER ELEMENT
          
          class compare
          {
              public:
                  bool operator()(node& n1, node& n2)
                  {
                     if (n1.ele > n2.ele) 
                          return true;
                     else
                          return false;
                  }
          };
          
          vector<int> mergeKArrays(vector< vector<int> > v)
          {
              int k = v.size();       // NUMBER OF LISTS
              int n = v.at(0).size(); //SIZE OF EACH LIST
          
              vector<int> result;
              //result.resize( n*k );
          
              priority_queue<node, vector<node>, compare> minHeap;
              for (int i = 0; i < k; i++)
              {
                  node temp;
                  temp.ele = v[i][0]; //STORE THE FIRST ELEMENT
                  temp.arr_no = i;        //INDEX OF ARRAY
                  temp.next_index = 1;    //INDEX OF NEXT ELEMENT TO BE STORED FROM ARRAY
                  minHeap.push(temp);
              }
          
              // NOW ONE BY ONE GET THE MINIMUM ELEMENT FROM MIN
              // HEAP AND REPLACE IT WITH NEXT ELEMENT OF ITS ARRAY
              for (int count = 0; count < n*k; count++)
              {
                  // GET THE MINIMUM ELEMENT AND STORE IT IN OUTPUT
                  node min_ele_node = minHeap.top();
                  minHeap.pop();      
                  result.push_back(min_ele_node.ele);
          
                  // FIND THE NEXT ELELEMENT THAT WILL REPLACE CURRENT
                  // ROOT OF HEAP. THE NEXT ELEMENT BELONGS TO SAME
                  // ARRAY AS THE CURRENT ROOT.
                  node new_node;
                  new_node.arr_no = min_ele_node.arr_no;
                  if (min_ele_node.next_index < n)
                  {
                      new_node.ele = v.at(min_ele_node.arr_no)[min_ele_node.next_index];
                      new_node.next_index = min_ele_node.next_index + 1;
                  }
                  // IF ROOT WAS THE LAST ELEMENT OF ITS ARRAY
                  else 
                  {
                      new_node.ele =  INT_MAX; //INT_MAX IS FOR INFINITE
                  }
          
                  // REPLACE ROOT WITH NEXT ELEMENT OF ARRAY
                  minHeap.push(new_node);
              }
              return result;
          }
          
          
          int main()
          {
              int arr[ROWS][COLS] = 
                              { 
                                  {10, 20, 30, 40, 50, 60, 71, 86},
                                  {15, 25, 35, 45, 60, 69, 77, 78},
                                  {27, 29, 37, 48, 50, 65, 75, 78},
                                  {32, 33, 39, 50, 80, 133, 139, 150},
                              }; 
          
              vector< vector<int> > matrix ;
          
              for( int i=0 ; i < ROWS; i++)
              {
                  vector<int> vec;
                  for(int j=0; j < COLS; j++)
                      vec.push_back(arr[i][j]);
                  matrix.push_back(vec);
              }
          
              vector<int> result = mergeKArrays(matrix);
              printVector(result);
              return 0;
          }
          

          【讨论】:

            猜你喜欢
            • 2019-04-20
            • 1970-01-01
            • 1970-01-01
            • 2012-11-01
            • 1970-01-01
            • 1970-01-01
            • 2021-03-22
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多