Android的点密码系统的熵是多少?

时间:2010-01-22 21:27:40

标签: security math statistics

可能有多少排列的机器人点登录系统?我知道这个问题的解决方案在于离散数学,特别是Permutations Without Repetition,如果你的答案没有使用排列组合,那么你就错了。

密码的长度在4到9个点之间,但总共有9个点可以进行置换。所以我的初始等式是:

9P4+9P5+9P6+9P7+9P8+9P9

但是,我知道这个等式是不完整的,因为它没有考虑所有的规则。每个点都是不同的,因为它是位置所以如果你为每个点分配一个数字,如下所示:

123
456
789

密码“1397”是不可能的,如果您尝试使用此密码,您实际上已输入“1236987”,因为会自动选择中间的数字。需要创建另一个等式来解释这些限制,然后从上面的nPr等式中减去。

这个link有一个很棒的视频,有人使用Android登录。它还详细介绍了规则。该页面上的数学是完全错误的,他甚至不接近真正的解决方案。

6 个答案:

答案 0 :(得分:20)

这只是部分答案。唯一相关的起始点是1,2和5.对于点1和2,您可以通过简单的旋转变换生成相当于从任何其他点(除5之外)开始的模式。这意味着如果您可以枚举从1和2开始的所有模式,您可以通过乘以4来计算从5以外的任何数字开始的所有模式。

从5开始的路径是不同的情况。您可以通过计算以5-> 1和5-> 2开头并乘以4的所有路径来计算所有路径,因为您可以通过简单的旋转变换再次生成所有其他可能的路径。

因此,枚举路径的方法是......

(所有路径从1开始+所有路径从2开始+所有路径从5开始> 1 +所有路径以5> 2开头)* 4

再次,编号系统,以便于参考:

1  2  3
4  5  6
7  8  9

第一步:

从1 - >可以访问2,4,5,6和8。

从2 - >可以访问1,3,4,5,6,7和9(基本上不是8或本身)。

从5 - >可以访问1,2,3,4,6,7,8和9。

对于之后的动作,它变得非常有趣。

例如,1537是有效密码。 1539不是。

这里简洁的是规则:

没有点可能是路径的两倍。如果一个点不是路径的一部分并且它被转换完全交叉,则它将成为源点和目标点之间路径的一部分。

以下是一些示例路径:

    允许
  • 2-> 3-> 1-> 8。
  • 不允许
  • 1-> 3-> 2-> 5,因为当1> 3正好超过2时,2不是路径的一部分,因此路径变为1-> 2-> 5。 3-> 5,无论您是否想要它。
  • 1-> 2-> 3-> 7是不允许的,因为3> 7跨越5并且5还不是路径的一部分。
  • 允许
  • 1-> 5-> 3-> 7。在3> 7中忽略了5,因为它已经是路径的一部分。

这个程序:

class LockPattern(object):
    def __init__(self, *args):
        if (len(args) == 1) and hasattr(args[0], '__iter__'):
            args = tuple(args[0])
        if len(args) > 9:
            raise TypeError("A LockPattern may have at most 9 elements.")
        self._pattern = ()
        for move in args:
            if not self.isValidNextStep(move):
                raise TypeError("%r is not a valid lock sequence." % (args,))
            else:
                self._pattern = self._pattern + (move,)
    def __len__(self):
        return len(self._pattern)
    def __iter__(self):
        return iter(self._pattern)
    def isValidNextStep(self, nextdot):
        nextdot = int(nextdot)
        if (nextdot < 1) or (nextdot > 9):
            raise ValueError("A lock sequence may only contain values from 1 "
                             "to 9 and %d isn't." % (nextdot,))
        if len(self._pattern) <= 0:
            return True # Any dot is valid for the first dot
        if len(self._pattern) >= 9:
            return False
        if nextdot in self._pattern:
            return False # No dot may be visited twice
        prevdot = self._pattern[-1]
        dotpair = tuple(sorted((prevdot, nextdot)))
        if dotpair == (1,3):
            return 2 in self._pattern
        if dotpair == (1,7):
            return 4 in self._pattern
        if dotpair in ((1,9),(2,8),(3,7),(4,6)):
            return 5 in self._pattern
        if dotpair == (3,9):
            return 6 in self._pattern
        if dotpair == (7,9):
            return 8 in self._pattern
        return True
    def isValidLockSequence(self):
        return 4 <= len(self)
    def newSequenceAddDot(self, nextdot):
        if not self.isValidNextStep(nextdot):
            raise ValueError("%d is not a valid next dot for the sequence." % (nextdot,))
        newseq = LockPattern()
        newseq._pattern = self._pattern + (nextdot,)
        return newseq

