给定 n 个点,每个点都有自己的范围,调整所有点以最大化相邻点的最小距离

Given n points where each point has its own range, adjust all points to maximize the minimum distance of adjacent points

一维取n个点:

每个点都可以在上面箭头指示的一定范围内移动。这些范围是已知的。范围可以重叠。如何调整这些点,使相邻点的最小距离最大化?

我也可以接受近似值。

编辑: 我不是真的在寻找代码,而是在寻找一般策略。 我真的不知道如何解决这个问题。

最初,我认为贪心算法可能会起作用。

  1. 设p0为起点,pn为终点。设dist(p0,p1)表示p0到p1.Well的距离,显然p0尽量往左,pn尽量往右

  2. 接下来,求dist(p0,pn)/n-1。这表示每个点之间应尽可能分散的最佳距离。将 p1 移动到接近此距离。

  3. 求 dist(p1, pn)/n-2。将 p2 移动到尽可能靠近此距离。

  4. 重复其余点数。

这不起作用,因为稍后调整另一个指针可能会破坏之前的点。

这是一些 Ruby 代码,后面是示例结果。这个想法是从将点随机分配到其范围内的值开始。然后我们重复执行以下操作:

1. Find the minimum gap
2. Find all points that border a minimum gap
3. Re-randomize the values of those points

一直以来,我们都记得迄今为止最好的解决方案。这不会给你最好的结果,但应该给你一个相当好的结果。

对于示例 运行,我通过在大约 1100 的范围内均匀分配 100 个点来给出一组 'good' 点,然后给每个点一个围绕该值的范围。该算法只是以随机顺序给出了范围。可以获得一组输入点,其中最佳可能最小间隙很小或为零,这是任何算法都无法解决的。

def test(n, range, max_attempts)
  points = get_points_with_optimal_min_gap(n, range)
  puts "input: #{points}"
  allocation = allocate_points(points, max_attempts)
  puts "solution: #{allocation.keys.sort}"
  puts "best gap: #{get_min_gap(allocation, points)}" 
  puts "\n\nallocation:"
  allocation.keys.sort.each do |k|
    puts "  #{k} => #{allocation[k]}"
  end
end

def get_points_with_optimal_min_gap(n, range)
  points = []
  1.upto(n) do |num|
    points.append([[0, num * range/n - rand(5*range/n)].max, num * range/n + rand(5*range/n)])
  end
  return points.shuffle
end




def allocate_points(input_points, max_attempts)
  val_to_points = Hash.new {|h, k| h[k] = []}
  input_points.each do |point|
    val = get_rand_val_for_point(point)
    val_to_points[val].append(point)
  end
  
  
  best_min_gap = -1
  best_allocation = nil
  attempts = 0
  while attempts < max_attempts do
    attempts += 1
    min_gap = get_min_gap(val_to_points, input_points)
    if min_gap > best_min_gap
      best_min_gap = min_gap
      best_allocation = deep_copy(val_to_points)
      puts "found new gap after #{attempts} tries: #{best_min_gap}"
      attempts = 0
    end
    vals_to_adjust = get_vals_bordering_min_gap(val_to_points, min_gap)
    points_to_reallocate = []
    vals_to_adjust.each do |val|
      points_to_reallocate += val_to_points.delete(val)
    end
  

    
    points_to_reallocate.each do |point|
      new_val = get_rand_val_for_point(point)
      val_to_points[new_val].append(point)
    end
  end
  return best_allocation
end


def get_vals_bordering_min_gap(val_to_points, min_gap)
  vals_bordering_min_gap = Set.new()
  if min_gap == 0
    val_to_points.each do |val, points|
      vals_bordering_min_gap.add(val) if points.size > 1
    end
  else
    sorted_vals = val_to_points.keys.sort
    
    prior_val = sorted_vals[0]
  
    1.upto(sorted_vals.size - 1) do |i|
      cur_val = sorted_vals[i]
      cur_gap = cur_val - prior_val
      if cur_gap == min_gap
        vals_bordering_min_gap.add(cur_val)
        vals_bordering_min_gap.add(prior_val)
      end
      prior_val = cur_val
    end
  end
  return vals_bordering_min_gap.to_a
