Problèmes algorithmiques

De Banane Atomic
Révision datée du 26 février 2017 à 22:53 par Nicolas (discussion | contributions) (→‎Trouver les cycles dans un graphe orienté)
(diff) ← Version précédente | Voir la version actuelle (diff) | Version suivante → (diff)
Aller à la navigationAller à la recherche

Tri d'une collection

Bubble sort / sinking sort

Fait remonter les éléments les plus grands à la fin du tableau jusqu'à ce que le tableau soit trié.
Complexité en temps de O(n²)

Csharp.svg
// à chaque passage le plus grand élément du tableau sera remonté à droite
// on ne traite donc plus les éléments de droite déjà triés
for (int i = A.Length - 1; i >= 1; i--)
{
    bool sorted = true;
    // pour chaque élément restant à trier du tableau
    for (int j = 0; j < i; j++)
    {
        // si l'élément courant est plus grand que l'élément de droite, on inverse les 2 éléments
        if (A[j] > A[j + 1])
        {
            int temp = A[j + 1];
            A[j + 1] = A[j];
            A[j] = temp;
            sorted = false;
        }
    }
    // si on a procédé à aucune inversion c'est que ce qui reste à analyser du tableau est déjà trié, on s'arrête donc là
    if (sorted)
    {
        break;
    }
}

Insertion sort

Complexité en temps O(n²), pour les tableaux triés ou presque triés la complexité en temps passe à O(n)

Csharp.svg
// pour chaque élément du tableau
for (int i = 1; i < A.Length; i++)
{
    int j = i - 1;
    // on compare avec l'élément précédent
    // au besoin on déplace le plus petit élément vers la gauche de sorte que les éléments à gauche soit triés
    while (j >= 0 && A[j] > A[j + 1])
    {
        int temp = A[j + 1];
        A[j + 1] = A[j];
        A[j] = temp;
        j--;
    }
}

Nombres premiers

Compter le nombre de diviseurs d'un nombre entier N

L'approche la plus basique consiste à parcourir les nombres de 1 à N pour tester s'ils sont bien des diviseurs de N. Cette solution a une complexité en temps de O(N).
Sachant que si A est un diviseur de N, alors N/A est aussi un diviseur de N.

N=30, A=5 → N/A = 6 qui est un diviseur de 30  (30/6 = 5)
6 est le diviseur symétrique de 5 pour le nombre 30

Un de ces 2 diviseurs symétriques est inférieur à √N, on peut donc réduire la complexité en temps à O(√N).

Csharp.svg
int i;
int numberOfDivisors = 0;
for (i = 1; Math.BigMul(i, i) < N; i++)
{
    if (N % i == 0)
    {
        // le diviseur et son symétrique
        numberOfDivisors += 2;
    }
}

// si (i == √N) il y a un diviseur qui n'a pas de symétrique.
// Ex: N=4, on a 1 et son symétrique 4, mais 2 n'a pas de symétrique
if (Math.BigMul(i, i) == N)
{
    numberOfDivisors++;
}

return numberOfDivisors;

Tester si un entier N est premier

Un entier est premier s'il est divisible seulement par lui-même et par 1.
Ici aussi on réduit la complexité en temps à O(√N).

Csharp.svg
// on commence à 2, car tous les entiers sont divisible par 1
for (int i = 2; Math.BigMul(i, i) <= N; i++)
{
    if (N % i == 0)
    {
        return false;
    }
}

return true;

Lister les N premiers nombres premiers

L'approche basique consiste à tester si les entiers de 2 à N sont premier. La complexité en temps est de O(N*√N).
En utilisant le crible d'Ératosthène on réduit la complexité O(log(log(N)))

Csharp.svg
bool[] sieve = new bool[N + 1];
// tout le tableau à true sauf les éléments 0 et 1, car ils ne sont jamais premiers
for (int i = 2; i < N + 1; i++)
{
    sieve[i] = true;
}

// met à false les multiples de j
// par exemple: pour 2 met à false les multiles de 2 (4,6,8,...)
// puis pour 3 met à false les multiles de 3 restants (9,12,...)
// puis pour 4, il a déjà été mis à false par 2
int j = 2;
while (Math.BigMul(j, j) <= N)
{
    if (sieve[j])
    {
        long k = Math.BigMul(j, j);
        while (k <= N)
        {
            sieve[k] = false;
            k += j;
        }
    }
    j++;
}

