---
title: "Introduction aux algorithmes"
date: "2023-10-10"
---

# Rappel

## Quel est l'algorithme du tri par sélection?

. . .

1. Soit un tableau d'entiers, `tab[0:SIZE-1]` et `i = 0`.
2. Trouver l'indice, `j`, de `tab[i:SIZE-2]` où la valeur est minimale.
3. Échanger `tab[i]` et `tab[j]`.
4. `i += 1` et revenir à 2, tant que `j < SIZE-2`.

# Tri par sélection

## Implémentation par groupe de 3

* Initialiser aléatoirement un tableau de `double` de taille 10;
* Afficher le tableau;
* Trier par sélection le tableau;
* Afficher le résultat trié;
* Vérifier algorithmiquement que le résultat est bien trié.

# Un type de tableau particulier

## Les chaînes de caractères

```C
string = tableau + char + magie noire
```

# Le type `char`{.C}

- Le type `char`{.C} est utilisé pour représenter un caractère.
- C'est un entier 8 bits signé.
- En particulier:
    - Écrire
        
        ```C
        char c = 'A';
        ```
    - Est équivalent à:

        ```C
        char c = 65;
        ```
- Les fonctions d'affichage interprètent le nombre comme sa valeur ASCII.

# Chaînes de caractères (strings)

- Chaîne de caractère `==` tableau de caractères **terminé par la valeur** `'\0'`{.C} ou `0`{.C}.

## Exemple

```C
char *str  = "HELLO !";
char str[] = "HELLO !";
```

Est représenté par

| `char`  | `H`  | `E`  | `L`  | `L`  | `O`  |      | `!`  | `\0`|
|---------|------|------|------|------|------|------|------|-----|
| `ASCII` | `72` | `69` | `76` | `76` | `79` | `32` | `33` | `0` |

. . .

## A quoi sert le `\0`?

. . .

Permet de connaître la fin de la chaîne de caractères (pas le cas des autres
sortes de tableaux).

# Syntaxe

```C
char name[5];
name[0] = 'P';  // = 70;
name[1] = 'a';  // = 97;
name[2] = 'u';  // = 117;
name[3] = 'l';  // = 108;
name[4] = '\0'; // = 0;
char name[] = {'P', 'a', 'u', 'l', '\0'};
char name[5] = "Paul";
char name[] = "Paul";
char name[100] = "Paul is not 100 characters long.";
```

# Fonctions

- Il existe une grande quantités de fonction pour la manipulation de chaînes de caractères dans `string.h`.
- Fonctions principales:

    ```C
    // longueur de la chaîne (sans le \0)
    size_t strlen(char *str);
    // copie jusqu'à un \0
    char *strcpy(char *dest, const char *src);
     // copie len char
    char *strncpy(char *dest, const char *src, size_t len);
    // compare len chars
    int strncmp(char *str1, char *str2, size_t len);
    // compare jusqu'à un \0
    int strcmp(char *str1, char *str2);
    ```

- Pour avoir la liste complète: `man string`.

. . .

## Quel problème peut se produire avec `strlen`, `strcpy`, `strcmp`?

. . .

- Si `\0` est absent... on a un comportement indéfini.

# Les anagrammes

## Définition

Deux mots sont des anagrammes l'un de l'autre quand ils contiennent les mêmes 
lettres mais dans un ordre différent.

## Exemple

| `t`  | `u`  | `t`  | `u`  | `t`  | `\0` | ` `  | ` ` |
|------|------|------|------|------|------|------|-----|
| `t`  | `u`  | `t`  | `t`  | `u`  | `\0` | ` `  | ` ` |


## Problème: Trouvez un algorithme pour déterminer si deux mots sont des anagrammes.

# Les anagrammes

## Il suffit de:

1. Trier les deux mots.
2. Vérifier s'ils contiennent les mêmes lettres.

## Implémentation ensemble

```C
int main() { // pseudo C
    tri(mot1);
    tri(mot2);
    if egalite(mot1, mot2) {
        // anagrammes
    } else {
        // pas anagrammes
    }
}
```

