32 votes

Algorithme permettant de déterminer toutes les façons possibles de supprimer un groupe de valeurs d'une séquence.

J'essaie de déterminer de combien de façons différentes je peux supprimer un groupe de valeurs d'une séquence, en laissant la séquence originale dans l'ordre (stable) et en m'assurant de ne supprimer qu'une seule valeur d'instance chacune de la séquence originale. Par exemple, si j'avais [1,2,1,3,1,4,4] et je veux enlever [1,4,4] mes combinaisons résultantes seraient :

[1,2,1,3,1,4,4] \ [1,4,4] = [ [2,1,3,1], [1,2,3,1], [1,2,1,3] ]

ou [1,2,1,3,1,4,4] \ [1,1] = [ [2,3,1,4,4], [1,2,3,4,4], [2,1,3,4,4] ]

J'ai un code javascript que j'ai écrit pour générer des combinaisons de toutes les valeurs d'un tableau sans suppression et la partie suppression semble devoir être facile mais je ne vois pas l'algorithme lorsqu'il faut potentiellement supprimer plusieurs valeurs plusieurs fois.

31voto

m69 Points 987

(Comme il n'était pas clair dans la version originale de la question si vous vouliez supprimer une sous-séquence ou une liste non ordonnée, j'ai mis à jour ma réponse pour traiter les deux cas).

1. Suppression d'une sous-séquence dans l'ordre

Nous obtenons une séquence de valeurs, par exemple [1,5,1,3,4,2,3,2,1,3,1,2] et d'une sous-séquence de valeurs à supprimer de la première séquence, par ex. [1,3,2,1] . Si nous trouvons où chaque instance des valeurs se situe dans la séquence, nous obtenons un graphique comme celui-ci :

all connections

Trouver toutes les façons dont les valeurs peuvent être retirées de la séquence, dans l'ordre, signifie alors trouver toutes les façons dont les valeurs à retirer de la ligne inférieure peuvent être reliées à une instance de la séquence, sans qu'aucune ligne ne se croise, par exemple :

example solution

Pour éviter de supprimer des valeurs d'une manière qui ne mène pas à une solution valide (par exemple, en commençant par supprimer la valeur 1 la plus à droite, après quoi il n'y a pas de valeur 3 qui puisse être supprimée), nous allons d'abord supprimer toutes les connexions dans le graphe qui mènent à de telles solutions invalides.

Cela se fera en itérant deux fois sur la sous-séquence. Tout d'abord, nous procédons de gauche à droite, et pour chaque valeur, nous examinons sa connexion la plus à gauche, et supprimons toutes les connexions de la valeur à sa droite qui rencontrent ou croisent cette connexion ; par exemple, en considérant la connexion la plus à gauche de la valeur 2, deux connexions de la valeur 1 à sa droite (indiquées en rouge) sont supprimées car elles croisent cette connexion :

removing crossed connections ltr

Dans l'étape suivante, nous allons de droite à gauche, et pour chaque valeur, nous regardons sa connexion la plus à droite, et supprimons toutes les connexions de la valeur sur sa gauche qui rencontrent ou croisent cette connexion ; par exemple, lorsque nous considérons la connexion la plus à droite de la valeur 1 sur la droite, la connexion la plus à droite de la valeur 2 sur sa gauche (indiquée en rouge) est supprimée car elle croise cette connexion :

removing crossed connections rtl

Nous obtenons alors le graphique simplifié ci-dessous. Les combinaisons sont ensuite réalisées en combinant chaque instance connectée de chaque valeur de la sous-séquence, en utilisant, par exemple, la récursion : on itère sur les options de la première valeur de la sous-séquence, et à chaque fois on récure avec le reste de la sous-séquence, de sorte que l'option de la première valeur soit combinée avec toutes les options des autres valeurs.

simplified graph

Il peut y avoir des connexions croisées dans le graphique simplifié, mais elles ne posent plus de problème. Dans l'exemple, vous verrez que même si la bonne connexion est choisie pour la valeur 3, il existe une connexion pour la valeur 2 qui ne se croise pas avec elle :

