Как мне найти ближайших соседей для каждого элемента в списке? - PullRequest
5 голосов
/ 12 октября 2011

У меня есть два набора целых чисел A и B (размер A меньше или равен B), и я хочу ответить на вопрос: "Насколько близко A к * 1006?" *?». Я хочу ответить на этот вопрос, измерив, как далеко вы должны пройти от заданного a в A, чтобы найти b в B.

Конкретная мера, которую я хочу произвести, делает следующее: для каждого a найдите ближайший b, единственный улов - то, что как только я сопоставлю b с a, я больше не могу использовать что b соответствует любому другому a. (РЕДАКТИРОВАТЬ: алгоритм, который я пытаюсь реализовать, всегда предпочитает более короткое совпадение. Поэтому, если b является ближайшим соседом для более чем одного a, выберите a, ближайший к b. Я не конечно, что делать, если несколько a имеют одинаковое расстояние до b, сейчас я выбираю a, предшествующий b, но это совершенно произвольно и не обязательно оптимально.) Мера, которую я Для этих наборов конечный продукт представляет собой гистограмму, показывающую количество пар по вертикальной оси и расстояние между парами по оси X.

Так что если A = {1, 3, 4} и B = {1, 5, 6, 7}, я получу следующие пары a,b: 1,1, 4,5, 3,6. Для этих данных гистограмма должна показывать одну пару с расстоянием ноль, одну пару с расстоянием 1 и одну пару с расстоянием 3.

(Фактический размер этих наборов имеет верхнюю границу около 100 000 элементов, и я считал их с диска, уже отсортированного от низкого до высокого. Целые числа варьируются от 1 до ~ 20 000 000. РЕДАКТИРОВАТЬ: также, элементы A и B являются уникальными, то есть без повторяющихся элементов.)


Решение, которое я нашел, кажется немного неуклюжим. Я использую Perl, но проблема более или менее независима от языка.

  1. Сначала я создаю хеш с одной клавишей для каждого числа, которое появляется в объединении A и B, и значениями, указывающими, появляется ли каждое число в A, B или обоих, например $hash{5} = {a=>1, b=>1}, если число 5 появляется в обоих наборах данных. (Если бы оно появилось только в A, вы бы получили $hash{5} = {a=>1}.)

  2. Далее я перебираю A, чтобы найти все элементы хеша, которые появляются в A и B, отметить их в мере и удалить их из хеша.

  3. Затем я сортирую все хеш-ключи и делаю каждый элемент хеш-точки на своих ближайших соседей, как связанный список, где данный хеш-элемент теперь выглядит как $hash{6} = {b=>1, previous=>4, next=>8}. Связанный список не знает, находятся ли следующий и предыдущий элементы в A или B.

  4. Затем я перебираю пары расстояний, начиная с d=1, и нахожу все пары с расстоянием d, отмечаю их, удаляю из хеша, пока не останется элементов A для сопоставления.

Цикл выглядит так:

for ($d=1; @a > 0; $d++) {
    @left = ();
    foreach $a in @a {
        $next = $a;
        # find closest b ahead of $a, stop searching if you pass $d
        while (exists $hash{$next}{next} && $next - $a < $d) {
            $next = $hash{$next}{next};
        }
        if ($next is in B && $next - $a == $d) {
            # found a pair at distance $d
            mark_in_measure($a, $next);
            remove_from_linked_list($next);
            remove_from_linked_list($a);
            next;
        }

        # do same thing looking behind $a
        $prev = $a;
        ...

        # you didn't find a match for $a
        push @left, $a;
    }
    @a = @left;
}

Этот цикл, очевидно, предпочитает пары, соответствующие b, которые появляются позже, чем a; Я не могу сказать, есть ли разумный способ решить, лучше ли позже, чем раньше (лучше с точки зрения сближения пар). Основная оптимизация, которая меня интересует - это время обработки.

Ответы [ 2 ]

2 голосов
/ 12 октября 2011

Похоже, у вас есть частный случай Задачи назначения (поиск минимального соответствия в взвешенном двудольном графе).

Алгоритм для решения задачи присваивания слишком медленный для вас в точке O (N ^ 3), но я вполне уверен, что вы можете избавиться от некоторых из этих сложностей, используя вашу функцию весов или то, как вам нужна только гистограмма вместо точного соответствия.