end


def get_min_gap(val_to_points, input_points)
  return 0 if val_to_points.size < input_points.size
  
  sorted_vals = val_to_points.keys.sort
  prior_val = sorted_vals[0]
  
  min_gap = nil
  1.upto(sorted_vals.size - 1) do |i|
    cur_val = sorted_vals[i]
    min_gap = cur_val - prior_val if min_gap.nil? || cur_val - prior_val < min_gap
    prior_val = cur_val
    return min_gap if min_gap == 1
  end
  return min_gap
end


def get_rand_val_for_point(point)
  return point[0] + rand(point[1] - point[0] + 1)
end

def deep_copy(int_to_arr_of_int_pairs)
  new_hash = Hash.new {|h, k| h[k] = []}
  int_to_arr_of_int_pairs.each do |int, arr|
    arr.each do |int_pair|
      new_hash[int].append([int_pair[0], int_pair[1]])
    end
  end
  return new_hash
end

示例结果

input: [[207, 258], [754, 826], [957, 993], [127, 158], [332, 352], [539, 615], [213, 236], [572, 590], [668, 712], [802, 823], [385, 427], [595, 641], [924, 1018], [20, 50], [698, 749], [318, 335], [64, 120], [462, 521], [708, 760], [513, 569], [806, 877], [364, 380], [732, 799], [896, 923], [947, 997], [250, 281], [798, 857], [717, 795], [542, 583], [910, 964], [277, 315], [168, 221], [263, 337], [858, 920], [316, 348], [646, 705], [557, 573], [293, 315], [48, 131], [488, 522], [252, 271], [269, 326], [43, 106], [673, 728], [399, 432], [140, 184], [564, 630], [906, 973], [508, 566], [0, 17], [493, 547], [782, 817], [184, 246], [230, 305], [5, 90], [446, 507], [429, 449], [822, 917], [35, 127], [630, 699], [888, 944], [899, 917], [179, 201], [715, 785], [40, 65], [338, 415], [908, 958], [2, 31], [469, 517], [649, 718], [96, 150], [380, 428], [347, 401], [197, 289], [849, 912], [603, 642], [805, 830], [564, 629], [19, 88], [80, 151], [593, 660], [932, 994], [479, 541], [291, 331], [175, 192], [842, 877], [987, 1028], [382, 432], [796, 884], [102, 160], [535, 587], [621, 696], [461, 476], [131, 170], [437, 469], [343, 410], [660, 713], [122, 171], [699, 752], [776, 782]]
found new gap after 1 tries: 0
found new gap after 2 tries: 1
found new gap after 8 tries: 2
found new gap after 9 tries: 3
found new gap after 35 tries: 4
found new gap after 87 tries: 5
found new gap after 384 tries: 6
found new gap after 8043 tries: 7
solution: [4, 12, 20, 31, 43, 51, 59, 68, 82, 102, 110, 119, 127, 134, 141, 148, 161, 172, 179, 188, 202, 209, 223, 233, 244, 251, 258, 273, 284, 299, 308, 326, 335, 344, 351, 361, 369, 376, 383, 395, 408, 415, 423, 430, 438, 454, 468, 482, 491, 499, 508, 515, 524, 532, 539, 549, 557, 574, 582, 589, 596, 615, 623, 634, 642, 649, 656, 663, 676, 693, 720, 728, 741, 748, 758, 769, 776, 786, 801, 808, 815, 827, 834, 842, 851, 858, 865, 872, 882, 899, 911, 935, 943, 952, 962, 974, 987, 996, 1011, 1028]
best gap: 7


