使用哈希进行路径重建?

时间:2015-06-10 00:29:18

标签: java algorithm hash

我一直在试图找出如何找到一个O(n)时间复杂度算法来解决 Java 中的以下内容:

我们给出了一个带有起点和终点的输入对,我们必须构造一个路径,使得一个输入的开头与另一个输入的结束匹配(在这种情况下,按字母顺序排列)

EX:如果我有一个清单

P B
B M
O P
M Z

其中PB或BM是成对的,P是起点,B是结束

我应该创建输出

O P
P B
B M
M Z

当然,我想检查周期 - 在这种情况下,我只会报告是否存在周期。

我第一次尝试这样做时,我使用了一个O(N ^ 2)的交换算法,如果匹配并向下移动列表,则基本上交换两个条目。有一个绝对更快的方法来做到这一点,我直觉地知道如何做到这一点,但我想知道是否有人可以清除它。

基本上,我假设您需要制作某种哈希/键类型的结构,您可以通过键引用对象本身的值。

例如,您将保留两个集合,一个是开头,另一个是结尾。您可以获取每个输入并创建一个具有开始和结束字段的对象,然后将所有开始和结束添加到两个数组中。然后你必须找到每个开始的基本结束[start]并在找到它们之后将它们各自的对象添加到列表中。

我唯一的事情是,我无法弄清楚如何使用哈希表或类似的数据结构在Java中实现它。我是否必须将开始和结束添加到单独的哈希表中,然后将起点用作查找键?

在github上查看在python中解决它的人的伪代码:

for inputs
    parse input 
    add parse[1] to starts, add parse[2] to ends

for starts
    find origin (a start not in ends) <--requires hash?

if no origin
    cycle exists

for inputs
    find ends[origin] <--requires hash?
    origin = ends[origin] <-- so we can find the next one

想知道是否有人可以帮助我将其转换为Java中的算法(其他有效的解决方案非常受欢迎,因为我对这种类型的问题解决感兴趣)或者从数据结构角度更一般地理解它。

2 个答案:

答案 0 :(得分:1)

这是在Java中使用HashMaps的简单实现:

    String[][] paths = {
        {"P", "B"}, 
        {"B", "M"},
        {"O", "P"},
        {"M", "Z"},
    };

    // create a single hash map, mapping start->end
    HashMap<String, String> end = new HashMap<>();

    for(int i = 0; i < paths.length; i++)
    {
        end.put(paths[i][0], paths[i][1]);
    }

    // find if a cycle exists
    String origin = null;
    for (String key : end.keySet()) {
        if(!end.containsValue(key))
        {
            origin = key;
            break;
        }
    }

    if(origin == null)
    {
        System.out.println("cycle exists");
        return;  // no origin found, cycle exists
    }

    // iterate over hash map:
    int count = 0;
    while(true)
    {
        if(!end.containsKey(origin))
            break;
        String next = end.get(origin);
        System.out.println(origin + " " + next);
        origin = next;
        if(++count > paths.length)
        {
            System.out.println("cycle exists");
            break;
        }
    }

end存储给定起点(键)的终点(值)。

如果某个点作为起点但不是终点存在,那么它将成为end中的一个键,而不是end中的值。因此迭代end的keySet并检查end是否包含每个键作为值将找到原点。如果没有这样的起源那么就有一个循环。然而,这不足以确定是否存在循环,因为即使存在原点也可能存在循环。考虑一下这个集合:

P B 
B M
O P
M Z
Z M

有一个独特的起源(O),但也有一个周期M - > Z - &gt; M.要检测到这一点,您可以走设置并跟踪您已经访问过的点,或者更简单地说您可以走设备,如果您最终访问的点数多于输入的长度,则必须有一个周期。

要遍历该集合,请将字符串设置为原点。然后继续查找end给定origin作为键的值,并打印出键,值对(origin,end.get(origin))作为开始,结束。然后将end.get(origin)设置为新原点。当在当前原点的末尾HashMap中找不到值时,循环终止。

如果列表中有重复的开始或结束位置,此算法将失败,例如:

P B 
B M
O P
M Z
B Z

从问题中不清楚你是否必须处理这个案件。

答案 1 :(得分:1)

虽然我更喜欢用

之类的东西构建类似链表的结构
document.getElementById("chatWrapper").addEventListener("click", playSound);
var sEE0 = new Audio('../sound/00-menu-music.mp3');
sEE0.volume = 0.2;
function playSound() {
    //alert("Success!");
    if (sEE0.paused) {
        sEE0.play();
    }
}

仍然可以使用Map(如果你想要的话,HashMap)。

它看起来类似于@samgak的答案,只是一些技巧可以让你的代码看起来更短(没有必要更快,没有基准测试):

class Node {
    String name;
    Node prev;
    Node next;
}

可能仍然存在这样的循环岛路径:

String[][] rawPaths = {
    {"P", "B"}, 
    {"B", "M"},
    {"O", "P"},
    {"M", "Z"},
};


// 1. You can keep only one `Map<String, String> which keeps start as key and end as value.

Map<String, String> pathMap = new HashMap<>();
for (String[] path : rawPaths) {
    pathMap.put(path[0], path[1]);
}

// 2. Some validity checks for data:

// 2.1 No of entry in final Map should equals to number of lines in raw data, which means there is no duplicated "start" node.
assert pathMap.size() == rawPaths.length;

// 2.2 There should be no duplicate in "end", i.e. no partial cyclic path
assert new HashSet<>(pathMap.values()).size() == rawPaths.length;

// 2.3 there should be one and only one start value that does not exists in end values, i.e. no full cyclic path
Set<String> startPoints = new HashSet<>(pathMap.keySet());
startPoints.removeAll(pathMap.values());

assert startPoints.size() == 1;

//3. Then you can make up the path by getting the "head" which is the only value left in startPoints, and construct the full path

String start= startPoints.iterator().next();

while (pathMap.contains(start)) {
    String end = pathMap.get(start);
    System.out.println(start + " " + end);
    start = end;
}

在遍历第3部分后,可以很容易地检查,保留计数器,计数器最终应与A B B C C D X Y Y X 相同。如果没有,则存在循环岛