1 голос
/ 14 октября 2016
#!/usr/bin/perl

use strict;
use warnings FATAL => 'all';
use diagnostics;  

# http://www.hungarianalgorithm.com/solve.php?c=3-2-6-22--7-2-2-18--13-8-4-12--23-18-14-2&random=1
# https://www.topcoder.com/community/data-science/data-science-tutorials/assignment-problem-and-hungarian-algorithm/
# http://www.cse.ust.hk/~golin/COMP572/Notes/Matching.pdf

my @mat;
my @out_mat;

my $spaces    = 6;
my $precision = 0;

my $N = 10;
my $M = 12;
my $r = 100;

my @array1; my @array2;

for my $i (1..$N) {
    push @array1, sprintf( "%.${precision}f",  rand($r)  );
}

for my $i (1..$M) {
    push @array2, sprintf( "%.${precision}f",  rand($r)  );
}

#@array1 = ( 1, 3, 4);      # $mat[i]->[j] = abs( array1[i] - array2[j] )
#@array2 = ( 1, 5, 6, 7);

#                1     5     6     7

#     1     [    0*    4     5     6 ]

#     3     [    2     2*    3     4 ]

#     4     [    3     1     2*    3 ]

my $min_size  = $#array1 < $#array2 ? $#array1 : $#array2;
my $max_size  = $#array1 > $#array2 ? $#array1 : $#array2;

for (my $i = 0; $i < @array1; $i++){
   my @weight_function;
   for (my $j = 0; $j < @array2; $j++){
      my $dif = sprintf( "%.${precision}f", abs ($array1[$i] - $array2[$j])  );
      #my $dif = sprintf( "%.${precision}f", ($array1[$i] - $array2[$j])**2  ); 
      push @weight_function, $dif;
   }
   push @mat, \@weight_function;
}


# http://cpansearch.perl.org/src/TPEDERSE/Algorithm-Munkres-0.08/lib/Algorithm/Munkres.pm

Algorithm::Munkres::assign(\@mat,\@out_mat);


print "\n\@out_mat index  = [";
for my $index (@out_mat) {
   printf("%${spaces}d", $index);
}
print " ]\n";

print "\@out_mat values = [";

my %hash;
for my $i (0 .. $max_size){
   my $j = $out_mat[$i];
   last if ( $i > $min_size and $#array1 < $#array2 );
   next if ( $j > $min_size and $#array1 > $#array2 );
   my $dif = $mat[$i]->[$j];
   printf( "%${spaces}.${precision}f", $dif );
   $hash{ $dif } { $i } { 'index_array1' } = $i;
   $hash{ $dif } { $i } { 'index_array2' } = $j;
   $hash{ $dif } { $i } { 'value_array1' } = $array1[$i];
   $hash{ $dif } { $i } { 'value_array2' } = $array2[$j]; 
}

print " ]\n\n";


my $soma_da_dif = 0;

foreach my $min_diferenca ( sort { $a <=> $b } keys %hash ){
   foreach my $k ( sort { $a <=> $b } keys %{$hash{$min_diferenca}} ){
      $soma_da_dif += $min_diferenca;
      my $index_array1 = $hash{ $min_diferenca } { $k } { 'index_array1' };
      my $index_array2 = $hash{ $min_diferenca } { $k } { 'index_array2' };
      my $value_array1 = $hash{ $min_diferenca } { $k } { 'value_array1' };
      my $value_array2 = $hash{ $min_diferenca } { $k } { 'value_array2' };
      printf( "   index (%${spaces}.0f,%${spaces}.0f), values (%${spaces}.${precision}f,%${spaces}.${precision}f), dif = %${spaces}.${precision}f\n", 
              $index_array1, $index_array2, $value_array1, $value_array2, $min_diferenca );

   }
}
print "\n\nSum = $soma_da_dif\n";





#-------------------------------------------------#
#------------------ New-Package ------------------# 

{ # start scope block

package Algorithm::Munkres;

use 5.006;
use strict;
use warnings;

require Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw( assign );
our $VERSION = '0.08';

...
... <---- copy all the 'package Algorithm::Munkres' here
...

return $minval;
}

1;  # don't forget to return a true value from the file

} # end scope block
...