allocation:
  4 => [[0, 17]]
  12 => [[5, 90]]
  20 => [[2, 31]]
  31 => [[20, 50]]
  43 => [[19, 88]]
  51 => [[40, 65]]
  59 => [[35, 127]]
  68 => [[43, 106]]
  82 => [[64, 120]]
  102 => [[96, 150]]
  110 => [[80, 151]]
  119 => [[48, 131]]
  127 => [[122, 171]]
  134 => [[102, 160]]
  141 => [[140, 184]]
  148 => [[127, 158]]
  161 => [[131, 170]]
  172 => [[168, 221]]
  179 => [[179, 201]]
  188 => [[175, 192]]
  202 => [[184, 246]]
  209 => [[207, 258]]
  223 => [[197, 289]]
  233 => [[213, 236]]
  244 => [[230, 305]]
  251 => [[250, 281]]
  258 => [[252, 271]]
  273 => [[263, 337]]
  284 => [[269, 326]]
  299 => [[277, 315]]
  308 => [[293, 315]]
  326 => [[291, 331]]
  335 => [[318, 335]]
  344 => [[316, 348]]
  351 => [[332, 352]]
  361 => [[347, 401]]
  369 => [[343, 410]]
  376 => [[364, 380]]
  383 => [[338, 415]]
  395 => [[382, 432]]
  408 => [[399, 432]]
  415 => [[380, 428]]
  423 => [[385, 427]]
  430 => [[429, 449]]
  438 => [[437, 469]]
  454 => [[446, 507]]
  468 => [[461, 476]]
  482 => [[479, 541]]
  491 => [[469, 517]]
  499 => [[462, 521]]
  508 => [[488, 522]]
  515 => [[493, 547]]
  524 => [[513, 569]]
  532 => [[508, 566]]
  539 => [[535, 587]]
  549 => [[539, 615]]
  557 => [[557, 573]]
  574 => [[542, 583]]
  582 => [[572, 590]]
  589 => [[564, 629]]
  596 => [[564, 630]]
  615 => [[595, 641]]
  623 => [[603, 642]]
  634 => [[630, 699]]
  642 => [[593, 660]]
  649 => [[621, 696]]
  656 => [[649, 718]]
  663 => [[646, 705]]
  676 => [[660, 713]]
  693 => [[668, 712]]
  720 => [[673, 728]]
  728 => [[699, 752]]
  741 => [[698, 749]]
  748 => [[717, 795]]
  758 => [[708, 760]]
  769 => [[715, 785]]
  776 => [[776, 782]]
  786 => [[732, 799]]
  801 => [[754, 826]]
  808 => [[802, 823]]
  815 => [[782, 817]]
  827 => [[805, 830]]
  834 => [[806, 877]]
  842 => [[796, 884]]
  851 => [[798, 857]]
  858 => [[822, 917]]
  865 => [[842, 877]]
  872 => [[858, 920]]
  882 => [[849, 912]]
  899 => [[899, 917]]
  911 => [[896, 923]]
  935 => [[906, 973]]
  943 => [[888, 944]]
  952 => [[908, 958]]
  962 => [[910, 964]]
  974 => [[957, 993]]
  987 => [[932, 994]]
  996 => [[947, 997]]
  1011 => [[924, 1018]]
  1028 => [[987, 1028]]

----更新----

我添加了代码以尝试找到给定顺序的最佳间隙。这明显更好。

allocate_points() 改变了,我添加了一个名为 attempt_to_achieve_min_gap() 的新方法。

def attempt_to_achieve_min_gap(val_to_points, target_min_gap)
  new_val_to_points = Hash.new {|h, k| h[k] = []}
  
  leftmost_val = val_to_points.keys.min
  leftmost_point = val_to_points[leftmost_val][0]
  new_val_to_points[leftmost_point[0]].append(leftmost_point)
  
  sorted_vals = val_to_points.keys.sort
  prior_val = leftmost_val
  1.upto(sorted_vals.length - 1) do |i|
    cur_val = sorted_vals[i]
    cur_point = val_to_points[cur_val][0]
    target_val = prior_val + target_min_gap
    if target_val <= cur_point[0]
      new_val_to_points[cur_point[0]].append(cur_point)
      prior_val = cur_point[0]
    elsif target_val <= cur_point[1]
      new_val_to_points[target_val].append(cur_point)
      prior_val = target_val
    else
      return false
    end
  end
  return new_val_to_points
