Algoritma nontrivial untuk menghitung median sliding window

25

Saya perlu menghitung median berjalan:

  • Input: , k , vektor ( x 1 , x 2 , ... , x n ) .nk(x1,x2,,xn)

  • Output: vektor , di mana y i adalah median dari ( x i , x i + 1 , ... , x i + k - 1 ) .(y1,y2,,ynk+1)yi(xi,xi+1,,xi+k1)

(Tidak ada kecurangan dengan perkiraan; Saya ingin memiliki solusi yang tepat. Elemen adalah bilangan bulat besar.)xi

Ada algoritma sepele yang memelihara pohon pencarian ukuran ; total waktu berjalan adalah O ( n log k ) . (Di sini "pohon pencarian" mengacu pada beberapa struktur data efisien yang mendukung penyisipan, penghapusan, dan kueri median dalam waktu logaritmik.)kO(nlogk)

Namun, ini agak bodoh bagi saya. Kami akan secara efektif mempelajari semua statistik pesanan dalam semua jendela ukuran , bukan hanya median. Selain itu, ini tidak terlalu menarik dalam praktiknya, terutama jika k besar (pohon pencarian besar cenderung lambat, overhead dalam konsumsi memori adalah non-sepele, efisiensi cache sering buruk, dll).kk

Bisakah kita melakukan sesuatu yang jauh lebih baik?

Adakah batas bawah (misalnya, apakah algoritma trivial asimtotik optimal untuk model perbandingan)?


Sunting: David Eppstein memberikan batas bawah yang bagus untuk model perbandingan! Saya bertanya-tanya apakah mungkin untuk melakukan sesuatu yang sedikit lebih pintar daripada algoritma sepele?

Sebagai contoh, dapatkah kita melakukan sesuatu di sepanjang baris ini: membagi vektor input ke bagian-bagian berukuran ; mengurutkan setiap bagian (melacak posisi asli setiap elemen); dan kemudian menggunakan vektor disortir piecewise untuk menemukan median berjalan secara efisien tanpa struktur data tambahan? Tentu saja ini masih O ( n log k ) , tetapi dalam praktiknya sortasi array cenderung jauh lebih cepat daripada mempertahankan pohon pencarian.kO(nlogk)


Sunting 2: Saeed ingin melihat beberapa alasan mengapa saya pikir penyortiran lebih cepat daripada operasi pencarian pohon. Berikut ini adalah tolok ukur yang sangat cepat, untuk , n = 10 8 :k=107n=108

  • ≈ 8s: masing-masing menyortir vektor dengan elemen kn/kk
  • ≈ 10d: menyortir vektor dengan elemenn
  • ≈ 80an: penyisipan & penghapusan dalam tabel hash dengan ukuran knk
  • ≈ 390an: penyisipan & penghapusan di pohon pencarian seimbang ukuran knk

Tabel hash ada hanya untuk perbandingan; itu tidak ada gunanya langsung dalam aplikasi ini.

Singkatnya, kami memiliki hampir faktor 50 perbedaan dalam kinerja operasi pohon pencarian penyortiran vs seimbang. Dan segalanya menjadi lebih buruk jika kita meningkatkan .k

(Detail teknis: Data = bilangan bulat 32-bit acak. Komputer = laptop modern tipikal. Kode tes ditulis dalam C ++, menggunakan rutin perpustakaan standar (std :: sort) dan struktur data (std :: multiset, std :: unsorted_multiset) Saya menggunakan dua kompiler C ++ yang berbeda (GCC dan Dentang), dan dua implementasi berbeda dari library standar (libstdc ++ dan libc ++) .Secara tradisional, std :: multiset telah diimplementasikan sebagai pohon merah-hitam yang sangat dioptimalkan.)

