Apakah mungkin untuk menemukan apakah ada urutan dalam waktu polinomial dalam masalah berikut?

27

Saya sudah memikirkan masalah berikut untuk sementara waktu, dan saya belum menemukan solusi polinomial untuk itu. Hanya sumber kasar. Saya sudah mencoba untuk mengurangi masalah NP-Complete ke dalamnya juga tanpa keberhasilan.

Inilah masalahnya :


Anda memiliki set yang diurutkan {(A1,B1),(A2,B2),,(An,Bn)} dari pasangan bilangan bulat positif.

(Ai,Bi)<(Aj,Bj)Ai<Aj(Ai=AjBi<Bj) (Ai,Bi)=(Aj,Bj)Ai=AjBi=Bj

Operasi berikut dapat diterapkan untuk sepasang: Swap(pair). Ini menukar elemen pasangan, sehingga (10,50) akan menjadi (50,10)

Ketika pasangan di set ditukar, set secara otomatis akan diurutkan lagi (pasangan bertukar tidak pada tempatnya dan itu akan dipindahkan ke tempatnya di set)

Masalahnya terdiri pada melihat apakah ada urutan yang, dimulai pada beberapa pasangan, menukar seluruh rangkaian, dengan kondisi berikut:

Setelah satu pasangan ditukar, pasangan berikutnya yang akan ditukar harus menjadi penerus atau pasangan predecesor di set.


Akan sangat bagus untuk menemukan solusi waktu polinomial untuk masalah ini, atau pengurangan masalah NP-Complete ke dalamnya.

Catatan:
Ini sudah menjadi masalah keputusan. Saya tidak ingin tahu urutan mana: hanya jika ada urutan.

Contoh bagaimana set akan diurutkan setelah bertukar pasangan

(6, 5)
(1,2)
(3,4)
(7,8)

Jika saya menukar pasangan pertama, itu menjadi: (5,6) , dan setelah mengurutkan set (menempatkan pasangan yang diurutkan di posisi baru), kita memiliki:

(1,2)
(3,4)
(5,6)
(7,8)

Maka saya harus menukar salah satu (pendahulu) atau ( 7 , 8 ) (penerus), dan ulangi prosesnya sampai semua pasangan bertukar (jika memungkinkan).(3,4)(7,8)

Penting:
Anda tidak dapat menukar pasangan yang sudah ditukar.
Jika ada urutan operasi 'swap', maka semua pasangan harus diganti namanya menjadi sekali dan hanya sekali.

Contoh di mana tidak mungkin untuk menukar semua pasangan

( 1 , 4 ) ( 3 , 2 )(0,0)
(1,4)
(3,2)
(5,5)

Oscar Mederos
sumber
1
Apakah daftar diurutkan setelah Anda mengganti nama file dan sebelum Anda memilih file berikutnya untuk mengubah nama? Bisakah Anda menulis ulang kondisi penyortiran sebagai: iff ( A < A ) atau ( A = A dan B < B ) atau ( A = A Dan B = B dan C < C (A,B,C)<(A,B,C)A<AA=AB<BA=AB=BC<C )?
mjqxxxx
3
Masalah penugasan tidak diterima di cstheory.stackexchange.com secara umum.
Tsuyoshi Ito
3
Hmm, saya tidak yakin. Biasanya logika di sini adalah bahwa itu bukan praktik yang baik untuk menjawab pertanyaan pekerjaan rumah yang khas karena hal itu akan merusak tujuan pekerjaan rumah bagi seseorang di masa depan. Tetapi dalam kasus ini, masalahnya tidak seperti masalah biasa .
Tsuyoshi Ito
2
mungkin jika Anda memberikan motivasi yang berbeda dari "itu adalah pekerjaan rumah", orang bisa tertarik dan itu tidak akan ditutup. Apa yang bisa menjadi aplikasi ini?
Marcos Villagra
2
tentang merumuskan ulang masalah, Anda dapat melupakan file dan melihatnya dengan cara ini. Anda memiliki satu set pasangan bilangan bulat positif , dan aturannya sama seperti yang Anda katakan. Awalnya disortir di kolom pertama, lalu Anda mulai mengganti nama poin. A={(x1,y1),,(xn,yn)}
Marcos Villagra

