【问题标题】:Morse without separators - best algorithm没有分隔符的莫尔斯 - 最佳算法
【发布时间】:2016-05-01 09:24:15
【问题描述】:

在摩尔斯电码中,dotsdashes 以 1-4 为一组,由分隔符分隔。每组表示一个字母。单词之间有两个分隔符。第三句之间。

解密基本摩尔斯电码的应用程序很容易制作。但我的问题是,当没有分隔符时,如何解决这个问题?我知道会有大量无意义的结果,但这不是我的意思。我只需要以最有效的方式得到所有可能的结果。

这将是一个输入:

......-...-..---

这将是众多输出之一:

hello

你会怎么做?

【问题讨论】:

  • 请注意,数字都是五个点/破折号。
  • 有些原文比其他原文更有可能吗? (例如,您知道原始文本大部分是英文文本吗?)如果您知道字符概率(或者甚至更好,字符对或三元组的概率等),那么使用隐马尔可夫模型将给出 much i> 更多信息输出。你可以例如确定最有可能的整体解码。
  • 这也是一个谷歌面试问题

标签: algorithm cryptography processing-efficiency morse-code


【解决方案1】:

读完一个嘀嘀或嘀嘀后,你有两个选择:终止这封信或继续当前的信。这将导致您的代码出现很多分歧,而递归方法可能是实现这一点的好方法。

到目前为止,保留可能字符串的缓冲区,并在您碰到字符串结尾并且与字母结尾重合时打印(或存储)结果。

这是 C 中的一个实现:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

static const char *letter = "**ETIANMSURWDKGOHVF*L*PJBXCYZQ**";

void morse_r(char *buf, int len, const char *str, int code)
{
    if (*str == '\0') {
        // end of string; print if staring new letter
        if (code == 1) printf("%.*s\n", len, buf);
    } else if (code < 16) {
        if (*str == '.') code = 2 * code;                    
        if (*str == '-') code = 2 * code + 1;

        // continue letter
        morse_r(buf, len, str + 1, code);

        // start new letter
        buf[len++] = letter[code];
        morse_r(buf, len, str + 1, 1);
    }
}

void morse(const char *str)
{
    char buf[strlen(str)];

    morse_r(buf, 0, str, 1);
}

int main()
{
    morse("......-...-..---");

    return 0;
}

这个实现非常简单。它使用简单的查找机制,并且不检查字母是否确实存在。 (letter 数组中的星号是有效的摩尔斯电码,但它们不是拉丁字母。)

这种方法也相当暴力:它一遍又一遍地重新计算尾部。尾部的记忆将为处理孤独字符串的处理器节省大量额外的工作。

而且,正如您所知,会有很多无意义的结果。上面的代码产生 20569 个字符串(其中一些带有星号,即无效)。当您在途中进行合理性或字典检查时,您可以防止许多递归。例如,一行中的许多点会产生很多带有重复 Es 的无意义词。

编辑: 正如 Jim Mischel 所指出的,按顺序解释摩尔斯电码查找的工作原理。 Yves Daoust 在他的回答中提到了一个 trie。一个trie是一个用于存储单词的树形结构;每个节点可以有与字母表中的字母一样多的子节点。摩尔斯电码只有两个字母:dit (.) 和 dah (-)。因此,摩尔斯电码树是二叉树。

尝试通常很少;单词相当长,许多字母组合不存在。莫尔斯尝试是密集的:莫尔斯字母编码很短,几乎每个组合都被使用。树可以存储为线性的“平面”数组,类似于heap。一个节点由它在数组中的索引i 表示。那么左孩子是2*i,右孩子是2*i + 1

可以在answer I posted 中找到另一个与莫尔斯相关的问题的更好和更详细的解释,我从中获取了我在上面示例中使用的查找代码。

【讨论】:

  • 一个有趣的算法。很好的答案,除了你应该解释你的 letter 数组是一个以广度优先顺序存储的二叉树,以及你的算法如何使用它。
  • @JimMischel:谢谢。你是对的,我在没有真正解释的情况下在这里打了代码。我的查找例程仍然在旧答案的某个地方,所以我只是使用它。我已经添加了解释。
  • 这种隐式堆表示在这里确实很合适。
【解决方案2】:

IMO 最有效的方法是使用 trie。这是一棵树,每个节点最多有两个儿子,一个用于.,一个用于-,当这些字符在给定阶段是可能的时。除了到儿子的链接之外,节点还有一个“终端”字符,告诉从根到该节点的路径编码什么字符;终端字符可以是零,表示路径不编码任何字符(字符串未完成)。

由于莫尔斯字母表很小,您甚至可以手动构建特里树。这是其中的一部分。

. => E
    . => I
        . => S
        - => U
    - => A
        . => R
        - => W
- => T
    . => N
        . => D
        - => K
    - => M
        . => G
        - => O

要利用 trie,编写一个递归函数,将输入流中的位置和 trie 中的节点作为输入。如果节点有终端字符,则将终端字符附加到输出字符串并将节点重置为 trie 的根。同时,通过跟随匹配下一个输入符号的儿子继续对trie的探索。

在您的示例案例中,以下是递归执行的几个第一步(分析前四个输入符号):

. => E
    .|. => EE
        .|.|. => EEE
            .|.|.|. => EEEE
            .|.|.. => EEI
        .|.. => EI
            .|..|. => EIE
            .|... => ES
    .. => I
        ..|. => IE
            ..|.|. => IEE
            ..|.. => II
        ... => S
            ...|. => SE
            .... => H

【讨论】:

    【解决方案3】:

    您可以通过 2 次完成。第一个将标记一个字母可能结束的位置,第二个将提取所有可能的字符串。

    您可以作为动态编程实现的第一遍。如果可以将第一个 x 字母解析为某些字符,possible[x] 为真。您从possible[0] = true 开始,然后为所有其他x 计算possible 的值。要计算它,您需要最后的 1、2、3 和 4 个字符,如果它们匹配一些现有的摩尔斯电码,并且与字符串的其余部分对应的可能值是 true,那么标记 possible[x] 也是 true。这是 O(N)。

    现在你必须提取所有可能的词。所以,从头开始,使用possible向量来消除错误的解决方案。在这里,您应该再次尝试最后 1-4 个字符,看看它们是否匹配,如果它们匹配,则对应的 possible 位置为真,那么您将其视为可能的字符并递归调用该函数以生成剩余部分的所有解决方案.不幸的是,在最坏的情况下(当分区是可能的),这是指数 O(4^N)。在实践中,这将遍历与输入字符串匹配的可能单词的数量,因此如果只有几个选项,此传递也​​会很快。

    请注意,字符串越长,您就越有可能拥有更多选项和更多可能的解释。

    如果您另外将可能的单词限制为预定义的集合,您可以修改第一遍以使用单词而不是单个字母。然后可能的解释数量应该会减少很多,即使在更长的字符串上你的算法也会很快。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-05
      • 2021-11-24
      • 2015-09-03
      相关资源
      最近更新 更多