web-dev-qa-db-fra.com

Calculez le nombre minimal de swaps pour ordonner une séquence

Je travaille sur le tri d'une séquence entière sans nombre identique (sans perte de généralité, supposons que la séquence est une permutation de 1,2,...,n) dans son ordre croissant naturel (c'est-à-dire 1,2,...,n). Je pensais à échanger directement les éléments (indépendamment de la position des éléments; en d'autres termes, un échange est valable pour deux éléments) avec un nombre minimal d'échanges (ce qui suit peut être une solution réalisable):

Échangez deux éléments avec la contrainte que l'un ou les deux doivent être échangés dans la ou les positions correctes. Jusqu'à ce que chaque élément soit placé dans sa position correcte.

Mais je ne sais pas comment prouver mathématiquement si la solution ci-dessus est optimale. Tout le monde peut aider?

40
mintaka

J'ai pu le prouver avec graph-theory . Pourrait vouloir ajouter cette balise :)

Créez un graphique avec n sommets. Créer un Edge à partir du nœud n_i à n_j si l'élément en position i doit être en position j dans le bon ordre. Vous aurez maintenant un graphique composé de plusieurs cycles sans intersection. Je soutiens que le nombre minimum de swaps nécessaires pour commander correctement le graphique est

M = sum (c in cycles) size(c) - 1

Prenez une seconde pour vous en convaincre ... si deux articles sont dans un cycle, un échange peut juste prendre soin d'eux. Si trois éléments sont dans un cycle, vous pouvez échanger une paire pour en mettre un au bon endroit, et un cycle reste, etc. Si n les éléments sont dans un cycle, vous avez besoin de n-1 swaps. (Cela est toujours vrai même si vous n'échangez pas avec des voisins immédiats.)

Cela étant, vous pouvez maintenant voir pourquoi votre algorithme est optimal. Si vous effectuez un échange et qu'au moins un élément est dans la bonne position, cela réduira toujours la valeur de M de 1. Pour tout cycle de longueur n, pensez à permuter un élément dans le bon endroit, occupé par son voisin. Vous avez maintenant un élément correctement ordonné et un cycle de longueur n-1.

Puisque M est le nombre minimum de swaps et que votre algorithme réduit toujours M de 1 pour chaque swap, il doit être optimal.

61
Andrew Mao

Ehm, tout le comptage de cycles est très difficile à garder dans la tête. Il existe un moyen beaucoup plus simple à mémoriser.

Tout d'abord, allons jeter un exemple de cas manuellement.

  • Séquence: [7, 1, 3, 2, 4, 5, 6]
  • Énumérer: [(0, 7), (1, 1), (2, 3), (3, 2), (4, 4), (5, 5), (6, 6)]
  • Trier l'énumération par valeur: [(1, 1), (3, 2), (2, 3), (4, 4), (5, 5), (6, 6), (0, 7)]
  • Commencez depuis le début. Bien que l'index soit différent de l'index énuméré, continuez à permuter les éléments définis par l'index et l'index énuméré. N'oubliez pas: swap(0,2);swap(0,3) est identique à swap(2,3);swap(0,2)
    • swap(0, 1) => [(3, 2), (1, 1), (2, 3), (4, 4), (5 , 5), (6, 6), (0, 7)]
    • swap(0, 3) => [(4, 4), (1, 1), (2, 3), (3, 2), (5 , 5), (6, 6), (0, 7)]
    • swap(0, 4) => [(5, 5), (1, 1), (2, 3), (3, 2), (4, 4), (6, 6), (0, 7)]
    • swap(0, 5) => [(6, 6), (1, 1), (2, 3), (3, 2), (4, 4), (5, 5), (0, 7)]
    • swap(0, 6) => [(0, 7), (1, 1), (2, 3), (3, 2), (4, 4), (5, 5), (6, 6)]

C'est à dire. sémantiquement, vous triez les éléments, puis découvrez comment les remettre à l'état initial en échangeant l'élément le plus à gauche qui n'est pas à sa place.

L'algorithme Python est aussi simple que cela:

def swap(arr, i, j):
    arr[i], arr[j] = arr[j], arr[i]


def minimum_swaps(arr):
    annotated = [*enumerate(arr)]
    annotated.sort(key = lambda it: it[1])

    count = 0

    i = 0
    while i < len(arr):
        if annotated[i][0] == i:
            i += 1
            continue
        swap(annotated, i, annotated[i][0])
        count += 1

    return count

Ainsi, vous n'avez pas besoin de mémoriser les nœuds visités ni de calculer une certaine longueur de cycle.

15
Archibald

Pour votre référence, voici un algorithme que j'ai écrit, pour générer le nombre minimum de swaps nécessaires pour trier le tableau. Il trouve les cycles décrits par @Andrew Mao.

/**
 * Finds the minimum number of swaps to sort given array in increasing order.
 * @param ar array of <strong>non-negative distinct</strong> integers. 
 *           input array will be overwritten during the call!
 * @return min no of swaps
 */