end


def allocate_points(input_points, max_attempts)
  val_to_points = Hash.new {|h, k| h[k] = []}
  input_points.each do |point|
    val = get_rand_val_for_point(point)
    val_to_points[val].append(point)
  end
  
  
  best_min_gap = -1
  best_allocation = nil
  attempts = 0
  while attempts < max_attempts do
    attempts += 1
    min_gap = get_min_gap(val_to_points, input_points)
    if min_gap > 0
      found_improvement = true
      while found_improvement
        found_improvement = false
        trial_min_gap = [min_gap, best_min_gap].max + 1
        improved_results = attempt_to_achieve_min_gap(val_to_points, trial_min_gap)
        if improved_results
          found_improvement = true
          min_gap = trial_min_gap
          puts "found improvement!: #{min_gap}"
          val_to_points = improved_results
        end
      end
    end
    if min_gap > best_min_gap
      best_min_gap = min_gap
      best_allocation = deep_copy(val_to_points)
      puts "found new gap after #{attempts} tries: #{best_min_gap}"
      attempts = 0
    end
    vals_to_adjust = get_vals_bordering_min_gap(val_to_points, min_gap)
    points_to_reallocate = []
    vals_to_adjust.each do |val|
      points_to_reallocate += val_to_points.delete(val)
    end
  

    
    points_to_reallocate.each do |point|
      new_val = get_rand_val_for_point(point)
      val_to_points[new_val].append(point)
    end
  end
  return best_allocation
end

新样本结果:

input: [[0, 21], [787, 819], [614, 642], [503, 553], [771, 854], [701, 740], [768, 807], [475, 480], [223, 251], [431, 473], [19, 67], [711, 764], [650, 673], [381, 401], [832, 907], [303, 357], [414, 480], [201, 215], [130, 182], [233, 269], [335, 417], [637, 704], [193, 242], [566, 632], [854, 927], [545, 577], [548, 574], [552, 607], [812, 868], [949, 962], [290, 334], [299, 372], [0, 31], [234, 304], [488, 540], [132, 215], [975, 994], [389, 406], [378, 437], [685, 721], [410, 453], [750, 800], [856, 919], [237, 270], [322, 355], [964, 1018], [67, 90], [925, 1015], [736, 792], [755, 823], [29, 78], [709, 761], [828, 897], [873, 927], [464, 533], [332, 380], [788, 831], [354, 413], [500, 545], [144, 196], [96, 138], [925, 982], [0, 67], [183, 207], [83, 116], [665, 679], [843, 904], [654, 694], [892, 955], [179, 203], [56, 87], [575, 636], [484, 542], [380, 439], [592, 689], [419, 431], [654, 700], [907, 978], [136, 151], [242, 313], [96, 126], [975, 1023], [449, 491], [866, 924], [117, 177], [291, 351], [460, 485], [959, 1004], [797, 846], [210, 277], [207, 240], [138, 184], [536, 606], [710, 774], [695, 714], [71, 83], [617, 629], [284, 302], [576, 633], [88, 137]]
found new gap after 1 tries: 0
found improvement!: 2
found improvement!: 3
found improvement!: 4
found improvement!: 5
found improvement!: 6
found new gap after 1 tries: 6
found improvement!: 7
found new gap after 5 tries: 7
found improvement!: 8
found improvement!: 9
found new gap after 2 tries: 9
found improvement!: 10
found new gap after 5686 tries: 10
solution: [0, 12, 22, 32, 42, 56, 67, 77, 87, 97, 107, 117, 127, 137, 147, 157, 167, 177, 187, 197, 207, 217, 227, 237, 247, 257, 267, 277, 287, 297, 307, 317, 327, 337, 347, 357, 367, 377, 389, 399, 409, 419, 429, 439, 449, 459, 469, 479, 489, 499, 509, 519, 529, 539, 549, 559, 569, 579, 589, 599, 614, 624, 634, 644, 654, 664, 674, 684, 694, 704, 714, 724, 734, 744, 754, 764, 774, 784, 794, 804, 814, 824, 834, 844, 854, 864, 874, 884, 894, 904, 914, 925, 935, 949, 959, 975, 985, 995, 1005, 1015]
best gap: 10