Jukka Suomela
sumber
1
Saya tidak berpikir Anda akan dapat meningkatkan . Pasalnya, jika Anda melihat jendela x t , . . . , X t + k - 1 , Anda tidak pernah mengesampingkan salah satu nomor x t + knlogkxt,...,xt+k1dari median jendela masa depan. Ini berarti bahwa setiap saat Anda harus menyimpan setidaknyakxt+k2,...,xt+k1 bilangan bulat dalam struktur data, dan sepertinya tidak diperbarui dalam waktu kurang dari waktu log. k2
RB
Algoritma sepele Anda bagi saya tampaknya bukan O ( n log k ) , apakah saya salah mengerti sesuatu? Dan saya pikir karena ini Anda memiliki masalah dengan k besar , jika faktor logaritmik tidak ada dalam aplikasi praktis, juga tidak ada konstanta tersembunyi yang besar dalam algoritma ini. O((nk)klogk)O(nlogk)k
Saeed
@ Saeed: Dalam algoritma sepele, Anda memproses elemen satu per satu; pada langkah Anda menambahkan x i ke pohon pencarian dan (jika i > k ) Anda juga menghapus x i - k dari pohon pencarian. Ini adalah n langkah, yang masing-masing membutuhkan waktu O ( log k ) . ixii>kxiknO(logk)
Jukka Suomela
Jadi maksud Anda Anda memiliki pohon pencarian seimbang bukan pohon pencarian kasual?
Saeed
1
@ Saeed: Harap dicatat bahwa dalam tolok ukur saya, saya bahkan tidak mencoba mencari median. Aku hanya melakukan sisipan dan n penghapusan dalam pohon pencarian dari ukuran k , dan operasi ini dijamin untuk mengambil O ( log k ) waktu. Anda hanya perlu menerima bahwa operasi pohon pencarian sangat lambat dalam praktiknya, dibandingkan dengan penyortiran. Anda akan melihat ini dengan mudah jika Anda mencoba untuk menulis algoritma penyortiran yang berfungsi dengan menambahkan elemen ke pohon pencarian seimbang - itu pasti bekerja dalam waktu O ( n log n ) , tetapi akan sangat lambat dalam prakteknya, dan juga membuang banyak memori. nnkO(logk)O(nlogn)
Jukka Suomela

Jawaban:

32

Inilah batas bawah dari penyortiran. Diberikan set input dengan panjang n yang akan diurutkan, buat input untuk masalah median Anda yang sedang berjalan yang terdiri dari n - 1 salinan angka lebih kecil dari minimum S , kemudian S sendiri, lalu n - 1 salinan angka lebih besar dari maksimum S , dan atur k = 2 n - 1 . Median berjalan dari masukan ini adalah sama dengan urutan diurutkan S .Snn1SSn1Sk=2n1S

Jadi dalam model perbandingan perhitungan, diperlukan waktu . Mungkin jika input Anda adalah bilangan bulat dan Anda menggunakan algoritma pengurutan bilangan bulat yang dapat Anda lakukan dengan lebih baik.Ω(nlogn)

David Eppstein
sumber
6
Jawaban ini benar-benar membuat saya bertanya-tanya apakah kebalikannya juga berlaku: diberikan algoritma penyortiran yang efisien, apakah kita mendapatkan algoritma median berjalan yang efisien? (Misalnya, apakah pada algoritma penyortiran integer efisien menyiratkan algoritma median berjalan efisien untuk integer? Atau apakah algoritma penyortiran efisien-IO menyediakan algoritma median berjalan efisien-IO?)
Jukka Suomela
1
Sekali lagi, terima kasih banyak atas jawaban Anda, itu benar-benar membuat saya berada di jalur yang benar dan memberikan inspirasi untuk algoritma filter median berdasarkan penyortiran! Pada akhirnya saya dapat menemukan makalah dari tahun 1991 yang pada dasarnya menyajikan argumen yang sama dengan apa yang Anda berikan di sini, dan Pat Morin memberikan petunjuk ke makalah lain yang relevan dari tahun 2005; lihat referensi. [6] dan [9] di sini .
Jukka Suomela
9