public int findMinSwapsToSort(int[] ar) {
    int n = ar.length;
    Map<Integer, Integer> m = new HashMap<>();
    for (int i = 0; i < n; i++) {
        m.put(ar[i], i);
    }
    Arrays.sort(ar);
    for (int i = 0; i < n; i++) {
        ar[i] = m.get(ar[i]);
    }
    m = null;
    int swaps = 0;
    for (int i = 0; i < n; i++) {
        int val = ar[i];
        if (val < 0) continue;
        while (val != i) {
            int new_val = ar[val];
            ar[val] = -1;
            val = new_val;
            swaps++;
        }
        ar[i] = -1;
    }
    return swaps;
}
7
bekce

Nous n'avons pas besoin d'échanger les éléments réels, il suffit de trouver combien d'éléments ne sont pas dans le bon index (Cycle). Les swaps min seront Cycle - 1; Voici le code ...

static int minimumSwaps(int[] arr) {
        int swap=0;
        boolean visited[]=new boolean[arr.length];

        for(int i=0;i<arr.length;i++){
            int j=i,cycle=0;

            while(!visited[j]){
                visited[j]=true;
                j=arr[j]-1;
                cycle++;
            }

            if(cycle!=0)
                swap+=cycle-1;
        }
        return swap;


    }
5
loneWolf2019

// En supposant que nous n'ayons affaire qu'à une séquence commencée par zéro

function minimumSwaps(arr) {
    var len = arr.length
    var visitedarr = []
    var i, start, j, swap = 0
    for (i = 0; i < len; i++) {
        if (!visitedarr[i]) {
            start = j = i
            var cycleNode = 1
            while (arr[j] != start) {
                j = arr[j]
                visitedarr[j] = true
                cycleNode++
            }
            swap += cycleNode - 1
        }
    }
    return swap
}
2
Darshan Puttaswamy

@Archibald, j'aime votre solution, et telle était ma supposition initiale que trier le tableau serait la solution la plus simple, mais je ne vois pas la nécessité de passer par l'effort de la marche arrière comme je l'ai surnommé, c'est-à-dire énumérer puis trier le tableau puis calculer les échanges pour les énumérations.

Je trouve plus simple de soustraire 1 de chaque élément du tableau, puis de calculer les swaps nécessaires pour trier cette liste

voici mon Tweak/solution:

def swap(arr, i, j):
    tmp = arr[i]
    arr[i] = arr[j]
    arr[j] = tmp

def minimum_swaps(arr):

    a = [x - 1 for x in arr]

    swaps = 0
    i = 0
    while i < len(a):
        if a[i] == i:
            i += 1
            continue
        swap(a, i, a[i])
        swaps += 1

    return swaps

Quant à prouver l'optimalité, je pense que @arax a un bon point.

2
Ieuan Uys

Version Swift 4:

func minimumSwaps(arr: [Int]) -> Int {

      struct Pair {
         let index: Int
         let value: Int
      }

      var positions = arr.enumerated().map { Pair(index: $0, value: $1) }
      positions.sort { $0.value < $1.value }
      var indexes = positions.map { $0.index }

      var swaps = 0
      for i in 0 ..< indexes.count {
         var val = indexes[i]
         if val < 0 {
            continue // Already visited.
         }
         while val != i {
            let new_val = indexes[val]
            indexes[val] = -1
            val = new_val
            swaps += 1
         }
         indexes[i] = -1
      }
      return swaps
}
2
Vlad

En Javascript

Si le compte du tableau commence par 1

function minimumSwaps(arr) {
   var len = arr.length
    var visitedarr = []
    var i, start, j, swap = 0
    for (i = 0; i < len; i++) {
        if (!visitedarr[i]) {
            start = j = i
            var cycleNode = 1
            while (arr[j] != start + 1) {
                j = arr[j] - 1
                visitedarr[j] = true
                cycleNode++
            }
            swap += cycleNode - 1
        }
    }
    return swap
}

sinon pour entrée commençant par 0

function minimumSwaps(arr) {
    var len = arr.length
    var visitedarr = []
    var i, start, j, swap = 0
    for (i = 0; i < len; i++) {
        if (!visitedarr[i]) {
            start = j = i
            var cycleNode = 1
            while (arr[j] != start) {
                j = arr[j]
                visitedarr[j] = true
                cycleNode++
            }
            swap += cycleNode - 1
        }
    }
    return swap
}

Extension du code Darshan Puttaswamy pour les entrées HackerEarth actuelles

1
iRohitBhatia

Solution bien faite par @bekce. Si vous utilisez C #, le code initial de configuration du tableau modifié ar peut être exprimé succinctement comme suit:

var origIndexes = Enumerable.Range(0, n).ToArray();
Array.Sort(ar, origIndexes);

puis utilisez origIndexes au lieu de ar dans le reste du code.

1
Steve Faiwiszewski

Code Python

