28 votes

Algorithme pour diviser un tableau en P sous-réseaux de somme équilibrée

J'ai un grand tableau de longueur N, disons quelque chose comme :

2 4 6 7 6 3 3 3 4 3 4 4 4 3 3 1

Je dois diviser ce tableau en P sous-ensembles (dans cet exemple, P=4 serait raisonnable), de telle sorte que la somme des éléments de chaque sous-réseau soit aussi proche que possible de sigma, c'est-à-dire :

sigma=(sum of all elements in original array)/P

Dans cet exemple, sigma=15 .

Par souci de clarté, un résultat possible serait le suivant :

2 4 6    7 6 3 3   3 4 3 4    4 4 3 3 1
(sums: 12,19,14,15)

J'ai écrit un algorithme très naïf basé sur la façon dont je ferais les divisions à la main, mais je ne sais pas comment imposer la condition qu'une division dont les sommes sont (14,14,14,14,14,19) est pire qu'une division qui est (15,14,16,14,16).

Je vous remercie d'avance.

6voto

Gumbo Points 279147

Tout d'abord, formalisons votre problème d'optimisation en spécifiant l'entrée, la sortie et la mesure pour chaque solution possible (j'espère que cela vous intéresse) :

Étant donné un tableau A d'entiers positifs et un entier positif P , séparer le tableau A en P des sous-réseaux qui ne se chevauchent pas, de sorte que la différence entre la somme de chaque sous-réseau et la somme parfaite des sous-réseaux (somme( A )/ P ) est minimal.

Entrée : Tableau A d'entiers positifs ; P est un nombre entier positif.
Sortie : Tableau SA de P des nombres entiers non négatifs représentant la longueur de chaque sous-réseau de A où la somme des longueurs de ces sous-réseaux est égale à la longueur de A .
Mesure : abs(sum( sa )-somme( A )/ P ) est minimal pour chaque sa ∈ { sa sa \= ( A <em>i </em> , , A <em>i </em>+‍ <em>SA </em><em>j </em> ) pour i \= (Σ SA <em>j </em> ), j de 0 à P -1}.

En entrée y sortie définissent l'ensemble des solutions valides. Les mesure définit une mesure permettant de comparer plusieurs solutions valides. Et puisque nous recherchons une solution qui présente le moins de différence avec la solution parfaite (problème de minimisation), mesure devrait également être minime.

Avec ces informations, il est assez facile de mettre en œuvre le programme measure (ici en Python) :

def measure(a, sa):
    sigma = sum(a)/len(sa)
    diff = 0
    i = 0
    for j in xrange(0, len(sa)):
        diff += abs(sum(a[i:i+sa[j]])-sigma)
        i += sa[j]
    return diff

print measure([2,4,6,7,6,3,3,3,4,3,4,4,4,3,3,1], [3,4,4,5]) # prints 8

Il est maintenant un peu plus difficile de trouver une solution optimale.

Nous pouvons utiliser le Algorithme de retour en arrière pour trouver des solutions valables et utiliser le mesure pour les évaluer. Nous essayons toutes les combinaisons possibles de P nombres entiers non négatifs dont la somme est égale à la longueur( A ) pour représenter toutes les solutions valides possibles. Bien que cela garantisse de ne pas manquer une solution valable, il s'agit essentiellement d'une approche brute, avec l'avantage de pouvoir omettre certaines branches qui ne peuvent pas être meilleures que notre meilleure solution. Par exemple, dans l'exemple ci-dessus, nous n'aurions pas besoin de tester les solutions avec [9, ] ( mesure > 38) si nous avons déjà une solution avec mesure ≤ 38.

En suivant le modèle de pseudocode de Wikipedia, notre bt se présente comme suit :

def bt(c):
    global P, optimum, optimum_diff
    if reject(P,c):
        return
    if accept(P,c):
        print "%r with %d" % (c, measure(P,c))
        if measure(P,c) < optimum_diff:
            optimum = c
            optimum_diff = measure(P,c)
        return
    s = first(P,c)
    while s is not None:
        bt(list(s))
        s = next(P,s)

