【问题标题】:Recursively going through permutations without storing in memory递归遍历排列而不存储在内存中
【发布时间】:2021-05-31 00:43:45
【问题描述】:

我正在尝试使用递归函数遍历数组的所有可能排列。 排列不需要存储在内存中。它们正在被递归函数立即处理。

这个想法是递归函数有一个参数'used'跟踪递归树中此时'固定'的元素,还有一个参数'free'跟踪不存在的元素此时已修复(即它们将在从那里向下树的递归步骤中重新排列)。所以第一次,函数是用一个空的'used'数组和一个完整的'free'数组调用的。

不知何故,我下面的代码还不能工作。它只成功处理了第一个排列。

const elements = [7, 23, 41, 65, 99]
const n = elements.length;

handlePermutations([], elements);

function handlePermutations(used, free) {
  if (used.length<n) {
    for (i = 0; i < free.length; i++) {
      newUsed = used.concat(free[i]);           // add element i from free to used
      newFree = free.filter(x => x != free[i])  // remove element i from free
      handlePermutations(newUsed, newFree);
    }
  } else {        
    // ... 'process' this permutation (do something useful with it) ...
  }
}

【问题讨论】:

  • 你有想要的结果的例子吗?
  • 真的有必要每次迭代都调用handlePermutations吗? for 不是已经这样做了吗?
  • @NinaScholz,好吧.. 在这种情况下 [7, 23, 41, 65, 99] 的所有可能排列。但我不会把它们都写在这里。
  • @Peladao 我想你会发现this Q&A 很有用
  • 我喜欢这个问题产生的各种答案!

标签: javascript algorithm recursion permutation


【解决方案1】:

您正在使用(隐式声明的)全局 i,因此它不会为每个函数重置。

改成let i修复问题

const elements = [7, 23, 41, 65, 99]
const n = elements.length;

handlePermutations([], elements);

function handlePermutations(used, free) {
  if (used.length < n) {
    for (let i = 0; i < free.length; i++) {
      let newUsed = used.concat(free[i]); // add element i from free to used
      let newFree = free.filter(x => x != free[i]) // remove element i from free
      handlePermutations(newUsed, newFree);
    }
  } else {
    console.log(...used)
    // ... 'process' this permutation (do something useful with it) ...
  }
}

顺便说一句,如果您有重复的项目,您当前的free.filter(...) 会中断。一种可能的方法是简单地将其更改为检查传入的索引。

free.filter((x,index) => index!=i)

【讨论】:

  • 非常感谢!有那么一刻,我开始认为自己老了,无法创建简单的算法。但幸运的是,这只是一个小小的疏忽。 (我不是真正的 javascript 专家)。
  • @Peladao 请注意,此算法将使用O(n^2) space afaict
【解决方案2】:

出于兴趣,这里是同一算法的生成器版本(有一些更改)。

const elements = [1, 2, 3, 4, 5]

for (let item of Permutations(elements)) {
  console.log(...item)
}

// note: this (OP's) algorithm use O(n**2) space afaict 
function Permutations(elements) {
  return handlePermutations([], elements)

  function* handlePermutations(used, free) {
    if (free.length == 0)
      yield used;
    else {
      for (let i = 0; i < free.length; i++) {
        let newUsed = used.concat(free[i]) // add element i from free to used
        let newFree = free.filter((x, index) => index != i) // remove element i from free
        yield* handlePermutations(newUsed, newFree);
      }
    }
  }
}

