桥渡难题

时间:2009-07-17 16:00:38

标签: algorithm puzzle

四个人必须在晚上过桥。任何穿过一个或两个男人的派对都必须随身携带手电筒。手电筒必须来回走动;它不能被抛出等等。每个人都以不同的速度行走。一分钟需要1分钟,另外2分钟,另外5分钟,最后10分钟。如果两个男人交叉在一起,他们必须以较慢的男人的步伐走路。没有技巧 - 男人都是从同一侧开始,手电筒不能长距离照射,没有人可以随身携带等等。

问题是他们所能遇到的最快的是什么。我基本上是在寻找一些针对这类问题的通用方法。我的朋友告诉我,这可以通过Fibonacci系列解决,但解决方案并不适用于所有人。

请注意,这不是家庭作业。

11 个答案:

答案 0 :(得分:19)

an entire PDFalternate link)解决了这个问题的一般情况(在正式证明中)。

答案 1 :(得分:13)

17分钟 - 这是一个经典的MS问题。

1,2 => 2 minutes passed.
1 retuns => 3 minutes passed.
5,10 => 13 minutes passed.
2 returns => 15 minutes passed.
1,2 => 17 minute passed.

一般来说,最大的问题/最慢的人应该总是放在一起,并且最快的足够的旅行每次都能在不使用缓慢的资源的情况下带回光线。

答案 2 :(得分:12)

我会通过在Dice.com上放置一个假工作广告解决这个问题,然后在面试中问这个问题,直到有人做对了。

答案 3 :(得分:5)

根据维基百科

众所周知,这个谜题最早出现在1981年的“超级策略与谜题策略”一书中。在这个版本的拼图中,A,B,C和D分别需要5,10,20和25分钟才能越过,时间限制为60分钟

然而,这个问题在“你将如何移动富士山?”一书中出现后才得以普及。

这个问题可以推广给N个人,他们有不同的个人时间过桥。

以下程序适用于普通N人和他们的时代。

class Program
{
    public static int TotalTime(List<int> band, int n)
    {
        if (n < 3)
        {
            return band[n - 1];
        }
        else if (n == 3)
        {
            return band[0] + band[1] + band[2];
        }
        else
        {
            int temp1 = band[n - 1] + band[0] + band[n - 2] + band[0];
            int temp2 = band[1] + band[0] + band[n - 1] + band[1];

            if (temp1 < temp2)
            {
                return temp1 + TotalTime(band, n - 2);
            }
            else if (temp2 < temp1)
            {
                return temp2 + TotalTime(band, n - 2);
            }
            else
            {
                return temp2 + TotalTime(band, n - 2);
            }
        }
    }

    static void Main(string[] args)
    {
        // change the no of people crossing the bridge
        // add or remove corresponding time to the list
        int n = 4; 

        List<int> band = new List<int>() { 1, 2, 5, 10 };

        band.Sort();

        Console.WriteLine("The total time taken to cross the bridge is: " + Program.TotalTime(band, n));
        Console.ReadLine();
    }
}

输出:

过桥需要的总时间是:17

对,

int n = 5; 
List<int> band = new List<int>() { 1, 2, 5, 10, 12 };

输出:

过桥需要的总时间是:25

有关,

int n = 4; 
List<int> band = new List<int>() { 5, 10, 20, 25 };

OUTPUT 过桥需要的总时间是:60

答案 4 :(得分:4)

以下是ruby中的回复:

@values = [1, 2, 5, 10]
# @values = [1, 2, 5, 10, 20, 25, 30, 35, 40]
@values.sort!
@position = @values.map { |v| :first }
@total = 0

def send_people(first, second)
  first_time = @values[first]
  second_time = @values[second]

  @position[first] = :second
  @position[second] = :second

  p "crossing #{first_time} and #{second_time}"

  first_time > second_time ? first_time : second_time
end

def send_lowest
  value = nil
  @values.each_with_index do |v, i|
    if @position[i] == :second
      value = v
      @position[i] = :first
      break
    end
  end

  p "return #{value}"
  return value
end


def highest_two
  first = nil
  second = nil

  first_arr = @position - [:second]

  if (first_arr.length % 2) == 0
    @values.each_with_index do |v, i|
      if @position[i] == :first
        first = i unless first
        second = i if !second && i != first
      end

      break if first && second
    end
  else
    @values.reverse.each_with_index do |v, i|
      real_index = @values.length - i - 1
      if @position[real_index] == :first
        first = real_index unless first
        second = real_index if !second && real_index != first
      end

      break if first && second
    end
  end

  return first, second
end

#we first send the first two
@total += send_people(0, 1)
#then we get the lowest one from there
@total += send_lowest
#we loop through the rest with highest 2 always being sent
while @position.include?(:first)
  first, second = highest_two
  @total += send_people(first, second)
  @total += send_lowest if @position.include?(:first)
end

