【问题标题】:Why is my Steinhaus-Johnson-Trotter Algorithm implementation producing duplicate permutations?为什么我的 Steinhaus-Johnson-Trotter 算法实现会产生重复排列?
【发布时间】:2021-11-06 05:35:03
【问题描述】:

我已经使用 JavaScript 实现了Steinhaus-Johnson-Trotter Algorithm,这个实现只计算数字数组的所有排列。

当数组的大小 >= 4 时,算法会产生重复的结果。我明白它为什么会产生结果,但我不确定如何避免这种情况,因为创建这些重复项满足算法原则。

class Direction {
    constructor(dir) {
        if(dir === 'LEFT' || dir === 'RIGHT') {
            this.dir = dir
        }
    }
    setDir(dir) {
        if(dir === 'LEFT' || dir === 'RIGHT') {
            this.dir = dir
        }
    }
    switchDir() {
        switch(this.dir) {
            case 'LEFT':
                this.dir = 'RIGHT'
                break
            case 'RIGHT':
                this.dir = 'LEFT'
                break
        }
    }
}
var permute = function(nums) {
    if(nums.length === 1) return [nums]
    if(nums.length === 2) return [nums, [nums[1], nums[0]]]
    // I'm only worried about arrays up to length 6
    const facts = [1, 2, 6, 24, 120, 720]
    const dirs = {}
    const max = Math.max(...nums)
    nums.forEach(v => {
        dirs[v] = new Direction('LEFT')
    })
    const res = []
    const move = (n) => {
        const i = nums.indexOf(n)
        const ele = dirs[n]
        switch(ele.dir) {
            case 'LEFT':
                [nums[i], nums[i - 1]] = [nums[i - 1], nums[i]]
                break
            case 'RIGHT':
                [nums[i], nums[i + 1]] = [nums[i + 1], nums[i]]
                break
        }
        if(n === max) {
            return
        }
        nums.forEach(v => {
            if(v > n) dirs[v].switchDir()
        })
    }
    // Number is said to mobile if it can move to its direction
    const isMobile = (n) => {
        const d = dirs[n].dir
        if(d === 'LEFT' && nums.indexOf(n) !== 0) {
            return true
        }
        if(d === 'RIGHT' && nums.indexOf(n) !== nums.length - 1) {
            return true
        }
        return false
    }
    // Finding mobiles means finding the largest number and checking if it is mobile
    const findMobile = () => {
        // If not max then lets find the next largest mobile
        var num = Number.MIN_VALUE
        nums.forEach(v => {
            if(isMobile(v) && v > num) {
                num = v
            }
        })
        return num
    }
    // Loop through the max length factorial, included up to only 6 as req
    while(res.length < facts[nums.length - 1]) {
        const next = findMobile()
        move(next)
        res.push([...nums])
        console.log(res)
    }
    return res
};

测试用例:

    Test 1:
    Input: [1,2,3]
    Result: [[1,3,2],[3,1,2],[3,2,1],[2,3,1],[2,1,3],[1,2,3]], Passed
 
    Test 2:
    Input: [5,4,6,2]
    Result: [
  [ 5, 6, 4, 2 ], [ 6, 5, 4, 2 ],
  [ 5, 6, 4, 2 ], [ 5, 4, 6, 2 ],
  [ 5, 4, 2, 6 ], [ 4, 5, 2, 6 ],
  [ 4, 5, 6, 2 ], [ 4, 6, 5, 2 ],
  [ 6, 4, 5, 2 ], [ 6, 4, 2, 5 ],
  [ 4, 6, 2, 5 ], [ 4, 2, 6, 5 ],
  [ 4, 2, 5, 6 ], [ 4, 2, 6, 5 ],
  [ 4, 6, 2, 5 ], [ 6, 4, 2, 5 ],
  [ 4, 6, 2, 5 ], [ 4, 2, 6, 5 ],
  [ 4, 2, 5, 6 ], [ 4, 5, 2, 6 ],
  [ 4, 5, 6, 2 ], [ 4, 6, 5, 2 ],
  [ 6, 4, 5, 2 ], [ 6, 5, 4, 2 ]
], Failed

如结果所示,算法会产生重复,但如前所述,重复之间的步骤满足算法。

我对算法的理解:

所有元素从右到左开始:

即。

我们找到下一个最大的“移动”数字,在本例中为 4。然后我们将其移向它的方向。

即。

重复这个过程。

如果移动的手机号码小于另一个号码,则较大的号码交换方向。

编辑 问题我已经解决了,就是不检查手机号大小和要换号的问题。

【问题讨论】:

  • 这并不完美,但您可以使用Set 来获得独特的解决方案。
  • 你是说对于长度为 4 的数组,它会产生 48 个结果(24 个不同)或 24 个结果(12 个不同)。
  • 如果你能读懂 Python 代码,我会展示工作实现
  • 另请注意,初始数组应排序 - 否则您需要正确的方向数组
  • @RBarryYoung 它正在产生 24 个结果,其中有一些重复。

标签: javascript arrays algorithm permutation


【解决方案1】:

将步骤与这个简单的 Python 实现进行比较(ideone link to look at results,顺序类似于wiki example)。

我没有看到方向项与代码中的元素交换在一起

def SJTperms(a, dirs):
    n = len(a)
    id = -1
    for i in range(n):
        #   can check mobility           mobile                        largest mobile
        if (0<=i+dirs[i]<n)       and (a[i] > a[i+dirs[i]])     and ((id == -1) or (a[i] > a[id])):
            id = i

    if (id == -1):  #last permutation
        return False

    for i in range(n):
        if a[i] > a[id]:
            dirs[i] = - dirs[i]

    #swap elements AND their directions
    a[id], a[id + dirs[id]] = a[id + dirs[id]], a[id]
    t = dirs[id]
    dirs[id], dirs[id + t] = dirs[id + t], dirs[id]
    return True

a = [1,2,3,4]
d = [-1]*len(a)
cont = True
while cont:
    print(a)
    #print(d)
    cont = SJTperms(a, d)

【讨论】:

  • 我将路线存储在地图中,而不是数组中。该地图以数字为键,以数字的方向为值。你认为这是不正确的吗?此外,您的解决方案对我来说很有意义,除了 d = [-1]*len(a) 行。你能解释一下吗?
  • 这是 n (-1) 的 [-1,-1,-1,-1] 的数组
  • 所以你用 -1 代表左边,用 1 代表右边。
  • 是的,完全正确。所以 a[id + dirs[id]] 以箭头方向显示项目。
  • 也许值得在 ideone 中取消注释#print(d),获取带有方向[1, 4, 3, 2] [-1, 1, -1, -1] 的逐步输出,并在每个阶段与您的数组和映射状态进行比较
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-04-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多