Jawaban:

16

... Saya mencari beberapa pola untuk membuat pengurangan dari masalah NPC, tetapi tidak menemukan cara untuk mewakili "aliran" dengan "garpu" ...

Jadi (setelah beberapa pekerjaan) ini adalah algoritma polinomial ...

ALGORITMA

Daftar awal dapat dilihat sebagai larik " lubang " berurutan . Untuk setiap pasangan awal ( a j , b j ) , letakkan " elemen " b j pada nomor lubang a j . Setiap pasangan dapat dilihat sebagai tepi diarahkan dari posisi suatu j ke posisi b j . Sebuah langkah terdiri dalam memilih sebuah elemen b j pada posisi yang j dan pindah ke nya posisi tujuan b jN2(aj,bj)bjajajbjbjajbj(lubang tujuan menjadi pasak tidak bergerak ). Kami menghapus tepi, dan lanjutkan untuk memilih langkah selanjutnya yang akan dimulai dari salah satu dari dua unsur dicapai terdekat dari posisi b j (hanya lubang antara b j dan b k diperbolehkan). Kami harus menemukan urutan gerakan N berturut-turut.bkbjbjbkN

  • Untuk masing-masing pertimbangkan b j (pada posisi larik a j ) sebagai elemen awal s t a r t .(aj,bj)bjajstart

    • Untuk masing-masing pertimbangkan a k sebagai elemen terakhir e n d (tepi dari posisi a k ke posisi b k(ak,bk),akajakendakbk akan menjadi ujung akhir).

      • menghasilkan urutan gerakan dari menggunakan kriteria berikut sampai Anda mencapai elemen e n d (dan solusi telah ditemukan), atau kondisi berhentistartend

Ketika Anda bergerak, Anda mematok pasak pada posisi dan array dibagi menjadi dua partisi L (kiri) dan R (kanan) dan satu-satunya cara untuk beralih dari L ke R (atau dari R ke L ) menggunakan tepi yang melompat di pasak. SetbjLRLRRL

  • = jumlah tepi dari kiri ke kanan (jangan hitung tepi akhir)edgesLR
  • = jumlah tepi dari kanan ke kiri (jangan hitung tepi akhir)edgesRL
  • = e d g e s L R - e d g e s R LflowedgesLRedgesRL

Kasus:

A) jika maka salah satu dari dua partisi akan menjadi tidak terjangkau, berhenti|flow|>1

Sekarang anggaplah bahwa , yaitu e n d Rend>bjendR

B) jika maka ada keunggulan tambahan dari kiri ke kanan, Anda harus pergi kiri (memilih elemen terdekat dari L ), jika tidak, anda tidak akan pernah mencapai e n dflow=1Lend

C) jika maka ada tepi ekstra dari kanan ke kiri dan simpul apa pun yang Anda pilih, Anda tidak akan pernah mencapai e n dflow=1end , berhenti

D) jika Anda harus pergi ke kanan (memilih elemen terdekat dari R ), jika tidak Anda akan neve jangkauan e n dflow=0Rend

Jika ( e n d L ), B, C, D terbalik.end<bjendL

CATATAN: saat bergerak ke kiri atau ke kanan, Anda harus mempertimbangkan sebagai pasak. Misalnya, jika Anda harus berbelok ke kanan, tetapi elemen terdekat pada R adalah e n d maka langkah tersebut tidak mungkin (dan Anda harus melanjutkan dengan pasangan lain ( s t a r t , e n d ) )endRend(start,end)

Terapkan pengubahan ulang yang sama di setiap gerakan.

KOMPLEKSITAS

Aliran pada setiap lubang dapat dihitung ulang dalam O (N) dan digunakan kembali pada setiap pemindaian.

Loop adalah:

for start = 1 to N
  for end = 1 to N
    for move = 1 to N
      make a move (fix a peg and update flows)
      check if another move can be done using flow     

Tidak ada pilihan yang dibuat selama perhitungan, sehingga kompleksitas algoritma adalah O(N3)

KODE

Ini adalah implementasi algoritma Java yang berfungsi:

public class StrangeSort {
    static int PEG = 0xffffff, HOLE = 0x0;
    static int M = 0, N = 0, choices = 0, aux = 0, end;
    static int problem[][], moves[], edgeflow[], field[];    
    boolean is_hole(int x) { return x == HOLE; }
    boolean is_peg(int x) { return x == PEG; }
    boolean is_ele(int x) { return ! is_peg(x) && ! is_hole(x); };
    int []cp(int src[]) { // copy an array
        int res[] = new int[src.length];
        System.arraycopy(src, 0, res, 0, res.length);
        return res;
    }    
    /* find the first element on the left (dir=-1) right (dir=1) */
    int find(int pos, int dir, int nm) {
        pos += dir;
        while (pos >= 1 && pos <= M ) {
            int x = field[pos];
            if ( is_peg(x) || (pos == end && nm < N-1) ) return 0;
            if ( is_ele(x) ) return pos;
            pos += dir;
        }
        return 0;
    }
    void build_edges() {
        edgeflow = new int[M+1];
        for (int i = 1; i<=M; i++) {
            int start = i;
            int b = field[start];
            if (! is_ele(b)) continue;
            if (i == end) continue;
            int dir = (b > start)? 1 : -1;
            start += dir;
            while (start != b) { edgeflow[start] += dir; start += dir; }
        }
    }
    boolean rec_solve(int start, int nm) {
        boolean f;
        int j;
        int b = field[start];
        moves[nm++] = b;
        if (nm == N) return true;
        //System.out.println("Processing: " + start + "->" + field[start]);        
        field[start] = HOLE;
        field[b] = PEG;
        int dir = (b > start)? 1 : -1;
        int i = start + dir;
        while (i != b) { edgeflow[i] -= dir; i += dir; } // clear edge                
        int flow = edgeflow[b];
        if (Math.abs(flow) > 2) return false;
        if (end > b) {
            switch (flow) {
            case 1 :                    
                j = find(b,-1,nm);
                if (j <= 0) return false;
                return rec_solve(j,nm);
            case -1 :
                return false;
            case 0 :          
                j = find(b,1,nm);
                if (j <= 0) return false;
                return rec_solve(j,nm);
            }        
        } else {
            switch (flow) {
            case -1 :                    
                j = find(b,1,nm);
                if (j <= 0) return false;
                return rec_solve(j,nm);
            case 1 :
                return false;
            case 0 :          
                j = find(b,-1,nm);
                if (j <= 0) return false;
                return rec_solve(j,nm);
            }            
        }
        return false;
    }
    boolean solve(int demo[][]) {
        N = demo.length;
        for (int i = 0; i < N; i++)
            M = Math.max(M, Math.max(demo[i][0], demo[i][1]));
        moves = new int[N];
        edgeflow = new int[M+1];
        field = new int[M+1];
        problem = demo;        
        for (int i = 0; i < problem.length; i++) {
            int a = problem[i][0];
            int b = problem[i][1];
            if ( a < 1 || b < 1 || a > M || b > M || ! is_hole(field[a]) || ! is_hole(field[b])) {
                System.out.println("Bad input pair (" + a + "," + b + ")");
                return false;
            }
            field[a] = b;
        }
        for (int i = 1; i <= M; i++) {
            end = i;
            build_edges();
            if (!is_ele(field[i])) continue;
            for (int j = 1; j <= M; j++) {
                if (!is_ele(field[j])) continue;
                if (i==j) continue;
                int tmp_edgeflow[] = cp(edgeflow);
                int tmp_field[] = cp(field);
                choices = 0;
                //System.out.println("START: " + j + " " + " END: " + i);
                if (rec_solve(j, 0)) {
                    return true;
                }
                edgeflow = tmp_edgeflow;
                field = tmp_field;
            }
        }
        return false;
    }
    void init(int demo[][]) {

    }
    public static void main(String args[]) {
        /**** THE INPUT ********/        

        int demo[][] =  {{4,2},{5,7},{6,3},{10,12},{11,1},{13,8},{14,9}};

        /***********************/        
        String r = "";
        StrangeSort sorter = new StrangeSort();       
        if (sorter.solve(demo)) {
            for (int i = 0; i < N; i++) { // print it in clear text
                int b =  moves[i];
                for (int j = 0; j < demo.length; j++)
                    if (demo[j][1] == b)
                        r += ((i>0)? " -> " : "") + "(" + demo[j][0] + "," + demo[j][1] + ")";
            }             
            r = "SOLUTION: "+r;
        }
        else
            r = "NO SOLUTIONS";
        System.out.println(r);
    }    
}
Marzio De Biasi
sumber
Ini merupakan pendekatan yang menarik. Secara umum, setiap kali Anda menggunakan tepi , harus ada jumlah yang sama (atau berbeda satu) dari tepi yang tidak digunakan yang melintasi b di setiap arah; dan jika jumlahnya berbeda satu, Anda tahu sisi mana yang harus Anda ambil selanjutnya. Ketika angkanya sama, Anda memiliki pilihan, yang harus Anda selesaikan dengan menguji kedua opsi. Sepertinya strategi pencarian yang cukup efisien, tetapi bagaimana Anda tahu itu polinomial dalam kasus terburuk? Yaitu, bagaimana Anda tahu Anda hanya akan menemukan O ( log n ) pilihan di mana jumlah sisi yang tidak digunakan pada setiap arah sama? (a,b)bO(logn)
mjqxxxx
@mjqxxxx ... Saya menulis ulang seluruh jawaban untuk mencocokkan algoritma Java ...
Marzio De Biasi
@ mjqxxxx ... ok, akhirnya saya mendapatkannya ... :-)
Marzio De Biasi
2
Ini terlihat benar dan sangat elegan bagi saya. Setelah Anda menggunakan tepi , Anda tidak bisa lagi "berjalan" melintasi b ; satu-satunya transisi yang tersisa di b adalah "lompatan" (tepi terarah) yang tidak digunakan melintasinya, yang semuanya harus Anda gunakan. Jika tepi akhir ( a n , b n ) yang ditentukan, maka Anda perlu untuk angin di sisi yang sama dari b sebagai suatu n(a,b)bb(an,bn)ban. Maka hanya ada satu arah yang mungkin untuk berjalan setelah setiap sisi, karena jumlah lompatan (genap) ganjil akan membuat Anda berada di sisi yang berlawanan (sama) yang awalnya Anda tuju. Jadi menguji setiap pilihan ujung mulai dan ujung dapat dilakukan dalam waktu polinomial.
mjqxxxx
1
Ini adalah algoritma yang indah. Tidak pernah terpikir oleh saya untuk memperbaiki langkah terakhir terlebih dahulu. Poin minor: (1) Seperti yang ditulis mjqxxxx, end harus a_k. Kalau tidak, kondisi "end> b_j" salah. (2) Entah definisi "aliran" harus dinegasikan, atau kasus B dan C harus ditukar.
Tsuyoshi Ito
10