【讨论】:

    【解决方案3】:

    另一种方法是传递将与每个排列一起使用的回调函数。

    const excludeIndex = (i) => (xs) => 
      [... xs .slice (0, i), ... xs .slice (i + 1)]
    
    const handlePermutations = (fn) => (free, used = []) =>
      free.length == 0
        ? fn (used)
        : free .forEach (
            (e, i) => handlePermutations (fn) (excludeIndex (i) (free), [...used, e])
          )
    
    handlePermutations (xs => console. log(...xs)) ([7, 23, 41, 65])
    .as-console-wrapper {max-height: 100% !important; top: 0}

    我们包含简单的帮助器excludeIndex,它返回缺少索引的数组副本。我们在递归调用中使用它以及将当前元素连接到used

    我不太喜欢只为副作用而编写的代码,但由于这是问题的基本目标,我当然可以在这里接受。

    时间复杂度不可避免地是O (n!)我认为空间复杂度是 O (n),因为 freeused 一起包含原始的 n 元素。(糟糕,见 cmets!)空间复杂度是 O (n^2)

    【讨论】:

    • 空间不是二次方的吗?在使用修改后的副本进行递归调用时,不是每个free.forEach(...) 都保留自己的free 副本吗?回调确实应该为 O(n) 空间完成。我有一个answer,它在 O(n) 空间中使用链表执行此操作,但在 Common Lisp 中...
    • used 的实例可能共享相同的存储空间——我不知道 JS——但是当从数组中间提取元素时似乎很难实现共享? (并且递归深度为 n 级)
    • @WillNess:是的,你说的很对。我没有考虑清楚,因为这只是事后的想法。已更新。
    【解决方案4】:

    对此采取不同的方法。使用单个整数来确定该数组的不同排列。

    因此,为数组生成所有排列并对其进行处理将是:

    function permutation(array, n) {
      let result = array.slice(); // don't mutate the input
      for (let i = 0, rest = result.length; rest > 1 && n > 0; ++i, n = Math.floor(n / rest--)) {
        let j = n % rest + i;
        if (i === j) continue;
        const tmp = result[i];
        result[i] = result[j];
        result[j] = tmp;
      }
      return result;
    }
    
    const elements = [1, 2, 3, 4, 5];
    const length = elements.reduce((a,_,i) => a*(i+1), 1);
    //const length = 5 * 4 * 3 * 2 * 1;
    
    const map = {};
    for (let i = 0; i <= length; ++i) {
      map[i] = permutation(elements, i).join();
    }
    
    console.log(map);
    console.log("map[120] === map[0]", "We're back at the start. From now on this will just loop the results.");
    console.log("expected %i, got %i (different values/permutations in the results)", length, new Set(Object.values(map)).size);
    .as-console-wrapper{top:0;max-height:100%!important}

    你说没有存储。我正在使用上面的map,因为这个 sn-p 只会存储最后 50 个console.logs,而它会在地图中显示所有 120 个条目/行。最后表明它不会产生重复,map 中确实有 120 种不同的排列。

    【讨论】:

      【解决方案5】:

      您可以采用一种简化的方法,只交出一组索引并采用两条规则:

      • 如果最后一个元素大于前一个元素,则交换两者。

      • 从数组的末尾开始搜索元素大于左右元素的索引。

        如果找到,则在找到的索引右侧(包括实际索引)搜索下一个 grater 元素,将其作为组前的新元素,对其余元素进行排序并应用。

        如果没有找到,结束递归。

      function next([...array]) {
          console.log(...array);
          if (array[array.length - 2] < array[array.length - 1]) {
              [array[array.length - 2], array[array.length - 1]] = [array[array.length - 1], array[array.length - 2]];
              return next(array);
          }
          let index = array.length - 1;
          while (--index) {
              if (array[index - 1] < array[index] && array[index] > array[index + 1]) {
                  let value = Math.min(...array.slice(index - 1).filter(v => v > array[index - 1]))
                  array = [
                    ...array.slice(0, index - 1),
                    value,
                    ...array.slice(index - 1).filter(v => v !== value).sort((a, b) => a - b)
                  ];
                  break;
              }
          }
          if (!index) return;
      
          return next(array);
      }
      
      next([0, 1, 2, 3, 4]);
      .as-console-wrapper { max-height: 100% !important; top: 0; }

      【讨论】:

        猜你喜欢
        • 2011-08-29
        • 2013-02-24
        • 2019-08-03
        • 1970-01-01
        • 2018-11-20
        • 1970-01-01
        • 2013-07-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多