example solution in simplified graph

La transposition en code est relativement simple ; vous trouverez, sous l'extrait de code, un exemple d'exécution du code avec les données de l'exemple.

function removeSubSeq(seq, sub) {
    var posi = []; // list position of values in seq, then connect to positions in sub
    sub.forEach(function(elem) {posi[elem] = []});
    seq.forEach(function(elem, index) {if (posi[elem]) posi[elem].push(index)});
    var conn = sub.map(function(elem) {return posi[elem].slice()});

    for (var i = 1; i < conn.length; i++) {
        var left = conn[i - 1][0];
        while (conn[i][0] <= left) {
            conn[i].shift();                // remove crossed connection left-to-right
        }
    }
    for (var i = conn.length - 2; i >= 0; i--) {
        var right = conn[i + 1][conn[i + 1].length - 1];
        while (conn[i][conn[i].length - 1] >= right) {
            conn[i].pop();                  // remove crossed connection right-to-left
        }
    }
    var combinations = [], result = [];
    combine(0, -1, []);                     // initiate recursion to find combinations
    for (var i = 0; i < combinations.length; i++) {
        result[i] = seq.slice();            // create copies of seq and remove values
        for (var j = combinations[i].length - 1; j >= 0; j--) {
            result[i].splice(combinations[i][j], 1);
        }
    }
    return result;

    function combine(step, prev, comb) {    // generate combinations using recursion
        for (var i = 0; i < conn[step].length; i++) {
            var curr = conn[step][i];
            if (prev >= curr) continue;     // skip crossed connection
            if (step + 1 == conn.length) combinations.push(comb.concat([curr]));
            else combine(step + 1, curr, comb.concat([curr]));
        }
    }
}
var result = removeSubSeq([1,5,1,3,4,2,3,2,1,3,1,2], [1,3,2,1]);
for (var i in result) document.write(result[i] + "<br>");

