web-dev-qa-db-fra.com

Générer toutes les chaînes binaires de longueur n avec k bits définis

Quel est le meilleur algorithme pour trouver toutes les chaînes binaires de longueur n qui contiennent k bits définis? Par exemple, si n = 4 et k = 3, il y a ...

0111
1011
1101
1110

J'ai besoin d'un bon moyen de générer ces données étant donné tout n et tout k, je préférerais donc que cela se fasse avec des chaînes.

56
kevmo314

Cette méthode générera tous les entiers avec exactement N '1' bits.

De https://graphics.stanford.edu/~seander/bithacks.html#NextBitPermutation

Calculer la permutation lexicographique du bit suivant

Supposons que nous ayons un modèle de N bits mis à 1 dans un entier et que nous voulons la prochaine permutation de N 1 bits au sens lexicographique. Par exemple, si N est 3 et le motif binaire est 00010011, Les motifs suivants seraient 00010101, 00010110, 00011001, 00011010, 00011100, 00100011, Etc. Ce qui suit est un moyen rapide de calculer la prochaine permutation.

unsigned int v; // current permutation of bits
unsigned int w; // next permutation of bits

unsigned int t = v | (v - 1); // t gets v's least significant 0 bits set to 1
// Next set to 1 the most significant bit to change,
// set to 0 the least significant ones, and add the necessary 1 bits.
w = (t + 1) | (((~t & -~t) - 1) >> (__builtin_ctz(v) + 1));

La fonction __builtin_ctz(v) GNU C intrinsèque du compilateur pour les processeurs x86 renvoie le nombre de zéros de fin. Si vous utilisez des compilateurs Microsoft pour x86, l'intrinsèque est _BitScanForward. Ces deux émettent une instruction bsf, mais des équivalents peuvent être disponibles pour d'autres architectures. Sinon, envisagez d'utiliser l'une des méthodes de comptage des zéros consécutifs mentionnés plus haut. Voici une autre version qui a tendance à être plus lente car de son opérateur de division, mais il ne nécessite pas de compter les zéros de fin.

unsigned int t = (v | (v - 1)) + 1;
w = t | ((((t & -t) / (v & -v)) >> 1) - 1);

Merci à Dario Sneidermanis d'Argentine, qui l'a fourni le 28 novembre 2009.

35
finnw

Python

import itertools

def kbits(n, k):
    result = []
    for bits in itertools.combinations(range(n), k):
        s = ['0'] * n
        for bit in bits:
            s[bit] = '1'
        result.append(''.join(s))
    return result

print kbits(4, 3)

Output: ['1110', '1101', '1011', '0111']

Explication:

Essentiellement, nous devons choisir les positions des 1 bits. Il existe n choix k façons de choisir k bits parmi n bits totaux. itertools est un module Nice qui fait cela pour nous. itertools.combinations (range (n), k) choisira k bits parmi [0, 1, 2 ... n-1], puis il s'agit simplement de construire la chaîne en fonction de ces index binaires.

Puisque vous n'utilisez pas Python, regardez le pseudo-code pour itertools.combinations ici:

http://docs.python.org/library/itertools.html#itertools.combinations

Doit être facile à mettre en œuvre dans n'importe quelle langue.

17
FogleBird

Oubliez l'implémentation ("que ce soit fait avec des chaînes" est évidemment un problème d'implémentation !) - pensez au algorithme, pour l'amour de Pete ... tout comme dans votre tout premier TAG, mec!

Ce que vous cherchez, c'est toutes les combinaisons de K éléments d'un ensemble de N (les indices, 0 à N-1, des bits définis). C'est évidemment le plus simple à exprimer de manière récursive, par exemple, le pseudocode:

combinations(K, setN):
  if k > length(setN): return "no combinations possible"
  if k == 0: return "empty combination"
  # combinations INcluding the first item:
  return (first-item-of setN) combined combinations(K-1, all-but-first-of setN)
   union combinations(K-1, all-but-first-of setN)

c'est-à-dire que le premier élément est soit présent soit absent: s'il est présent, il vous reste K-1 à partir (de la queue alias all-but-firs), s'il est absent, encore K à gauche.

Les langages fonctionnels de correspondance de modèles comme SML ou Haskell peuvent être les meilleurs pour exprimer ce pseudocode (ceux procéduraux, comme mon grand amour Python, peuvent en fait masquer le problème trop profondément en incluant des fonctionnalités trop riches, telles que itertools.combinations, qui fait tout le travail pour vous et vous le cache donc!).