A = [4,3,2,1]
count = 0
for i in range (len(A)):
    min_idx = i
    for j in range (i+1,len(A)):
        if A[min_idx] > A[j]:
            min_idx = j
    if min_idx > i:
        A[i],A[min_idx] = A[min_idx],A[i]
        count = count + 1
print "Swap required : %d" %count
0
Ankit

Une implémentation sur des entiers avec des types primitifs dans Java (et tests).

import Java.util.Arrays;

public class MinSwaps {
  public static int computate(int[] unordered) {
    int size = unordered.length;
    int[] ordered = order(unordered);
    int[] realPositions = realPositions(ordered, unordered);
    boolean[] touchs = new boolean[size];
    Arrays.fill(touchs, false);
    int i;
    int landing;
    int swaps = 0;

    for(i = 0; i < size; i++) {
      if(!touchs[i]) {
        landing = realPositions[i];

        while(!touchs[landing]) {
          touchs[landing] = true;
          landing = realPositions[landing];

          if(!touchs[landing]) { swaps++; }
        }
      }
    }

    return swaps;
  }

  private static int[] realPositions(int[] ordered, int[] unordered) {
    int i;
    int[] positions = new int[unordered.length];

    for(i = 0; i < unordered.length; i++) {
      positions[i] = position(ordered, unordered[i]);
    }

    return positions;
  }

  private static int position(int[] ordered, int value) {
    int i;

    for(i = 0; i < ordered.length; i++) {
      if(ordered[i] == value) {
        return i;
      }
    }

    return -1;
  }

  private static int[] order(int[] unordered) {
    int[] ordered = unordered.clone();
    Arrays.sort(ordered);

    return ordered;
  }
}

Les tests

import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class MinimumSwapsSpec {
  @Test
  public void example() {
    // setup
    int[] unordered = new int[] { 40, 23, 1, 7, 52, 31 };

    // run
    int minSwaps = MinSwaps.computate(unordered);

    // verify
    assertEquals(5, minSwaps);
  }

  @Test
  public void example2() {
    // setup
    int[] unordered = new int[] { 4, 3, 2, 1 };

    // run
    int minSwaps = MinSwaps.computate(unordered);

    // verify
    assertEquals(2, minSwaps);
  }

  @Test
  public void example3() {
    // setup
    int[] unordered = new int[] {1, 5, 4, 3, 2};

    // run
    int minSwaps = MinSwaps.computate(unordered);

    // verify
    assertEquals(2, minSwaps);
  }
}
0
Claudio Carcaci

Swift 4.2:

func minimumSwaps(arr: [Int]) -> Int {
    let sortedValueIdx = arr.sorted().enumerated()
        .reduce(into: [Int: Int](), { $0[$1.element] = $1.offset })

    var checked = Array(repeating: false, count: arr.count)
    var swaps = 0

    for idx in 0 ..< arr.count {
        if checked[idx] { continue }

        var edges = 1
        var cursorIdx = idx
        while true {
            let cursorEl = arr[cursorIdx]
            let targetIdx = sortedValueIdx[cursorEl]!
            if targetIdx == idx {
                break
            } else {
                cursorIdx = targetIdx
                edges += 1
            }
            checked[targetIdx] = true
        }
        swaps += edges - 1
    }

    return swaps
}
0
duan

Voici une solution en Java pour ce que @Archibald a déjà expliqué.

    static int minimumSwaps(int[] arr){
        int swaps = 0;
        int[] arrCopy = arr.clone();
        HashMap<Integer, Integer> originalPositionMap
                = new HashMap<>();
        for(int i = 0 ; i < arr.length ; i++){
            originalPositionMap.put(arr[i], i);
        }

        Arrays.sort(arr);
        for(int i = 0 ; i < arr.length ; i++){
            while(arr[i] != arrCopy[i]){
                //swap
                int temp = arr[i];
                arr[i] = arr[originalPositionMap.get(temp)];
                arr[originalPositionMap.get(temp)] = temp;
                swaps += 1;
            }
        }
        return swaps;
    }
0
Prateek Bhuwania

Il s'agit de l'exemple de code en C++ qui trouve le nombre minimum de swaps pour trier une permutation de la séquence de (1,2,3,4,5,.......n-2,n-1,n)

#include<bits/stdc++.h>
using namespace std;


int main()
{
    int n,i,j,k,num = 0;
    cin >> n;
    int arr[n+1];
    for(i = 1;i <= n;++i)cin >> arr[i];
    for(i = 1;i <= n;++i)
    {
        if(i != arr[i])// condition to check if an element is in a cycle r nt
        {
            j = arr[i];
            arr[i] = 0;
            while(j != 0)// Here i am traversing a cycle as mentioned in 
            {             // first answer
                k = arr[j];
                arr[j] = j;
                j = k;
                num++;// reducing cycle by one node each time
            }
            num--;
        }
    }
    for(i = 1;i <= n;++i)cout << arr[i] << " ";cout << endl;
    cout << num << endl;
    return 0;
}
0
Vinayak Sangar