web-dev-qa-db-ja.com

シーケンスを注文するための最小スワップ数を計算します

同一番号のない整数シーケンスを(一般性を失うことなく、そのシーケンスが1,2,...,nの順列であると仮定して)自然の昇順(つまり1,2,...,n)にソートする作業をしています。要素を直接交換することを考えていました(要素の位置に関係なく、つまり、2つの要素に対して有効な交換です)。最小限の交換で実行できます(次の解決策も考えられます)。

1つまたは両方の要素を正しい位置に入れ替える必要があるという制約で2つの要素を入れ替えます。すべての要素が正しい位置に配置されるまで。

しかし、上記のソリューションが最適であるかどうかを数学的に証明する方法がわかりません。誰でも手伝ってくれる?

40
mintaka

graph-theory でこれを証明することができました。そのタグを:)に追加したいかもしれません

n頂点を持つグラフを作成します。ノードn_iからn_jへのエッジを作成します。位置iの要素が正しい順序でjの位置にある必要があります。これで、いくつかの非交差サイクルで構成されるグラフができます。グラフを正しく順序付けるために必要なスワップの最小数は

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

少し時間をかけて、それを納得してください... 2つのアイテムがサイクル内にある場合、1つのスワップでそれらを処理できます。 3つのアイテムがサイクルにある場合、ペアを交換して1つを正しい場所に配置し、2サイクルを残すなどできます。nアイテムがサイクルにある場合は、n-1が必要ですスワップ。 (これは、すぐ隣の人とスワップしない場合でも常に当てはまります。)

これで、アルゴリズムが最適である理由を確認できるようになります。入れ替えを行い、少なくとも1つのアイテムが正しい位置にある場合、Mの値は常に1ずつ減少します。長さnのサイクルの場合、要素をその隣人が占める正しいスポット。これで、正しく順序付けられた要素と長さn-1のサイクルができました。

Mはスワップの最小数であり、アルゴリズムはMをスワップごとに常に1ずつ減らすため、最適である必要があります。

61
Andrew Mao

えーと、すべての循環計算は頭の中で維持するのが非常に困難です。覚える方がはるかに簡単な方法があります。

まず、手作業でサンプルケースを投げましょう。

  • シーケンス:[7、1、3、2、4、5、6]
  • 列挙する:[(0、7)、(1、1)、(2、3)、(3、2)、(4、4)、(5、5)、(6、6)]
  • 列挙を値で並べ替えます:[(1、1)、(3、2)、(2、3)、(4、4)、(5、5)、(6、6)、(0、 7)]
  • 最初から始めましょう。インデックスが列挙されたインデックスとは異なる間、インデックスと列挙されたインデックスによって定義された要素を交換し続けます。注意:swap(0,2);swap(0,3)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)]

つまり意味的には、要素を並べ替え、配置されていない左端のアイテムをスワップして、要素を初期状態にする方法を見つけます。

Pythonアルゴリズムは次のように単純です。

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

したがって、訪問したノードを記憶したり、サイクルの長さを計算したりする必要はありません。

15
Archibald

参考までに、配列をソートするのに必要な最小数のスワップを生成するために、私が書いたアルゴリズムを以下に示します。 @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

実際の要素を交換する必要はありません。正しいインデックス(Cycle)にない要素の数を見つけるだけです。最小スワップはサイクル-1になります。これがコードです...

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

//ゼロから始まるシーケンスのみを処理するとします

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

@Archibald、私はあなたの解決策が好きで、配列をソートすることが最も簡単な解決策であるという私の最初の仮定でしたが、私がそれを吹き替えたように、逆トラバースの労力を経る必要があるとは思いません、すなわち配列を列挙してから並べ替え、列挙型のスワップを計算します。

配列の各要素から1を減算し、そのリストをソートするために必要なスワップを計算する方が簡単だと思います

ここに私の微調整/解決策があります:

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

最適性を証明することに関しては、@ araxが良い点だと思います。

2
Ieuan Uys

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

JavaScriptで

配列のカウントが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
}

それ以外の場合、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
}

現在のHackerEarth入力に対してDarshan Puttaswamyコードを拡張するだけ

1
iRohitBhatia

@bekceによる解決策。 C#を使用する場合、変更された配列arをセットアップする初期コードは、次のように簡潔に表現できます。

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

次に、残りのコードでorigIndexesの代わりにarを使用します。

1

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

Java(およびテスト))のプリミティブ型の整数の実装。

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;
  }
}

テスト

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

ここにJavaの解決策があります。@ Archibaldがすでに説明したものです。

    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

これは、C++のサンプルコードで、(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