p "Total time: #{@total}"

答案 5 :(得分:4)

受@ roc-khalil解决方案启发的另一个Ruby实现

@values = [1,2,5,10]
# @values = [1,2,5,10,20,25]
@left = @values.sort
@right = []
@total_time = 0

def trace(moving)
  puts moving
  puts "State: #{@left} #{@right}"
  puts "Time: #{@total_time}"
  puts "-------------------------"
end

# move right the fastest two
def move_fastest_right!
  fastest_two = @left.shift(2)
  @right = @right + fastest_two
  @right = @right.sort
  @total_time += fastest_two.max
  trace "Moving right: #{fastest_two}"
end

# move left the fastest runner
def move_fastest_left!
  fastest_one = @right.shift
  @left << fastest_one
  @left.sort!
  @total_time += fastest_one
  trace "Moving left: #{fastest_one}"
end

# move right the slowest two
def move_slowest_right!
  slowest_two = @left.pop(2)
  @right = @right + slowest_two
  @right = @right.sort
  @total_time += slowest_two.max
  trace "Moving right: #{slowest_two}"
end

def iterate!
  move_fastest_right!
  return if @left.length == 0

  move_fastest_left!
  move_slowest_right!
  return if @left.length == 0

  move_fastest_left!
end

puts "State: #{@left} #{@right}"
puts "-------------------------"
while @left.length > 0
  iterate!
end

输出:

State: [1, 2, 5, 10] []
-------------------------
Moving right: [1, 2]
State: [5, 10] [1, 2]
Time: 2
-------------------------
Moving left: 1
State: [1, 5, 10] [2]
Time: 3
-------------------------
Moving right: [5, 10]
State: [1] [2, 5, 10]
Time: 13
-------------------------
Moving left: 2
State: [1, 2] [5, 10]
Time: 15
-------------------------
Moving right: [1, 2]
State: [] [1, 2, 5, 10]
Time: 17
-------------------------

答案 6 :(得分:2)

如此小的问题空间,对所有可能性的详尽搜索很简单。广度或深度首先是有效的。这是一个简单的CS问题。

我自己更喜欢传教士和食人族的问题

答案 7 :(得分:1)

17 - 一个非常常见的问题

- &GT; 1-2 = 2
&lt; - 2 = 2
- &GT; 5,10 = 10(没有一个必须返回)
&lt; - 1 = 1
- &GT; 1,2 = 2

所有在另一边
总数= 2 + 2 + 10 + 1 + 2 = 17

通常人们在第一次尝试时将其视为19

答案 8 :(得分:1)

考虑到将有2面,即1面和2面,N个人数应从1面穿越到2面。以L人数限制的方式过桥的逻辑是-

第1步:将L个最快的成员从第1边移到第2边

第2步:将最快的人从Side 2带回到Side 1

第3步:将L个最慢的成员从第1侧移到第2侧

第4步:从Side 2中找回最快的人

重复这些步骤,直到第2步结束时或第4步结束时第1边没有人为止。

