[Moscow.pm] Равномерная, рандомная сортировка
Ruslan Zakirov
ruslan.zakirov на gmail.com
Вт Дек 22 15:31:39 PST 2009
Привет, dvhillard :)
Введение:
1) сгруппируем одинаковые элементы
2) введем L(g) - длина группы
3) задача отсутствия повторений не решается, если существует группа i,
где L(G(i)) = MAX(L(G(j))) для любого j и L(G(i)) > SUM(L(G(j)))+1 для
люого j != i. По простецки - если группа с максимальной длинной
длиннее объединения всех остальных групп. Доказать элементарно.
4) Введем понятие группа i в критичном состоянии, если L(G(i)) =
SUM(L(G(j))) + 1 где j != i. Доказательством от противного легко
доказывается, что в наборе не может быть две группы в критичном
состоянии. Понятно, что такая группа может иметь только максимальную
длинну во всем наборе.
Алгоритм:
0) Конец, если все группы пусты
1) Если есть группа в критичном состоянии, то берем элемент из нее. к пункту 0
2) Иначе выбираем "случайно" группу (пропорционально длиннам),
исключая группу с предыдущим элементом. К пункту 0
Одна и таже группа не может стать два раза подряд критичной, а значит
мы не сможем нарушить условие неповторения элементов. Если мы выбрали
элемент в пункте 2, то невозможно, что эта группа будет критичной на
следующем цикле, а следовательно не нарушается условие неповторения.
Из всего вышесказанного следует, что решение существует, при
соблюдении условия 3.
Вот и простой код в лоб:
use strict;
use warnings;
my @e = qw(a a a a a b b b c c c c d e e e e e e e e e e e e e e);
my %g;
$g{$_}++ foreach @e;
my @res;
my $last;
while ( keys %g ) {
my $pick = find_critical();
unless ( defined $pick ) {
$pick = pick_except_last();
}
$g{$pick}--;
delete $g{$pick} unless $g{$pick};
push @res, $last = $pick;
}
print join( ' ', @e ), "\n";
print join( ' ', @res ), "\n";
sub find_critical {
my $critical;
my ($max, $rest) = (0, 0);
foreach my $e ( keys %g ) {
if ( $g{$e} > $max ) {
$rest += $max;
$max = $g{$e};
$critical = $e;
} else {
$rest += $g{$e};
}
}
return undef if $max < $rest + 1;
return $critical if $max == $rest + 1;
die "No solution, too many '$critical' elements";
}
sub pick_except_last {
my @tmp;
while ( my ($k,$v) = each %g ) {
next if defined $last and $last eq $k;
push @tmp, ($k) x $v;
}
return $tmp[ int rand @tmp ];
}
--
Best regards, Ruslan.
Подробная информация о списке рассылки Moscow-pm