allocation:
  0 => [[0, 31]]
  12 => [[0, 21]]
  22 => [[0, 67]]
  32 => [[19, 67]]
  42 => [[29, 78]]
  56 => [[56, 87]]
  67 => [[67, 90]]
  77 => [[71, 83]]
  87 => [[83, 116]]
  97 => [[88, 137]]
  107 => [[96, 126]]
  117 => [[117, 177]]
  127 => [[96, 138]]
  137 => [[132, 215]]
  147 => [[136, 151]]
  157 => [[144, 196]]
  167 => [[130, 182]]
  177 => [[138, 184]]
  187 => [[179, 203]]
  197 => [[183, 207]]
  207 => [[201, 215]]
  217 => [[207, 240]]
  227 => [[223, 251]]
  237 => [[193, 242]]
  247 => [[237, 270]]
  257 => [[210, 277]]
  267 => [[233, 269]]
  277 => [[234, 304]]
  287 => [[242, 313]]
  297 => [[284, 302]]
  307 => [[291, 351]]
  317 => [[290, 334]]
  327 => [[303, 357]]
  337 => [[322, 355]]
  347 => [[299, 372]]
  357 => [[354, 413]]
  367 => [[335, 417]]
  377 => [[332, 380]]
  389 => [[389, 406]]
  399 => [[381, 401]]
  409 => [[378, 437]]
  419 => [[419, 431]]
  429 => [[380, 439]]
  439 => [[410, 453]]
  449 => [[431, 473]]
  459 => [[414, 480]]
  469 => [[460, 485]]
  479 => [[475, 480]]
  489 => [[449, 491]]
  499 => [[464, 533]]
  509 => [[488, 540]]
  519 => [[484, 542]]
  529 => [[500, 545]]
  539 => [[503, 553]]
  549 => [[545, 577]]
  559 => [[548, 574]]
  569 => [[566, 632]]
  579 => [[552, 607]]
  589 => [[536, 606]]
  599 => [[576, 633]]
  614 => [[614, 642]]
  624 => [[617, 629]]
  634 => [[575, 636]]
  644 => [[592, 689]]
  654 => [[650, 673]]
  664 => [[637, 704]]
  674 => [[665, 679]]
  684 => [[654, 694]]
  694 => [[654, 700]]
  704 => [[695, 714]]
  714 => [[685, 721]]
  724 => [[709, 761]]
  734 => [[701, 740]]
  744 => [[711, 764]]
  754 => [[750, 800]]
  764 => [[710, 774]]
  774 => [[768, 807]]
  784 => [[736, 792]]
  794 => [[787, 819]]
  804 => [[755, 823]]
  814 => [[788, 831]]
  824 => [[771, 854]]
  834 => [[797, 846]]
  844 => [[812, 868]]
  854 => [[828, 897]]
  864 => [[832, 907]]
  874 => [[843, 904]]
  884 => [[866, 924]]
  894 => [[873, 927]]
  904 => [[856, 919]]
  914 => [[854, 927]]
  925 => [[925, 982]]
  935 => [[892, 955]]
  949 => [[949, 962]]
  959 => [[907, 978]]
  975 => [[975, 994]]
  985 => [[964, 1018]]
  995 => [[959, 1004]]
  1005 => [[925, 1015]]
  1015 => [[975, 1023]]

您可以对间隙大小进行二进制搜索:执行现有的步骤 1 和 2 以获得最佳可实现间隙大小的上限 gap。设置 delta = gap / 2。现在继续将其他点精确 gap 分开布置,直到成功完成或导致不可能的情况:如果完成,则设置 gap += delta,否则设置 gap -= delta。无论哪种方式,设置 delta /= 2 并重新运行。重复直到 delta 低于您想要的精度阈值。