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

时间:2017-01-14 22:25:45

标签: javascript arrays algorithm sorting

enter image description here我正在寻找一种合并多个排序序列的算法,让我们将带有n个元素的X排序序列放到javascript中的一个排序序列中,你能提供一些例子吗?

注意:我不想使用任何库。 试图解决https://icpc.kattis.com/problems/stacking

在条件下合并排序数组所需的最小操作数:

拆分通过抬起堆栈的任何顶部并将其放在一边形成新堆栈,可以将单个堆栈拆分为两个堆栈。

加入可以通过将一个堆叠放在另一个堆叠的顶部来加入两个堆栈。仅当顶部堆叠的底板不大于底部堆叠的顶板时才允许这样做,也就是说,必须正确地订购连接堆栈。

6 个答案:

答案 0 :(得分:6)

天真的方法是连接所有k序列,并对结果进行排序。但如果每个序列都有n个元素,则费用为O(k*n*log(k*n))。太多了!

相反,您可以使用优先级队列或堆。像这样:

var sorted = [];
var pq = new MinPriorityQueue(function(a, b) {
  return a.number < b.number;
});
var indices = new Array(k).fill(0);
for (var i=0; i<k; ++i) if (sequences[i].length > 0) {
  pq.insert({number: sequences[i][0], sequence: i});
}
while (!pq.empty()) {
  var min = pq.findAndDeleteMin();
  sorted.push(min.number);
  ++indices[min.sequence];
  if (indices[min.sequence] < sequences[i].length) pq.insert({
    number: sequences[i][indices[min.sequence]],
    sequence: min.sequence
  });
}

优先级队列最多只包含k个元素,每个序列一个。您将继续提取最小值,并在该序列中插入以下元素。