def genAllPatterns(starting = LockPattern()):
    if starting.isValidLockSequence():
        yield starting
    for dot in xrange(1,10):
        if starting.isValidNextStep(dot):
            for result in genAllPatterns(starting.newSequenceAddDot(dot)):
                yield result

print reduce(lambda x, p: x+1, genAllPatterns(), 0)

生成389112的答案。

它也证实了我以前的直觉:

lsts = tuple(((p, list(genAllPatterns(LockPattern(p)))) for p in ((1,), (2,), (5,1), (5,2))))
[(x[0], len(x[1])) for x in lsts]
-> [((1,), 38042), ((2,), 43176), ((5, 1), 7352), ((5, 2), 8708)]
sum((len(x[1]) for x in lsts)
-> 97278
97278 * 4
-> 389112

答案 1 :(得分:2)

关键是要注意,一旦你访问了任何两个点,你就可以到达你想要的任何其他点而不会移动你尚未访问的任何点。因此,一旦您选择了两个起始点(按顺序),您就可以按照您希望的任何顺序随意选择其余的密码。 (这是因为“骑士的动作”是允许的,至少根据您链接的页面而言)

因此,除了“起始两个”点之外,剩余的2-7位尾巴的数量是7P2 + 7P3 + 7P4 + 7P5 + 7P6 + 7P7。有56个潜在的起始两位数头,因为你可以从任何角点有5个移动,7个从任何边缘点移动,8个从中心点移动。所以解锁模式的总数将是56 *(7P2 + 7P3 + 7P4 + 7P5 + 7P6 + 7P7)= 766 752.

如果这是错误的,可能是因为我遗漏了对该系统规则的一些了解。

答案 2 :(得分:2)

好的,首先让我开始说,多种多样似乎是正确的。我们可以用数学做什么。当他说确实只有3个案例需要关注时,我会同意他的意见。 1,2和5.

OP需要某种优雅的计数公式,对于这样的问题,我很怀疑。当你使用全部9个点时,你会发现汉密尔顿路径,如果这是一个完整的图形将非常容易计算(事实并非如此)。由于这三种情况中的每一种都是唯一的,因此您将枚举所有这些情况以查找路径总数。

让我们看看第一个案例,从一个角落开始。角落有四种选择,那么你的下一个广场有5种选择。现在你很快就会发现我们的公式扩展得相当快,因为​​我们有5个选择,我们必须将这5个选项分成2组。

移动到中间的正方形,无论您移动到哪一个,都会有相同的选择。与转向5相比,我们的公式是:

4 *(4 *(6)+ 1 *(7))

然后我们必须将6和7选项分成更多组。如果我们移动到侧面正方形,那么我们现在可以移动到两个正方形,三个角和一个中间正方形。我们的公式变为:

4 *(4 *(2 *(5)+ 3 *(3)+ 1 *(7))+ 1 *(7))

然后我们必须开始解决,如果我们搬到中间广场,我将停在这里。找到汉密尔顿路径是NP完全的,尽管我们只是计算它们。但是这里没有任何属性我们可以利用优雅的解决方案。这是一个图论理论问题,它是一个涉及暴力强制解决方案的问题,正如Omnifarious之前所做的那样。

我会试着解释为什么你的直觉是错的,你这样说:

“我知道这个问题的解决方案在于离散数学,特别是没有重复的排列,如果你的答案没有使用排列或组合,那么你就错了。”

不幸的是,你不知道这个事实,因为你不知道一个公式(除非你能给我一个严格的非建设性的证明)。事实是,排列或组合意味着当你有n个项目时,你可以在任何迭代中选择任何项目。现在,您可以对可以选择的项目数量以及在某些迭代中可以选择的项目进行限制。但是你不能做什么并期望一个优雅的解决方案是选择某些项目会影响你接下来可以选择的项目。这正是这个问题正在做什么以及为什么没有一些使用组合和排列的好公式。

简而言之,您所寻找的公式并不存在,但Omnifarious确实找到了正确的答案。

答案 3 :(得分:2)

Omnifarious在他的帖子中绝对准确 - 有389,112种组合。我发布了一个关于确定这个算法的整个过程的巨大的事情,以及一个列出1-9长度的每个组合的文本文件(这似乎是可能的,从我在女朋友的电话上可以看出)。此内容的链接是:http://bit.ly/hEHcBJ

答案 4 :(得分:1)

我不知道是否还有人在乎,但这是一个图形路径计数问题。有9个节点,因此创建一个9 x 9矩阵(每行是一个来自节点,每一列是一个节点)。如果节点n连接到节点m,则将(n,m)和(m,n)都设置为1.对所有连接执行此操作。剩下的就是零。这称为邻接矩阵。将此矩阵提升为模式中的行数,并添加所有条目。这是排列的数量。如果有人关心,我会发布一些python代码(在我的手机上或我现在发布)

import numpy as np
paths = [[1,3,4],[2,3,4,5],[4,5],[4,6,7],[5,6,7,8],[7,8],[7],[8],[]]
m = [[0 for i in range(0,len(paths))] for j in range(0,len(paths))]

for i in range(0,len(paths)):
    for j in paths[i]:
        m[i][j] = 1
        m[j][i] = 1

for row in m:
    print row

[0, 1, 0, 1, 1, 0, 0, 0, 0]
[1, 0, 1, 1, 1, 1, 0, 0, 0]
[0, 1, 0, 0, 1, 1, 0, 0, 0]
[1, 1, 0, 0, 1, 0, 1, 1, 0]
[1, 1, 1, 1, 0, 1, 1, 1, 1]
[0, 1, 1, 0, 1, 0, 0, 1, 1]
[0, 0, 0, 1, 1, 0, 0, 1, 0]
[0, 0, 0, 1, 1, 1, 1, 0, 1]
[0, 0, 0, 0, 1, 1, 0, 1, 0]

adj = np.matrix(m)

adj.sum()
40

(adj**2).sum()
200

(adj**6).sum()
107648

我的答案与上面其他人给出的答案不符,所以我还为相关图表编写了一个图表遍历模拟。模拟的答案(由于排气)与邻接矩阵计算的答案相匹配。我还手工制作了前两个(一个边缘和两个边缘)并得到了相同的答案(40和200)。请注意,我假设您可以重复访问相同的顶点(即1-> 2-> 1&gt; 2 ...)。

我的图表:

0 - 1 - 2
| X | X |
3 - 4 - 5
| X | X |
6 - 7 - 8

答案 5 :(得分:1)

编辑:不,我错了。图表会发生变化,因为一般情况下,例如1 - &gt; 3是不允许的(你必须首先通过2),你可以做2之类的事情 - > 5 - &gt; 1 - &gt; 3:1 - > 3边缘变得可用,因为2已经在路径中使用。

我写了一个程序来解释这个问题并得到了与Omnifarious相同的389,112(然后我意识到我的程序和他的程序一样)。

我对此感到好奇:相信它是139,880。像吉姆所说,这可以用图表建模。将邻接矩阵提升为幂不起作用,因为它使用重复计算路径,但您可以通过图表BFS:

nodes = range(1, 10)

edges = {
    1: [2, 6, 5, 8, 4],
    2: [1, 4, 7, 5, 9, 6, 3],
    3: [2, 4, 5, 8, 6],
    4: [1, 2, 3, 5, 9, 8, 7],
    5: [1, 2, 3, 4, 6, 7, 8, 9],
    6: [3, 2, 1, 5, 7, 8, 9],
    7: [4, 2, 5, 6, 8],
    8: [7, 4, 1, 5, 3, 6, 9],
    9: [8, 4, 5, 2, 6],
}

def num_paths(length):
    q = deque([node] for node in nodes)
    paths = []
    while q:
        path = q.popleft()
        if len(path) == length:
            paths.append(path)
            continue
        for node in edges[path[-1]]:
            if node not in path:
                q.append(path + [node])
    return len(paths)

然后只需添加长度为4,5,6,7,8和9的路径数。

>>> sum(num_paths(i) for i in range(4, 10))
139880