Les variables globales P , optimum y optimum_diff représente l'instance du problème contenant les valeurs de A , P y sigma ainsi que la solution optimale et sa mesure :

class MinimalSumOfSubArraySumsProblem:
    def __init__(self, a, p):
        self.a = a
        self.p = p
        self.sigma = sum(a)/p

Ensuite, nous spécifions le reject y accept qui sont assez simples :

def reject(P,c):
    return optimum_diff < measure(P,c)
def accept(P,c):
    return None not in c

Il s'agit simplement de rejeter tout candidat dont la mesure est déjà supérieure à notre solution optimale. Et nous acceptons toute solution valide.

En measure est également légèrement modifiée en raison du fait que la fonction c peut désormais contenir None valeurs :

def measure(P, c):
    diff = 0
    i = 0
    for j in xrange(0, P.p):
        if c[j] is None:
            break;
        diff += abs(sum(P.a[i:i+c[j]])-P.sigma)
        i += c[j]
    return diff

Les deux autres fonctionnent first y next sont un peu plus compliquées :

def first(P,c):
    t = 0
    is_complete = True
    for i in xrange(0, len(c)):
        if c[i] is None:
            if i+1 < len(c):
                c[i] = 0
            else:
                c[i] = len(P.a) - t
            is_complete = False
            break;
        else:
            t += c[i]
    if is_complete:
        return None
    return c

def next(P,s):
    t = 0
    for i in xrange(0, len(s)):
        t += s[i]
        if i+1 >= len(s) or s[i+1] is None:
            if t+1 > len(P.a):
                return None
            else:
                s[i] += 1
            return s

En principe, first soit remplace le None dans la liste avec l'un ou l'autre des éléments suivants 0 si ce n'est pas la dernière valeur de la liste ou avec le reste pour représenter une solution valide (petite optimisation ici) si c'est la dernière valeur de la liste, ou il retourne None s'il n'y a pas de None dans la liste. next incrémente simplement l'entier le plus à droite de un ou renvoie None si une augmentation entraîne un dépassement de la limite totale.

Il ne vous reste plus qu'à créer une instance de problème, à initialiser les variables globales et à appeler bt avec la racine :

P = MinimalSumOfSubArraySumsProblem([2,4,6,7,6,3,3,3,4,3,4,4,4,3,3,1], 4)
optimum = None
optimum_diff = float("inf")
bt([None]*P.p)

4voto

Harold Gao Points 81

La réponse de @Gumbo est claire et exploitable, mais consomme beaucoup de temps lorsque length(A) est plus grand que 400 et P plus grand que 8. C'est parce que cet algorithme est une sorte de forçage brutal avec des avantages comme il l'a dit.

En fait, une solution très rapide consiste à utiliser programmation dynamique .

Étant donné un tableau A d'entiers positifs et un entier positif P, séparez le tableau A en P sous-réseaux qui ne se chevauchent pas, de telle sorte que la différence entre la somme de chaque sous-réseau et la somme parfaite des sous-réseaux (somme(A)/P) soit minimale.

Mesure : donde est la somme des éléments du sous-réseau , est la moyenne des sommes des P sous-réseaux.

Cela permet de s'assurer de l'équilibre de la somme, car cela utilise la définition de Écart-type .

En supposant que le tableau A comporte N éléments ; Q(i,j) représente la valeur minimale de la mesure lorsque l'on divise les i derniers éléments de A en j sous-ensembles. D(i,j) moyens (sum(B)-sum(A)/P)^2 lorsque le tableau B est constitué des i~jèmes éléments de A ( 0<=i<=j<N ).

La mesure minimale de la question est de calculer Q(N,P). Et nous trouvons que :

Q(N,P)=MIN{Q(N-1,P-1)+D(0,0); Q(N-2,P-1)+D(0,1); ...; Q(N-1,P-1)+D(0,N-P)}

Il peut donc être résolu de la manière suivante programmation dynamique .

 Q(i,1) = D(N-i,N-1)

 Q(i,j) = MIN{ Q(i-1,j-1)+D(N-i,N-i); 
               Q(i-2,j-1)+D(N-i,N-i+1); 
               ...; 
               Q(j-1,j-1)+D(N-i,N-j)}