Que connaissez-vous le plus à cet effet - Scheme, SML, Haskell, ...? Je serai heureux de traduire le pseudocode ci-dessus pour vous. Je peux le faire dans des langages tels que Python aussi, bien sûr - mais comme le but est de vous faire comprendre les mécanismes de ce devoir, je n'utiliserai pas de fonctionnalités trop riches telles que comme itertools.combinations, mais plutôt la récursivité (et la récursivité-élimination, si nécessaire) sur des primitives plus évidentes (telles que head, tail et concatenation). Mais s'il vous plaît, faites-nous savoir quel langage pseudocode vous est le plus familier! (Vous comprenez que le problème que vous énoncez est identotiquement équipotent pour "obtenir toutes les combinaisons d'éléments K hors de portée ou de portée (N)", n'est-ce pas?).

9
Alex Martelli

Cette méthode C # renvoie un énumérateur qui crée toutes les combinaisons. Comme il crée les combinaisons au fur et à mesure que vous les énumérez, il utilise uniquement l'espace de pile, il n'est donc pas limité par l'espace mémoire dans le nombre de combinaisons qu'il peut créer.

Ceci est la première version que j'ai créée. Il est limité par l'espace de pile à une longueur d'environ 2700:

static IEnumerable<string> BinStrings(int length, int bits) {
  if (length == 1) {
    yield return bits.ToString();
  } else {
    if (length > bits) {
      foreach (string s in BinStrings(length - 1, bits)) {
        yield return "0" + s;
      }
    }
    if (bits > 0) {
      foreach (string s in BinStrings(length - 1, bits - 1)) {
        yield return "1" + s;
      }
    }
  }
}

Il s'agit de la deuxième version, qui utilise un fractionnement binaire plutôt que de séparer le premier caractère, il utilise donc la pile beaucoup plus efficacement. Il n'est limité que par l'espace mémoire de la chaîne qu'il crée à chaque itération, et je l'ai testé jusqu'à une longueur de 10000000:

static IEnumerable<string> BinStrings(int length, int bits) {
  if (length == 1) {
    yield return bits.ToString();
  } else {
    int first = length / 2;
    int last = length - first;
    int low = Math.Max(0, bits - last);
    int high = Math.Min(bits, first);
    for (int i = low; i <= high; i++) {
      foreach (string f in BinStrings(first, i)) {
        foreach (string l in BinStrings(last, bits - i)) {
          yield return f + l;
        }
      }
    }
  }
}
4
Guffa

Un problème avec de nombreuses solutions standard à ce problème est que l'ensemble complet des chaînes est généré, puis celles-ci sont itérées, ce qui peut épuiser la pile. Il devient rapidement difficile à manier pour tout sauf les plus petits ensembles. De plus, dans de nombreux cas, seul un échantillonnage partiel est nécessaire, mais les solutions standard (récursives) coupent généralement le problème en morceaux fortement biaisés dans une direction (par exemple, considérons toutes les solutions avec un bit de départ nul, puis toutes les solutions avec un bit de départ).

Dans de nombreux cas, il serait plus souhaitable de pouvoir passer une chaîne de bits (en spécifiant la sélection d'élément) à une fonction et de la renvoyer la chaîne de bits suivante de manière à avoir un changement minimal (c'est ce qu'on appelle un Gray Code) et d’avoir une représentation de tous les éléments.

Donald Knuth couvre une multitude d'algorithmes pour cela dans son fascicule 3A, section 7.2.1.3: Génération de toutes les combinaisons.

Il existe une approche pour aborder l'algorithme itératif du code gris pour toutes les façons de choisir k éléments parmi n à http://answers.yahoo.com/question/index?qid=20081208224633AA0gdMl avec un lien vers la finale PHP code répertorié dans le commentaire (cliquez pour l'agrandir) en bas de la page.

4
Csaba Gabor

Un algorithme qui devrait fonctionner:

generate-strings(prefix, len, numBits) -> String:
    if (len == 0):
        print prefix
        return
    if (len == numBits):
        print prefix + (len x "1")
    generate-strings(prefix + "0", len-1, numBits)
    generate-strings(prefix + "1", len-1, numBits)

Bonne chance!

1
Chip Uni

De manière plus générique, la fonction ci-dessous vous donnera toutes les combinaisons d'index possibles pour un problème N choose K que vous pourrez ensuite appliquer à une chaîne ou autre:

def generate_index_combinations(n, k):

    possible_combinations = []

    def walk(current_index, indexes_so_far=None):
        indexes_so_far = indexes_so_far or []
        if len(indexes_so_far) == k:
            indexes_so_far = Tuple(indexes_so_far)
            possible_combinations.append(indexes_so_far)
            return
        if current_index == n:
            return
        walk(current_index + 1, indexes_so_far + [current_index])
        walk(current_index + 1, indexes_so_far)

    if k == 0:
        return []
    walk(0)
    return possible_combinations
1
Scott Lobdell

Une doublure possible de 1,5:

$ python -c 'import itertools; \
             print set([ n for n in itertools.permutations("0111", 4)])'

set([('1', '1', '1', '0'), ('0', '1', '1', '1'), ..., ('1', '0', '1', '1')])

.. où k est le nombre de 1péché "0111".

Le module itertools explique les équivalents de ses méthodes; voir l'équivalent pour la méthode de permutation .

0
miku

Eh bien pour this question (où vous devez parcourir tous les sous-masques dans l'ordre croissant de leur nombre de bits définis), qui a été marqué comme doublon de cela.

Nous pouvons simplement parcourir tous les sous-masques, les ajouter à un vecteur et les trier en fonction du nombre de bits définis.

vector<int> v;
for(ll i=mask;i>0;i=(i-1)&mask)
    v.Push_back(i);
auto cmp = [](const auto &a, const auto &b){
    return __builtin_popcountll(a) < __builtin_popcountll(b);
}
v.sort(v.begin(), v.end(), cmp);

Une autre façon serait d'itérer sur tous les sous-masques N fois et d'ajouter un nombre au vecteur si le nombre de bits définis est égal à i dans la ième itération.

Les deux voies ont une complexité de O (n * 2 ^ n)

0
harshhx17

J'essaierais la récursivité.

Il y a n chiffres dont k 1s. Une autre façon de voir cela est la séquence de k + 1 intervalles avec n-k 0 répartis entre eux. Autrement dit, (une série de 0 suivie de 1) k fois, puis suivie d'une autre série de 0. Chacune de ces séquences peut être de longueur zéro, mais la longueur totale doit être n-k.

Représentez ceci comme un tableau de k + 1 entiers. Convertissez en chaîne au bas de la récursivité.

Appel récursivement à la profondeur n-k, une méthode qui incrémente un élément du tableau avant un appel récursif puis le décrémente, k + 1 fois.

À la profondeur de n-k, sortez la chaîne.

int[] run = new int[k+1];

void recur(int depth) {
    if(depth == 0){
        output();
        return;
    }

    for(int i = 0; i < k + 1; ++i){
        ++run[i];
        recur(depth - 1);
        --run[i];
    }

public static void main(string[] arrrgghhs) {
    recur(n - k);
}

Cela fait un moment que je n'ai pas fait Java, donc il y a probablement des erreurs dans ce code, mais l'idée devrait fonctionner.

0
UncleO

Python (style fonctionnel)

Utilisation de pythonitertools.combinations vous pouvez générer tous les choix de k notre de n et mapper ces choix à un tableau binaire avec reduce

from itertools import combinations
from functools import reduce # not necessary in python 2.x

def k_bits_on(k,n):
       one_at = lambda v,i:v[:i]+[1]+v[i+1:]
       return [Tuple(reduce(one_at,c,[0]*n)) for c in combinations(range(n),k)]

Exemple d'utilisation:

In [4]: k_bits_on(2,5)
Out[4]:
[(0, 0, 0, 1, 1),
 (0, 0, 1, 0, 1),
 (0, 0, 1, 1, 0),
 (0, 1, 0, 0, 1),
 (0, 1, 0, 1, 0),
 (0, 1, 1, 0, 0),
 (1, 0, 0, 0, 1),
 (1, 0, 0, 1, 0),
 (1, 0, 1, 0, 0),
 (1, 1, 0, 0, 0)]
0
Uri Goren

Les chaînes sont-elles plus rapides qu'un tableau d'entiers? Toutes les solutions précédant les chaînes entraînent probablement une copie de la chaîne à chaque itération.

Donc, le moyen le plus efficace serait probablement un tableau d'int ou de char auquel vous ajoutez. Java a des conteneurs évolutifs efficaces, non? Utilisez-le, s'il est plus rapide que la chaîne. Ou si BigInteger est efficace, il est certainement compact, car chaque bit ne prend qu'un bit, pas un octet entier ou entier . Mais ensuite, pour itérer sur les bits, vous devez & masquer un peu, et déplacer le masque vers la position de bit suivante. Donc probablement plus lentement, à moins que les compilateurs JIT ne soient bons à cela de nos jours.

Je posterais ceci un commentaire sur la question d'origine, mais mon karma n'est pas assez élevé. Pardon.

0
Peter Cordes