这里是n个人数(一次只能有两个人)的C#代码。这将吸收N个人员,可以在运行时指定。然后,它将接受N个人的姓名和时间。输出还指定了最短时间的迭代。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RiverCrossing_Problem
{
    class Program
    {
        static void Main(string[] args)
        {
            Dictionary<string, int> Side1 = new Dictionary<string, int>();
            Dictionary<string, int> Side2 = new Dictionary<string, int>();

            Console.WriteLine("Enter number of persons");
            int n = Convert.ToInt32(Console.ReadLine());

            Console.WriteLine("Enter the name and time taken by each");

            for(int a =0; a<n; a++)
            {
                string tempname = Console.ReadLine();
                int temptime = Convert.ToInt32(Console.ReadLine());
                Side1.Add(tempname, temptime);
            }

            Console.WriteLine("Shortest time and logic:");
            int totaltime = 0;
            int i = 1;
            do
            {
                KeyValuePair<string, int> low1, low2, high1, high2;
                if (i % 2 == 1)
                {
                    LowestTwo(Side1, out low1, out low2);
                    Console.WriteLine("{0} and {1} goes from side 1 to side 2, time taken = {2}", low1.Key, low2.Key, low2.Value);
                    Side1.Remove(low2.Key);
                    Side1.Remove(low1.Key);
                    Side2.Add(low2.Key, low2.Value);
                    Side2.Add(low1.Key, low1.Value);
                    totaltime += low2.Value;

                    low1 = LowestOne(Side2);
                    Console.WriteLine("{0} comes back to side 1, time taken = {1}", low1.Key, low1.Value);
                    totaltime += low1.Value;
                    Side1.Add(low1.Key, low1.Value);
                    Side2.Remove(low1.Key);
                    i++;
                }
                else
                {
                    HighestTwo(Side1, out high1, out high2);
                    Console.WriteLine("{0} and {1} goes from side 1 to side 2, time taken = {2}", high1.Key, high2.Key, high1.Value);
                    Side1.Remove(high1.Key);
                    Side1.Remove(high2.Key);
                    Side2.Add(high1.Key, high1.Value);
                    Side2.Add(high2.Key, high2.Value);
                    totaltime += high1.Value;

                    low1 = LowestOne(Side2);
                    Console.WriteLine("{0} comes back to side 1, time taken = {1}", low1.Key, low1.Value);
                    Side2.Remove(low1.Key);
                    Side1.Add(low1.Key, low1.Value);
                    totaltime += low1.Value;
                    i++;
                }
            } while (Side1.Count > 2);

            KeyValuePair<string, int> low3, low4;
            LowestTwo(Side1, out low3, out low4);
            Console.WriteLine("{0} and {1} goes from side 1 to side 2, time taken = {2}", low3.Key, low4.Key, low4.Value);
            Side2.Add(low4.Key, low4.Value);
            Side2.Add(low3.Key, low3.Value);
            totaltime += low4.Value;

            Console.WriteLine("\n");
            Console.WriteLine("Total Time taken = {0}", totaltime);

        }

        public static void LowestTwo(Dictionary<string, int> a, out KeyValuePair<string, int> low1, out KeyValuePair<string, int> low2)
        {
            Dictionary<string, int> b = a;
            low1 = b.OrderBy(kvp => kvp.Value).First();
            b.Remove(low1.Key);
            low2 = b.OrderBy(kvp => kvp.Value).First();
        }

        public static void HighestTwo(Dictionary<string,int> a, out KeyValuePair<string,int> high1, out KeyValuePair<string,int> high2)
        {
            Dictionary<string, int> b = a;
            high1 = b.OrderByDescending(k => k.Value).First();
            b.Remove(high1.Key);
            high2 = b.OrderByDescending(k => k.Value).First();
        }

        public static KeyValuePair<string, int> LowestOne(Dictionary<string,int> a)
        {
            Dictionary<string, int> b = a;
            return b.OrderBy(k => k.Value).First();
        }
    }
}

在这种情况下,提供的随机输入的样本输出为7,一次要交叉的人数为:

Enter number of persons
7
Enter the name and time taken by each
A
2
B
5
C
3
D
7
E
9
F
4
G
6
Shortest time and logic:
A and C goes from side 1 to side 2, time taken = 3
A comes back to side 1, time taken = 2
E and D goes from side 1 to side 2, time taken = 9
C comes back to side 1, time taken = 3
A and C goes from side 1 to side 2, time taken = 3
A comes back to side 1, time taken = 2
G and B goes from side 1 to side 2, time taken = 6
C comes back to side 1, time taken = 3
A and C goes from side 1 to side 2, time taken = 3
A comes back to side 1, time taken = 2
A and F goes from side 1 to side 2, time taken = 4


Total Time taken = 40

答案 9 :(得分:0)

我以代数方式绘制了可能的解决方案,并以最快的时间出来了。并为代数分配A,B,C,D列表,其中A最小,D最大 最短时间的公式是B + A + D + B + B或3B + A + D. 或者用罗嗦的术语来说,第二快的时间总和为3,并加上最快和最慢。

看着节目,还有一个增加项目的问题。虽然我还没有通过它,但我猜测公式仍然适用,只需添加到第二项目的所有项目时间为3,除了第二慢的时间之外的所有项目的总和。 例如因为4项是3 x秒+第一和第四。 然后5项是3 x秒+第一,第三和第五。 我想用这个程序来解决这个问题。

我也只是看了上面分享的pdf,所以对于更多的项目,它是总和 3 x秒+最快+每个后续对中最慢的总和。

查看优化解决方案的步骤,我们的想法是 - 正确 - 对于两个向右移动最快的项目是第一和第二快, -left-然后加上单个项目的最快回复是最快的项目 - 正确 - 带上最慢的2个项目,这将只占最慢的项目而忽略第二个最慢的项目。 -left - 第二快的项目。 - 最终权利 - 再次获得第一和第二快

所以再次总结=第二快最多3次,最快一次,最慢最慢第二次。

答案 10 :(得分:0)

一个简单的算法是:假设'N'是可以同时穿越并且一个人必须交叉背负火炬的人数

  1. 当人们从第一侧移动到第二侧时,应该优先考虑'N'最慢的步行者
  2. 始终使用最快的步行者从第二侧到第一侧接收火炬
  3. 当人们从第一侧移动到第二侧时,请考虑谁将在下一步将火炬带回来。如果下一步火炬手的速度等于最快的步行者,那么在'N'最慢的步行者中,在当前步骤中,然后选择'N',而不是选择'N'最慢的步行者,如'1'中所示。 '最快的步行者
  4. 以下是一个示例python脚本:https://github.com/meowbowgrr/puzzles/blob/master/bridgentorch.py