# Les palindromes

Mot qui se lit pareil de droite à gauche que de gauche à droite:

. . .

* rotor, kayak, ressasser, ...

## Problème: proposer un algorithme pour détecter un palindrome

. . .

## Solution 1

```C
while (first_index < last_index {
    if (mot[first_index] != mot [last_index]) {
        return false;
    }
    first_index += 1;
    last_index -= 1;
}
return true;
```

. . .

## Solution 2

```C
mot_tmp = revert(mot);
return mot == mot_tmp;
```

# Crible d'Ératosthène

Algorithme de génération de nombres premiers.

## Exercice

* À l'aide d'un tableau de booléens,
* Générer les nombres premiers plus petits qu'un nombre $N$

## Pseudo-code

* Par groupe de trois, réfléchir à un algorithme.

## Programme en C

* Implémenter l'algorithme et le poster sur le salon `Element`.

# Crible d'Ératosthène: solution

\footnotesize

```C
#include <stdio.h>
#include <stdbool.h>
#define SIZE 51
int main() {
   bool tab[SIZE];
   for (int i=0;i<SIZE;i++) {
      tab[i] = true;  
   }
   for (int i = 2; i < SIZE; i++) {
      if (tab[i]) {
         printf("%d ", i);
         int j = i;
         while (j < SIZE) {
            j += i;
            tab[j] = false;
         } 
      } 
   }
   printf("\n");
}
```


# Réusinage de code (refactoring)

## Le réusinage est?

. . .

* le processus de restructuration d'un programme:
    * en modifiant son design,
    * en modifiant sa structure,
    * en modifiant ses algorithmes
    * mais en **conservant ses fonctionalités**.

. . .

## Avantages?

. . .

* Amélioration de la lisibilité,
* Amélioration de la maintenabilité,
* Réduction de la complexité.

. . .

## "Make it work, make it nice, make it fast",  Kent Beck.

. . .

## Exercice:

* Réusiner le code se trouvant sur
  [Cyberlearn](https://cyberlearn.hes-so.ch/mod/resource/view.php?id=1627712).

# Tableau à deux dimensions (1/4)

## Mais qu'est-ce donc?

. . .

* Un tableau où chaque cellule est un tableau.

## Quels cas d'utilisation?

. . .

* Tableau à double entrée;
* Image;
* Écran (pixels);
* Matrice (mathématique);
 
# Tableau à deux dimensions (2/4)

## Exemple: tableau à 3 lignes et 4 colonnes d'entiers

+-----------+-----+-----+-----+-----+
| `indices` | `0` | `1` | `2` | `3` |
+-----------+-----+-----+-----+-----+
| `0`       | `7` | `4` | `7` | `3` |
+-----------+-----+-----+-----+-----+
| `1`       | `2` | `2` | `9` | `2` |
+-----------+-----+-----+-----+-----+
| `2`       | `4` | `8` | `8` | `9` |
+-----------+-----+-----+-----+-----+

## Syntaxe

```C
int tab[3][4]; // déclaration d'un tableau 4x3
tab[2][1]; // accès à la case 2, 1
tab[2][1] = 14; // assignation de 14 à la position 2, 1
```

# Tableau à deux dimensions (3/4)

## Exercice: déclarer et initialiser aléatoirement un tableau `50x100`

. . .

```C
#define NX 50
#define NY 100
int tab[NX][NY];
for (int i = 0; i < NX; ++i) {
    for (int j = 0; j < NY; ++j) {
        tab[i][j] = rand() % 256; // 256 niveaux de gris
    }
}
```
 
## Exercice: afficher le tableau

. . .

```C
for (int i = 0; i < NX; ++i) {
    for (int j = 0; j < NY; ++j) {
        printf("%d ", tab[i][j]);
    }
    printf("\n");
}
```

# Tableau à deux dimensions (4/4)

## Attention

* Les éléments ne sont **jamais** initialisés.
* Les bornes ne sont **jamais** vérifiées.

    ```C
    int tab[3][2] = { {1, 2}, {3, 4}, {5, 6} };
    printf("%d\n", tab[2][1]);  // affiche?
    printf("%d\n", tab[10][9]); // affiche?
    printf("%d\n", tab[3][1]);  // affiche?
    ```
    
# La couverture de la reine

* Aux échecs la reine peut se déplacer horizontalement et verticalement
* Pour un échiquier `5x6`, elle *couvre* les cases comme ci-dessous

+-----+-----+-----+-----+-----+-----+-----+
| ` ` | `0` | `1` | `2` | `3` | `4` | `5` |
+-----+-----+-----+-----+-----+-----+-----+
| `0` | `*` | ` ` | `*` | ` ` | `*` | ` ` |
+-----+-----+-----+-----+-----+-----+-----+
| `1` | ` ` | `*` | `*` | `*` | ` ` | ` ` |
+-----+-----+-----+-----+-----+-----+-----+
| `2` | `*` | `*` | `R` | `*` | `*` | `*` |
+-----+-----+-----+-----+-----+-----+-----+
| `3` | ` ` | `*` | `*` | `*` | ` ` | ` ` |
+-----+-----+-----+-----+-----+-----+-----+
| `4` | `*` | ` ` | `*` | ` ` | `*` | ` ` |
+-----+-----+-----+-----+-----+-----+-----+

## Exercice

* En utilisant les conditions, les tableaux à deux dimensions, et des
  `char` uniquement.
* Implémenter un programme qui demande à l'utilisateur d'entrer les
  coordonnées de la reine et affiche un tableau comme ci-dessus pour un
  échiquier `8x8`.

## Poster le résultat sur `Element`

# Types énumérés (1/2)

* Un **type énuméré**: ensemble de *variantes* (valeurs constantes).
* En `C` les variantes sont des entiers numérotés à partir de 0.

    ```C
    enum days {
        monday, tuesday, wednesday,
        thursday, friday, saturday, sunday
    };
    ```
* On peut aussi donner des valeurs "custom"

    ```C
    enum days {
        monday = 2, tuesday = 8, wednesday = -2,
        thursday = 1, friday = 3, saturday = 12, sunday = 9
    };
    ```
* S'utilise comme un type standard et un entier

    ```C
    enum days d = monday;
    (d + 2) == tuesday + tuesday; // true
    ```

# Types énumérés (2/2)

* Très utile dans les `switch ... case`{.C}

    ```C
    enum days d = monday;
    switch (d) {
        case monday:
            // trucs
            break;
        case tuesday:
            printf("0 ou 1\n");
            break;
    }
    ```
* Le compilateur vous prévient qu'il en manque!

# Utilisation des types énumérés

## Réusiner votre couverture de la reine avec des `enum`

# Représentation des nombres (1/2)

* Le nombre `247`.

## Nombres décimaux: Les nombres en base 10 

+--------+--------+--------+
| $10^2$ | $10^1$ | $10^0$ |
+--------+--------+--------+
| `2`    | `4`    | `7`    |
+--------+--------+--------+

$$
247 = 2\cdot 10^2 + 4\cdot 10^1 + 7\cdot 10^0.
$$

# Représentation des nombres (2/2)

* Le nombre `11110111`.

## Nombres binaires: Les nombres en base 2

+-------+-------+-------+-------+-------+-------+-------+-------+
| $2^7$ | $2^6$ | $2^5$ | $2^4$ | $2^3$ | $2^2$ | $2^1$ | $2^0$ |
+-------+-------+-------+-------+-------+-------+-------+-------+
| `1`   | `1`   | `1`   | `1`   | `0`   | `1`   | `1`   | `1`   |
+-------+-------+-------+-------+-------+-------+-------+-------+
 
$$
1\cdot 2^7 + 1\cdot 2^6 +1\cdot 2^5 +1\cdot 2^4 +0\cdot 2^3 +1\cdot 2^2
+1\cdot 2^1 +1\cdot 2^0
$$

. . .

$$
= 247.
$$

# Conversion de décimal à binaire (1/2)

## Convertir 11 en binaire?

. . .

* On décompose en puissances de 2 en partant de la plus grande possible

    ```
    11 / 8 = 1, 11 % 8 = 3
    3  / 4 = 0,  3 % 4 = 3
    3  / 2 = 1,  3 % 2 = 1
    1  / 1 = 1,  1 % 1 = 0
    ```
* On a donc

    $$
    1011 \Rightarrow 1\cdot 2^3 + 0\cdot 2^2 + 1\cdot 2^1 + 1\cdot
    2^0=11.
    $$

# Conversion de décimal à binaire (2/2)

## Convertir un nombre arbitraire en binaire: 247?

* Par groupe établir un algorithme.

. . .

## Algorithme

1. Initialisation
    
    ```C
    num = 247
    while (2^N < num) {
        N += 1
    }
    ```

. . .

2. Boucle

    ```C
    while (N >= 0) {
        bit = num / 2^N
        num = num % 2^N
        N += 1
    }
    ```

# Les additions en binaire

Que donne l'addition `1101` avec `0110`?

* L'addition est la même que dans le système décimal

    ```
       1101         8+4+0+1 = 13
    +  0110    +    0+4+2+0 =  6
    -------    -----------------
      10011      16+0+0+2+1 = 19
    ```
* Les entiers sur un ordinateur ont une précision **fixée** (ici 4 bits).
* Que se passe-t-il donc ici?

. . .

## Dépassement de capacité: le nombre est "tronqué"

* `10011 (19) -> 0011 (3)`.
* On fait "le tour"."

# Entier non-signés minimal/maximal

* Quel est l'entier non-signé maximal représentable avec 4 bit?

. . .

$$
(1111)_2 = 8+4+2+1 = 15
$$

* Quel est l'entier non-signé minimal représentable avec 4 bit?

. . .

$$
(0000)_2 = 0+0+0+0 = 0
$$

* Quel est l'entier non-signé min/max représentable avec N bit?

. . .

$$
0\mbox{ et  }2^N-1.
$$

* Donc `uint32_t?` maximal est?

. . .

$$
4294967295
$$


# Les multiplications en binaire (1/2)

Que donne la multiplication de `1101` avec `0110`?

* L'addition est la même que dans le système décimal

    ```
         1101                13
    *    0110    *            6
    ---------    --------------
         0000                78
        11010
       110100
    + 0000000
    ---------    --------------
      1001110    64+0+0+8+4+2+0
    ```

# Les multiplications en binaire (2/2)

## Que fait la multiplication par 2?

. . .

* Décalage de un bit vers la gauche!

    ```
         0110
    *    0010
    ---------
         0000
    +   01100
    ---------
        01100
    ```

. . .

## Que fait la multiplication par $2^N$?

. . .

* Décalade de $N$ bits vers la gauche!

# Entiers signés (1/2)

Pas de nombres négatifs encore...

## Comment faire?

. . .

## Solution naïve:

* On ajoute un bit de signe (le bit de poids fort):

    ```
    00000010: +2
    10000010: -2
    ```

## Problèmes?

. . .

* Il y a deux zéros (pas trop grave): `10000000` et `00000000`
* Les additions différentes que pour les non-signés (très grave)
    
    ```
      00000010              2    
    + 10000100           + -4
    ----------           ----
      10000110 = -6  !=    -2
    ```

# Entiers signés (2/2)

## Beaucoup mieux

* Complément à un:
    * on inverse tous les bits: `1001 => 0110`.

## Encore un peu mieux

* Complément à deux:
    * on inverse tous les bits,
    * on ajoute 1 (on ignore les dépassements).

. . .

* Comment écrit-on `-4` en 8 bits?

. . .

```
     4 =  00000100
            ________
    -4 =>   00000100
            
            11111011
          + 00000001 
          ----------
            11111100
```

# Le complément à 2 (1/2)

## Questions:

* Comment on écrit `+0` et `-0`?
* Comment calcule-t-on `2 + (-4)`?
* Quel est le complément à 2 de `1000 0000`?

. . .

## Réponses

* Comment on écrit `+0` et `-0`?

    ```
    +0 = 00000000
    -0 = 11111111 + 00000001 = 100000000 => 00000000 
    ```
* Comment calcule-t-on `2 + (-4)`?

    ```
      00000010            2
    + 11111100        +  -4
    ----------        -----
      11111110           -2
    ```
* En effet

    ```
    11111110 => 00000001 + 00000001 = 00000010 = 2.
    ```

# Le complément à 2 (1/2)

## Quels sont les entiers représentables en 8 bits?

. . .

```
01111111 =>  127
10000000 => -128 // par définition
```

## Quels sont les entiers représentables sur $N$ bits?

. . .

$$
-2^{N-1} ... 2^{N-1}-1.
$$

## Remarque: dépassement de capacité en `C`

* Comportement indéfini!


<!-- # TODO --

<!-- ## Entiers, entiers non-signés -->

<!-- ## Complément à 1, 2 -->

<!-- ## Nombres à virgule flottante, simple/double précision -->

# Types composés: `struct`{.C} (1/6)

## Fractions

* Numérateur: `int num`;
* Dénominateur: `int denom`.

## Addition

```C
int num1 = 1, denom1 = 2;
int num2 = 1, denom2 = 3;
int num3 = num1 * denom2 + num2 * denom1;
int denom3 = denom1 * denom2;
```

## Pas super pratique....

# Types composés: `struct`{.C} (2/6)

## On peut faire mieux

* Plusieurs variables qu'on aimerait regrouper dans un seul type: `struct`{.C}.

```C
struct fraction { // déclaration du type
    int32_t num, denom;
}

struct fraction frac; // déclaration de frac
```

# Types composés: `struct`{.C} (3/6)

## Simplifications

- `typedef`{.C} permet de définir un nouveau type.

    ```C
    typedef unsinged int uint;
    typedef struct fraction fraction_t;
    typedef struct fraction {
        int32_t num, denom;
    } fraction_t;
    ```
- L'initialisation peut aussi se faire avec

    ```C
    fraction_t frac = {1, -2}; // num = 1, denom = -2
    fraction_t frac = {.denom = 1, .num = -2};
    fraction_t frac = {.denom = 1}; // argl! .num non initialisé 
    fraction_t frac2 = frac; // copie
    ```

# Types composés: `struct`{.C} (4/6)

## Pointeurs

- Comme pour tout type, on peut avoir des pointeurs vers un `struct`{.C}.
- Les champs sont accessible avec le sélecteur `->`{.C}

    ```C
    fraction_t *frac; // on crée un pointeur
    frac->num = 1;    // seg fault...
    frac->denom = -1; // mémoire pas allouée.
    ```

![La représentation mémoire de
`fraction_t`.](figs/pointer_struct.svg){width=50%}

# Types composés: `struct`{.C} (5/6)

## Initialisation

- Avec le passage par **référence** on peut modifier un struct *en place*.
- Les champs sont accessible avec le sélecteur `->`{.C}

    ```C
    void fraction_init(fraction_t *frac, 
                      int32_t re, int32_t im) 
    {
        // frac a déjà été allouée
        frac->num   = frac;
        frac->denom = denom;
    }
    int main() {
        fraction_t frac; // on alloue une fraction
        fraction_init(&frac, 2, -1); // on l'initialise
    }
    ```

# Types composés: `struct`{.C} (6/6)

## Initialisation version copie

* On peut allouer une fraction, l'initialiser et le retourner.
* La valeur retournée peut être copiée dans une nouvelle structure.

    ```C
    fraction_t fraction_create(int32_t re, int32_t im) {
        fraction_t frac;
        frac.num = re;
        frac.denom = im;
        return frac;
    }
    int main() {
        // on crée une fraction et on l'initialise
        // en copiant la fraction créé par fraction_create
        // deux allocation et une copie
        fraction_t frac = fraction_create(2, -1); 
    }
    ```