Le code effectue les étapes suivantes :

  • Créer un tableau associatif de la position des instances de chaque valeur présente dans sub :

    posi[1] = [0,2,8,10], posi[2] = [5,7,11], posi[3] = [3,6,9]}

  • Créez un tableau 2D qui relie les positions de la sous-séquence à celles de la séquence :

    conn = [[0,2,8,10],[3,6,9],[5,7,11],[0,2,8,10]]

  • Passez en revue le tableau de gauche à droite et supprimez les connexions croisées :

    conn = [[0,2,8,10],[3,6,9],[5,7,11],[8,10]]

  • Passez en revue le tableau de droite à gauche et supprimez les connexions croisées :

    conn = [[0,2],[3,6],[5,7],[8,10]]

  • Générer toutes les combinaisons des positions en utilisant la récursion :

    combinations = [[0,3,5,8],[0,3,5,10],[0,3,7,8],[0,3,7,10], [0,6,7,8],[0,6,7,10],[2,3,5,8],[2,3,5,10], [2,3,7,8],[2,3,7,10],[2,6,7,8],[2,6,7,10]]

  • Utilisez les combinaisons pour supprimer les valeurs des copies de la séquence (voir l'extrait de code).


2. Suppression d'une liste non ordonnée de valeurs

Si la liste des valeurs à retirer n'est pas nécessairement une sous-séquence de la séquence principale, et que les valeurs peuvent être retirées dans n'importe quel ordre, la même méthode que ci-dessus peut être utilisée, avec un assouplissement des règles de connexion croisée :

La suppression des connexions croisées de la liste des positions, et l'évitement des connexions croisées lors de la génération des combinaisons, ne doit être effectuée que pour des valeurs identiques.

Dans cet exemple, seules les connexions croisées pour les valeurs dupliquées 1 sont supprimées ; d'abord de gauche à droite :

removing crossed connections ltr

et ensuite de droite à gauche :

removing crossed connections rtl

Le résultat est ce graphique simplifié, avec les connexions croisées problématiques pour la valeur 1 supprimées, et les connexions croisées pour les valeurs 2 et 3 restantes :

simplified graph

Vous trouverez ci-dessous un exemple de code adapté de la version pour les sous-séquences ; seules quelques lignes du code sont modifiées, comme indiqué par des commentaires (et j'ai également utilisé une méthode différente pour supprimer les valeurs de la séquence). La liste des valeurs à supprimer est triée au départ, de sorte que les valeurs identiques sont regroupées, pour faciliter le suivi des valeurs identiques.

function removeSubList(seq, sub) {
    sub.sort(function(a, b) {return a - b});       /* SORT SUB-LIST FIRST */
    var posi = []; // list position of values in seq, then connect to positions in sub
    sub.forEach(function(elem) {posi[elem] = []});
    seq.forEach(function(elem, index) {if (posi[elem]) posi[elem].push(index)});
    var conn = sub.map(function(elem) {return posi[elem].slice()});

    for (var i = 1; i < conn.length; i++) {
        if (sub[i - 1] != sub[i]) continue;        /* SKIP FOR NON-IDENTICAL VALUES */
        var left = conn[i - 1][0];
        while (conn[i][0] <= left) {
            conn[i].shift();                // remove crossed connection left-to-right
        }
    }
    for (var i = conn.length - 2; i >= 0; i--) {
        if (sub[i] != sub[i + 1]) continue;        /* SKIP FOR NON-IDENTICAL VALUES */
        var right = conn[i + 1][conn[i + 1].length - 1];
        while (conn[i][conn[i].length - 1] >= right) {
            conn[i].pop();                  // remove crossed connection right-to-left
        }
    }
    var combinations = [], result = [];
    combine(0, -1, []);                     // initiate recursion to find combinations
    for (var i = 0; i < combinations.length; i++) {
        var s = seq.slice();                // create copy of seq and delete values
        combinations[i].forEach(function(elem) {delete s[elem]});
        result[i] = s.filter(function(elem) {return elem != undefined});
    }
    return result;

    function combine(step, prev, comb) {    // generate combinations using recursion
        for (var i = 0; i < conn[step].length; i++) {
            var curr = conn[step][i];
            if (prev >= curr && seq[prev] == seq[curr]) continue;   /* SKIP FOR NIV */
            if (step + 1 == conn.length) combinations.push(comb.concat([curr]));
            else combine(step + 1, curr, comb.concat([curr]));
        }
    }
}
var result = removeSubList([1,5,1,3,4,2,3,2,1,3,1,2], [1,3,1,2,1]);
for (var i in result) document.write(result[i] + "<br>");

6voto

amit Points 74385

Cela peut être fait avec une simple combinatoire.

Pour simplifier, disons que les valeurs de la liste originale sont les suivantes 1,2,3,...,n .
Soit a[i] soit le nombre d'occurrences de i dans la liste originale.
Soit b[i] soit le nombre d'occurrences de i dans la liste des suppressions de valeur

Le nombre d'options pour réduire i es Choose(a[i],b[i]) = a[i]!/((a[i]-b[i])!b[i]!)

Puisque vous combinez toutes ces possibilités en fermeture "ET", le nombre total de possibilités est :

Choose(a[1],b[1]) * Choose(a[2],b[2]) * ... * Choose(a[n], b[n])

En ce qui concerne les valeurs qui ne font pas partie de l'ensemble de réduction, vous n'avez pas à vous en préoccuper puisque leur valeur dans b sera 0, et Choose(x,0) = 1 pour tous x


Cela vous laisse une solution en temps linéaire (en supposant que vous pouvez calculer Choose(.,.) en temps constant après avoir effectué un prétraitement pour mettre en cache les valeurs factorielles.


Dans votre exemple, vous avez :

a = [3, 1, 1, 2]
b = [1, 0, 0, 2]
Choose(3,1) = 3
Choose(1,0) = 1
Choose(1,0) = 1
Choose(2,2) = 1
#number_possibilities = 3 * 1 * 1 * 1 = 3

5voto

גלעד ברקן Points 3044

(Veuillez également consulter mes deux autres réponses ci-dessous, pour les combinaisons multi-ensembles ordonnées et non ordonnées).

Pour éviter les "impasses" dans une récursion, créez des combinaisons à partir d'index hachés. Par exemple,

[1,2,1,3,1,4,4] / [1,3] 

Hash = {1: [0,2,4], 3: [3]} // for repeated elements in the to-be-removed array,
                            // you could reserve the first element of the index array

Use the multi-set combination algorithm of your choice to make combinations from the 
hashed indexes, like [0,3], [2,3], etc.; and accumulate results without the corresponding elements.

5voto

heenenee Points 11

Pour déterminer toutes les manières dont un groupe de valeurs (appelons ce groupe needles ) peuvent être retirés d'une séquence (appelée haystack ), faites ce qui suit :

  1. Comptez combien de fois vous devez retirer chaque needle (appelons ce compte k ). Cela peut être déterminé par un seul passage sur le site de l needles .
  2. Trouver tous les emplacements de chaque needle à supprimer dans le haystack . Cela peut être déterminé par un seul passage sur le site de l'entreprise. haystack .
  3. Générer toutes les façons possibles de supprimer chaque individu. needle k temps des emplacements trouvés. Il s'agit d'une norme énumération de k -combinaisons et sa complexité temporelle est non polynomiale.
  4. Créez toutes les combinaisons possibles pour chaque individu. needle Les possibilités d'enlèvement de l'enfant sont réunies. Il s'agit d'un standard n -Produit cartésien double et sa complexité temporelle est également non polynomiale.
  5. Pour chaque voie trouvée, filtrer les éléments pertinents de la haystack .

Voici une mise en œuvre ECMAScript 2016 de cette approche :

function* removalCombinations(haystack, needles) {
  // Comments walk through sample input of haystack = [1,2,1,3,1,4,4] and needles = [1,4,4]

  // How many of each needle there are, e.g.,
  // needleCounts = { 1 => 1, 4 => 2 }
  let needleCounts = elementCounts(needles);

  // Where each needle is located, e.g.,
  // needleIndexes = { 1 => [ 0, 2, 4 ], 4 => [ 5, 6 ] }
  let needleIndexes = findIndices(needleCounts.keys(), haystack.entries());

  // The possible indices to be removed for a particular needle, e.g.,
  // indexCombinations = [ [ [ 0 ], [ 2 ], [ 4 ] ], [ [ 5, 6 ] ] ]
  var indexCombinations = [];
  for (let [needle, indexes] of needleIndexes) {
    indexCombinations.push(Array.from(generateCombinations(indexes, needleCounts.get(needle))));
  }

  // All the ways that the possible index removals can be fully combined together, e.g.,
  // fullRemovalCombinations = [ [ 0, 5, 6 ], [ 2, 5, 6 ], [ 4, 5, 6 ] ]
  let fullRemovalCombinations = cartesianProductOf(indexCombinations);

  // For every possible index removal combination,
  // filter those indices from the original haystack, e.g.,
  // yielded values = [ [ 2, 1, 3, 1 ], [ 1, 2, 3, 1 ], [ 1, 2, 1, 3 ] ]
  for (let indicesToFilter of fullRemovalCombinations) {
    indicesToFilter = new Set(indicesToFilter);
    yield haystack.filter((_, index) => !indicesToFilter.has(index))
  }

  // Calculates how many there are of each element.
  function elementCounts(iterable) {
    let counts = new Map();
    for (let el of iterable) {
      counts.set(el, counts.get(el) + 1 || 1);
    }
    return counts;
  }

  // Finds the indices of where each target occurs within iterable.
  function findIndices(targets, entries) {
    let indices = new Map();
    for (let el of targets) {
      indices.set(el, []);
    }
    for (let [index, value] of entries) {
      if (indices.has(value)) {
        indices.get(value).push(index);
      }
    }
    return indices;
  }

  // Generates all possible combinations of choosing k elements from arr.
  function* generateCombinations(arr, k) {
    function* doGenerateCombinations(offset, combo) {
      if (combo.length == k) {
        yield combo;
      } else {
        let len = arr.length;
        for (let i = offset; i < len; i++) {
          yield * doGenerateCombinations(i + 1, combo.concat(arr[i]));
        }
      }
    }

    yield* doGenerateCombinations(0, []);
  }

  // Given an array of arrays, generates all ways the elements can be combined together,
  // when taking a single element from each array.
  function* cartesianProductOf(arrays) {
    function* doCartesianProductOf(i, prod) {
      if (i == arrays.length) {
        yield prod;
      } else {
        for (let j = 0; j < arrays[i].length; j++) {
          yield* doCartesianProductOf(i + 1, prod.concat(arrays[i][j]));
        }
      }
    }

    yield* doCartesianProductOf(0, []);
  }
}

console.log(JSON.stringify(Array.from(removalCombinations([1, 2, 1, 3, 1, 4, 4], [1, 4, 4]))));
console.log(JSON.stringify(Array.from(removalCombinations([8, 6, 4, 4], [6, 4, 8]))));

3voto

Zeta Points 356

Je pense que la ramification et l'élagage sont la manière orthodoxe de résoudre ce problème et avec beaucoup de possibilités d'optimisation.
Mais, si vous souhaitez simplement une solution simple et intuitive. La voici.

Tout d'abord, trouvez les numéros qui se trouvent dans la liste de suppression.
[1,2,1,3,1,4,4][1,4,4]
à partir de là, nous obtenons [1,1,1,4,4]
Deuxièmement, choisissez autant d'éléments de la liste de suppression de la première étape, soit la combinaison 5C3.
Nous obtenons donc [1,1,1] [1,1,4] [1,4,4] ....
Troisièmement, comparez la séquence. Ensuite, vous obtenez le résultat.
Voici le code Désolé c'est en C++, et j'ai utilisé une simple bibliothèque de combinaison.

#include<vector>
#include<algorithm>
#include<iostream>
#include"combi.h"
using namespace std;

int main()
{
    vector<int> list {1,2,1,3,1,4,4};
    vector<int> to_remove {1,4,4};
    vector<int> index;
    for(int i=0; i<list.size(); i++) {
        if(find(to_remove.begin(), to_remove.end(), list[i]) != to_remove.end())
            index.push_back(i);//insert index
    }
    bool sequence;
    nCr ncr(index.size(), to_remove.size());
    while(ncr.next()) {
        sequence = true;
        for(int i=0; i<ncr.size(); i++) 
            if(list[index[ncr[i]-1]] != to_remove[i]) sequence = false;
        if(sequence) {
            for(int i=0, j=0; i<list.size(); i++) {
                if(i == index[ncr[j]-1]) j++;
                else cout << list[i] << ' ';
            }
            cout << endl;
        }
    }
}

Voici la bibliothèque de la combinaison..

class Combination
{
public:
    Combination(int n, int r);
    virtual ~Combination() { delete [] ar;}
    int& operator[](unsigned i) {return ar[i];}
    bool next();
    int size() {return r;}

protected:
    int* ar;
    int n, r;
};

class nCr : public Combination
{
public: 
    nCr(int n, int r);
    bool next();
};

Combination::Combination(int n, int r)
{
    ar = new int[r];
    this->n = n;
    this->r = r;
}

nCr::nCr(int n, int r) : Combination(n, r)
{
    if(r == 0) return;
    for(int i=0; i<r-1; i++) ar[i] = i + 1;
    ar[r-1] = r-1;
}

bool nCr::next()
{
    if(r == 0) return false;
    ar[r-1]++;
    int i = r-1;
    while(ar[i] == n-r+2+i) {
        if(--i == -1) return false;
        ar[i]++;
    }
    while(i < r-1) ar[i+1] = ar[i++] + 1;
    return true;
}

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