有了这个,费用将是:

  • k*n插入一堆k元素:O(k*n)
  • {li> k*n一堆k元素中的O(k*n*log(k))个删除:O(k*n)
  • 每个号码的各种常量操作:O(k*n*log(k))

所以只有{{1}}

答案 1 :(得分:6)

历史

这个问题已经解决了一个多世纪,回到了Hermann Hollerith和打牌。大量的信用卡,例如人口普查产生的信用卡,通过将它们分成批次,对每批次进行分类,然后合并分类的批次 - 所谓的分类进行分类。 "merge sort"。你在1950年的科幻电影中看到旋转的那些磁带驱动器很可能将多个分类的磁带合并到一个磁带上。

算法

您可以在https://en.wikipedia.org/wiki/Merge_algorithm找到所需的所有算法。用JS编写这个很简单。问题Algorithm for N-way merge中提供了更多信息。另请参阅this question,这几乎完全相同,但我不确定任何答案都非常好。

天真的连接和度假方法甚至没有资格作为问题的答案。有点天真的接下来最小值从任何输入方法要好得多,但不是最优的,因为找到下一个输入来获取值需要花费更多的时间。这就是使用称为“最小堆”或“优先级队列”的最佳解决方案的原因。

简单JS解决方案

这是一个真正的简单版本,除了能够看到它正在做什么之外,我没有声称要进行优化:

const data = [[1, 3, 5], [2, 4]];    

// Merge an array or pre-sorted arrays, based on the given sort criteria.
function merge(arrays, sortFunc) {
  let result = [], next;
   
  // Add an 'index' property to each array to keep track of where we are in it.
  arrays.forEach(array => array.index = 0);
 
  // Find the next array to pull from.
  // Just sort the list of arrays by their current value and take the first one.     
  function findNext() {
    return arrays.filter(array => array.index < array.length)
      .sort((a, b) => sortFunc(a[a.index], b[b.index]))[0];
  }

  // This is the heart of the algorithm.
  while (next = findNext()) result.push(next[next.index++]);

  return result;
}

function arithAscending(a, b) { return a - b; }

console.log(merge(data, arithAscending));

上面的代码在每个输入数组上维护一个index属性,以记住我们的位置。简单的替代方案是shift来自每个数组前面的元素,当轮到它被合并时,但这样效率会相当低。

优化查找要从

拉出的下一个数组

这个findNext的简单实现,为了找到从中拉出下一个值的数组,只需按第一个元素对输入列表进行排序,然后在结果中取出第一个数组。您可以使用"min-heap"按排序顺序管理数组来优化此操作,这样就无需每次都使用它们。 min-heap是一个树,由节点组成,其中每个节点包含一个值,该值是下面所有值的最小值,左右节点给出额外的(更大)值,依此类推。您可以找到有关最小堆here的JS实现的信息。

发电机解决方案

将它写成一个生成器可能会稍微清晰一点,它将一系列迭代作为输入,包括数组。

// Test data.
const data = [[1, 3, 5], [2, 4]];

// Merge an array or pre-sorted arrays, based on the given sort criteria.
function* merge(iterables, sortFunc) {
  let next;

  // Create iterators, with "result" property to hold most recent result.
  const iterators = iterables.map(iterable => {
    const iterator = iterable[Symbol.iterator]();
    iterator.result = iterator.next();
    return iterator;
  });

  // Find the next iterator whose value to use.
  function findNext() {
    return iterators
      .filter(iterator => !iterator.result.done)
      .reduce((ret, cur) => !ret || cur.result.value < ret.result.value ? cur : ret, 
         null);
  }

  // This is the heart of the algorithm.
  while (next = findNext()) {
    yield next.result.value;
    next.result = next.next();
  }
}

function arithAscending(a, b) { return a - b; }

console.log(Array.from(merge(data, arithAscending)));

答案 2 :(得分:0)

只需将它们添加到一个大数组中并对其进行排序。

你可以使用一个堆,将每个序列的第一个元素添加到它,弹出最低的一个(这是你的第一个合并元素),从弹出元素的序列中添加下一个元素并继续直到所有序列都结束。

然而,将它们添加到一个大数组并对其进行排序要容易得多。

答案 3 :(得分:0)

es6语法:

function mergeAndSort(arrays) {
    return [].concat(...arrays).sort()
}

函数接收要合并和排序的数组数组。

*编辑:由@Redu打电话,上面的代码不正确。如果未提供排序功能,则默认为sort(),为字符串Unicode。固定(和较慢)的代码是:

function mergeAndSort(arrays) {
    return [].concat(...arrays).sort((a,b)=>a-b)
}

答案 4 :(得分:0)

这是一个很漂亮的问题。与连接数组并应用.sort()不同;使用.reduce()的简单动态编程方法将产生O(m.n)时间复杂度的结果。其中 m 是数组的数量, n 是它们的平均长度。

我们将逐个处理数组。首先,我们将合并前两个数组,然后我们将结果与第三个数组合并,依此类推。

function mergeSortedArrays(a){
  return a.reduce(function(p,c){
                    var pc = 0,
                        cc = 0,
                       len = p.length < c.length ? p.length : c.length,
                       res = [];
                    while (p[pc] !== undefined && c[cc] !== undefined) p[pc] < c[cc] ? res.push(p[pc++])
                                                                                     : res.push(c[cc++]);
                    return p[pc] === undefined ? res.concat(c.slice(cc))
                                               : res.concat(p.slice(pc));
                  });
}


var sortedArrays = Array(5).fill().map(_ => Array(~~(Math.random()*5)+5).fill().map(_ => ~~(Math.random()*20)).sort((a,b) => a-b));
 sortedComposite = mergeSortedArrays(sortedArrays);

sortedArrays.forEach(a => console.log(JSON.stringify(a)));
console.log(JSON.stringify(sortedComposite));

根据@MirkoVukušić对此算法与.concat().sort()的比较,此算法仍然是使用FF但不是Chrome的最快解决方案。 Chrome .sort()实际上非常快,我无法确定它的时间复杂度。我只需要为JS性能调整一点,而不必触及算法的本质。所以现在它似乎比FF的concat和sort更快。

function mergeSortedArrays(a){
  return a.reduce(function(p,c){
                    var pc = 0,
                        pl =p.length,
                        cc = 0,
                        cl = c.length,
                       res = [];
                    while (pc < pl && cc < cl) p[pc] < c[cc] ? res.push(p[pc++])
                                                             : res.push(c[cc++]);
                    if (cc < cl) while (cc < cl) res.push(c[cc++]);
                    else while (pc < pl) res.push(p[pc++]);
                    return res;
                  });
}

function concatAndSort(a){
  return a.reduce((p,c) => p.concat(c))
          .sort((a,b) => a-b);
}


var sortedArrays = Array(5000).fill().map(_ => Array(~~(Math.random()*5)+5).fill().map(_ => ~~(Math.random()*20)).sort((a,b) => a-b));
console.time("merge");
 mergeSorted = mergeSortedArrays(sortedArrays);
console.timeEnd("merge");
console.time("concat");
concatSorted = concatAndSort(sortedArrays);
console.timeEnd("concat");

5000个随机排序的随机长度数组,介于5-10之间。

答案 5 :(得分:0)

这是一个简单的javascript算法我想出来了。希望能帮助到你。它将采用任意数量的排序数组并进行合并。我正在为数组的位置索引维护一个数组。它基本上遍历每个数组的索引位置,并检查哪一个是最小的。基于它,它拾取min并插入到合并的数组中。此后,它递增该特定数组的位置索引。我觉得时间复杂性可以提高。如果我拿出一个更好的算法,可能会使用最小堆来回发。

function merge() {
   var mergedArr = [],pos = [], finished = 0;
   for(var i=0; i<arguments.length; i++) {
       pos[i] = 0;
   }
   while(finished != arguments.length) {
       var min = null, selected;
       for(var i=0; i<arguments.length; i++) {
          if(pos[i] != arguments[i].length) {
              if(min == null || min > arguments[i][pos[i]]) {
                  min = arguments[i][pos[i]];
                  selected = i;
              }
          }
      }
      mergedArr.push(arguments[selected][pos[selected]]);
      pos[selected]++;
      if(pos[selected] == arguments[selected].length) {
         finished++;
      }
   }
   return mergedArr;
}