L'étape de l'algorithme est donc la suivante :

 1. Cal j=1:

    Q(1,1), Q(2,1)... Q(3,1)

 2. Cal j=2:

    Q(2,2) = MIN{Q(1,1)+D(N-2,N-2)};

    Q(3,2) = MIN{Q(2,1)+D(N-3,N-3); Q(1,1)+D(N-3,N-2)}

    Q(4,2) = MIN{Q(3,1)+D(N-4,N-4); Q(2,1)+D(N-4,N-3); Q(1,1)+D(N-4,N-2)}

 ... Cal j=...

 P. Cal j=P:

    Q(P,P), Q(P+1,P)...Q(N,P)

The final minimum Measure value is stored as Q(N,P)! 
To trace each subarray's length, you can store the 
MIN choice when calculate Q(i,j)=MIN{Q+D...}

pour D(i,j) ;

temps de calcul Q(N,P)

par rapport à la algorithme de forçage brutal consomme temps.

3voto

Alexander Chertov Points 1221

Si je ne me trompe pas, une autre approche est la programmation dynamique.

Vous pouvez définir P [ pos , n ] comme la plus petite "pénalité" possible accumulée jusqu'à la position pos si n ont été créés. Il est évident qu'il existe une position pos' telle que

P[pos', n-1] + penalty(pos', pos) = P[pos, n]

Vous pouvez simplement minimiser sur pos' = 1..pos.

L'implémentation naïve s'exécutera en O(N^2 * M), où N est la taille du tableau original et M le nombre de divisions.

1voto

WishtobeNothing Points 28

Code de travail ci-dessous (j'ai utilisé le langage php). Ce code décide lui-même de la quantité de pièces ;

$main = array(2,4,6,1,6,3,2,3,4,3,4,1,4,7,3,1,2,1,3,4,1,7,2,4,1,2,3,1,1,1,1,4,5,7,8,9,8,0);
$pa=0;
for($i=0;$i < count($main); $i++){
$p[]= $main[$i];
if(abs(15 - array_sum($p)) < abs(15 - (array_sum($p)+$main[$i+1])))
{
$pa=$pa+1;
$pi[] = $i+1;
$pc =  count($pi);

$ba = $pi[$pc-2] ;

$part[$pa] = array_slice( $main,  $ba, count($p));
unset($p);
}
}
print_r($part);
for($s=1;$s<count($part);$s++){
echo '<br>';
echo array_sum($part[$s]);
}

Le code affichera les sommes partielles comme suit

13
14
16
14
15
15
17

0voto

Dukeling Points 31203

Je me demande si la méthode suivante pourrait fonctionner :

Partir de la gauche, dès que sum > sigma La branche de l'utilisateur est divisée en deux, l'une incluant la valeur qui la fait passer, et l'autre non. Traiter récursivement les données vers la droite avec rightSum = totalSum-leftSum y rightP = P-1 .

Ainsi, au départ, la somme = 60

2 4 6 7 6 3 3 3 4 3 4 4 4 3 3 1

Ensuite, pour 2 4 6 7 , sum = 19 > sigma, donc divisé en :

2 4 6     7 6 3 3 3 4 3 4 4 4 3 3 1

2 4 6 7     6 3 3 3 4 3 4 4 4 3 3 1

Ensuite, nous traitons 7 6 3 3 3 4 3 4 4 4 3 3 1 y 6 3 3 3 4 3 4 4 4 3 3 1 con P = 4-1 y sum = 60-12 y sum = 60-19 respectivement.

Il en résulte, je pense, O(P*n).

Cela peut poser un problème lorsque 1 ou 2 valeurs sont de loin les plus importantes, mais pour toute valeur >= sigma, nous pouvons probablement la placer dans sa propre partition (le prétraitement du tableau pour trouver ces valeurs pourrait être la meilleure idée (et réduire la somme de manière appropriée)).

Si elle fonctionne, elle devrait minimiser la somme des erreurs quadratiques (ou s'en approcher), ce qui semble être la mesure souhaitée.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X