Ini bukan solusi, tetapi reformulasi yang menghindari penyebutan secara eksplisit operasi swapping dan sortasi. Mulailah dengan menyortir seluruh daftar nama file gabungan dan versi bertukar mereka, dan mengidentifikasi setiap nama file dengan indeks dalam daftar itu. Lalu dua file bertetangga jika semua nama file lama di antara mereka telah dihancurkan, dan jika belum ada nama file baru di antara mereka. Masalah yang dirumuskan kembali adalah sebagai berikut:

Mengingat satu set menguraikan tepi diarahkan ( a , b ) dengan a , b { 1 , 2 , ... , 2 n } , apakah ada pemesanan ( a 1 , b 1 ) , ( a 2 , b 2 ) , . . . , ( a n , b n ) dari tepi ini sedemikian rupa sehinggan(a,b)a,b{1,2,,2n}(a1,b1),(a2,b2),...,(an,bn)

  • ajbiai+1ji , dan
  • bjbiai+1ji+1
mjqxxxx
sumber
2
+1. Ini adalah cara yang lebih sederhana untuk menyatakan masalah yang setara. Hanya satu klarifikasi: tepi (a, b) diarahkan (dalam arti bahwa tepi (a, b) dan tepi (b, a) memiliki arti yang berbeda).
Tsuyoshi Ito
@ Tsuyoshi: terima kasih; Saya mengedit untuk mengatakan 'diarahkan'.
mjqxxxx
bacabc
@Oleksandr: Di sini "b adalah antara a dan c" berarti "baik a <b <c atau c <b <a."
Tsuyoshi Ito