// on extrait les nombres entiers
var primeNumbers = new List<int>();
for (int i = 0; i < sieve.Length; i++)
{
    if (sieve[i])
    {
        primeNumbers.Add(i);
    }
}

return primeNumbers;

Plus grande somme d'éléments contiguës dans un tableau

On cherche la plus grande somme qu’on puisse avoir en additionnant des éléments consécutifs d'un tableau.
Résolution à l'aide de l'algorithme de Kadane.

Csharp.svg
// tableau A
// [1, 2, 3, 4]   → somme des éléments 0 à 3 → 10
// [5, -9, 2, -1] → somme des éléments 2 à 3 → 1

int maxEndingHere = 0;
int maxSoFar = 0;
int maxElement = int.MinValue;

for (int i = 0; i < A.Length; i++)
{
    maxEndingHere = Math.Max(0, maxEndingHere + A[i]);
    maxSoFar = Math.Max(maxSoFar, maxEndingHere);
    maxElement = Math.Max(maxElement, A[i]);
}

// si tous les éléments du tableau sont négatif
// la somme max est égale à 0: somme d'un tableau vide
if (maxSoFar == 0)
{
    // ici on souhaite plutôt avoir le plus grand nombre négatif comme solution
    maxSoFar = maxElement;
}

return maxSoFar;
L'algorithme de Kadane suppose que si le tableau ne contient que des éléments négatifs, alors la plus grande somme correspond à 0: somme d'un sous-tableau vide.

Leader

Un élément d'un tableau est dit leader s'il est présent dans plus de la moitié des éléments du tableau.
Astuce: si on supprime 2 éléments différents du tableau, on supprime au plus un éléments leader.
Le leader reste donc leader du sous-tableau.

A = [4, 6, 6]
s = [4] → s = [4, 6] → 4 ≠ 6, on les supprime → s = [] → s = [6]
Csharp.svg
// on empile un à un les éléments du tableau
var s = new Stack<int>();
for (int i = 0; i < A.Length; i++)
{
    if (s.Count == 0)
    {
        s.Push(A[i]);
    }
    else
    {
        // si l'élément à empiler est différent de celui du sommet de la pile, on les supprime
        if (s.Peek() != A[i])
        {
            s.Pop();
        }
        else
        {
            s.Push(A[i]);
        }
    }
}

// si la pile est vide, c'est qu'il n'y a pas de leader
if (s.Count == 0)
{
    return -1;
}
// si la pile contient seulement 1 élément, il faut s'assurer qu'il est bien leader
// car ce pourrait être le dernier éléments d'un tableau de taille impair une fois qu'on a supprimer les paires différents
else if (s.Count == 1)
{
    int candidate = s.Peek();
                
    int count = 0;
    for (int i = 0; i < A.Length; i++)
    {
        if (A[i] == candidate)
        {
            count++;
            if (count > A.Length / 2)
            {
                return candidate;
            }
        }
    }

    return -1;
}
// si la pile contient plusieurs éléments, ce sont tous les même et c'est le leader
else
{
    return s.Peek();
}

Inverser les éléments d'un tableau A

On parcourt seulement la première moitié du tableau dont on échange les éléments avec la seconde moitié.

Csharp.svg
for (int i = 0; i < A.Length / 2; i++)
{
    var temp = A[i];
    A[i] = A[A.Length - 1 - i];
    A[A.Length - 1 - i] = temp;
}

Trouver les cycles dans un graphe orienté

Csharp.svg
public void FindCycle(int vertex)
{
    // enregistrer le parcourt pour pouvoir afficher les cycles
    _path.Push(vertex);
    // enregistrer les sommets visités afin de pouvoir détecter les cycles
    _visited[vertex] = true;

    // pour tous les sommets adjacents: reliés au sommet courant par une arête sortante
    foreach (var adjacentVertex in _graph.OutEdges(vertex).Select(e => e.Target))
    {
        // si ce sommet adjacent a déjà été parcouru, nous avons trouvé un cycle
        if (_visited[adjacentVertex])
        { ... }
        // sinon on continue à parcourir le graphe à partir du sommet adjacent
        else
        {
            FindCycle(adjacentVertex);
        }
    }

    // après avoir atteint un sommet final (sans arêtes sortantes)
    // on rebrousse chemin: on marque le sommet courant comme non-parcouru et on l'enlève du parcourt
    _path.Pop();
    _visited[vertex] = false;
}

Lexique sur les graphes