Sunting: Algoritma ini sekarang disajikan di sini: http://arxiv.org/abs/1406.1717


Ya, untuk mengatasi masalah ini, cukup melakukan operasi berikut:

  • Urutkan vektor , masing-masing dengan elemen k .n/kk
  • Lakukan pasca-pemrosesan linear-waktu.

Sangat kasar, idenya adalah ini:

  • Pertimbangkan dua blok input yang berdekatan, dan b , keduanya dengan elemen k ; biarkan elemen menjadi sebuah 1 , sebuah 2 , . . . , Sebuah k dan b 1 , b 2 , . . . , B k di urutan penampilan di vektor input x .abka1,a2,...,akb1,b2,...,bkx
  • Urutkan blok ini dan pelajari peringkat setiap elemen di dalam blok.
  • Menambah vektor dan b dengan pointer pendahulu / penerus sehingga dengan mengikuti rantai penunjuk kita dapat melintasi elemen dalam urutan yang meningkat. Dengan cara ini kita telah membangun ganda terkait daftar a ' dan b ' .abab
  • Satu per satu, menghapus semua elemen dari daftar terkait , dalam urutan terbalik dari penampilan b k , b k - 1 , . . . , b 1 . Setiap kali kami menghapus suatu elemen, ingat apa penggantinya & pendahulunya pada saat penghapusan .bbk,bk1,...,b1
  • Sekarang mempertahankan "pointer median" dan q yang mengarah ke daftar a ' dan b ' , masing-masing. Menginisialisasinya p ke titik tengah sebuah ' , dan Menginisialisasinya q ke ekor kosong daftar b ' .pqabpaqb
  • Untuk setiap :i

    • Menghapus dari daftar a ' (ini adalah O ( 1 ) waktu, hapus saja dari linked list). Bandingkan sebuah i dengan elemen ditunjuk oleh p untuk melihat apakah kita dihapus sebelum atau setelah p .aiaO(1)aipp
    • Masukkan kembali ke daftar b di posisi semula (ini O ( 1 ) kali, kami menghafal pendahulu dan penerus b i ). Bandingkan b i dengan elemen yang ditunjuk oleh q untuk melihat apakah kita menambahkan elemen sebelum atau setelah q .bibO(1)bibiqq
    • pqabpqO(1)pqpq

k


Berikut adalah contoh penerapan dan tolok ukur:

n2106

  • O(nlogk)
  • O(nlogk)
  • O(nlogk)
  • O(nk)
  • k/2
  • Sumbu Y = waktu berjalan dalam detik.
  • Data = bilangan bulat 32-bit dan bilangan bulat 64-bit acak, dari berbagai distribusi.

waktu berjalan

Jukka Suomela
sumber
3

mO(nlogm+mlogn)

O(logm)O(logn)O(logn) biaya hanya terjadi satu kali per median.

O(nlogm+mlogk)

Geoffrey Irving
sumber
Ups, ini tidak berfungsi seperti yang tertulis, karena jika Anda tidak menghapus elemen, penghitungan tidak akan mencerminkan jendela baru. Saya tidak yakin apakah itu bisa diperbaiki, tetapi saya akan meninggalkan jawabannya kalau-kalau ada cara.
Geoffrey Irving
O(nlogm)
catatan: Pertanyaan tidak jelas, struktur data bawahan tidak didefinisikan, kita hanya tahu sesuatu yang sangat kabur. bagaimana Anda ingin meningkatkan sesuatu yang Anda tidak tahu apa itu? bagaimana Anda ingin membandingkan pendekatan Anda?
Saeed
1
Saya minta maaf atas pekerjaan yang tidak lengkap. Saya telah mengajukan pertanyaan konkret yang diperlukan untuk memperbaiki jawaban ini di sini: cstheory.stackexchange.com/questions/21778/… . Jika menurut Anda itu sesuai, saya dapat menghapus jawaban ini sampai pertanyaan kedua diselesaikan.
Geoffrey Irving