Mengapa fungsi terbalik untuk std::list
kelas di pustaka standar C ++ memiliki runtime linier? Saya akan berpikir bahwa untuk daftar yang ditautkan dua kali lipat fungsi terbalik seharusnya O (1).
Membalik daftar yang tertaut ganda harus melibatkan penggantian kepala dan ujung ekor.
c++
c++11
stl
linked-list
Ingin tahu
sumber
sumber
Reverse
fungsi diimplementasikan dalam O (1)?Jawaban:
Secara hipotesis,
reverse
bisa jadi O (1) . Di sana (lagi-lagi secara hipotetis) mungkin ada anggota daftar boolean yang menunjukkan apakah arah daftar yang ditautkan saat ini sama atau berlawanan dengan yang asli tempat daftar itu dibuat.Sayangnya, itu pada dasarnya akan mengurangi kinerja operasi lainnya (walaupun tanpa mengubah runtime asimptotik). Dalam setiap operasi, boolean perlu dikonsultasikan untuk mempertimbangkan apakah mengikuti pointer "selanjutnya" atau "sebelumnya" dari suatu tautan.
Karena ini mungkin dianggap sebagai operasi yang relatif jarang, standar (yang tidak menentukan implementasi, hanya kompleksitas), menetapkan bahwa kompleksitas dapat linier. Hal ini memungkinkan pointer "berikutnya" selalu berarti arah yang sama secara jelas, mempercepat operasi kasus biasa.
sumber
reverse
denganO(1)
kompleksitas tanpa mempengaruhi besar-o dari operasi lainnya , dengan menggunakan ini boolean bendera trik. Tetapi, dalam praktiknya cabang tambahan dalam setiap operasi itu mahal, bahkan jika secara teknis O (1). Sebaliknya, Anda tidak dapat membuat struktur daftar yangsort
O (1) dan semua operasi lainnya adalah biaya yang sama. Inti dari pertanyaan adalah bahwa, tampaknya, Anda dapatO(1)
membalikkan secara gratis jika Anda hanya peduli pada O besar, jadi mengapa mereka tidak melakukan itu.std::uintptr_t
. Kemudian Anda dapat xor mereka.std::uintptr_t
, Anda bisa menggunakanchar
array dan kemudian XOR komponen. Akan lebih lambat tapi portabel 100%. Kemungkinan Anda bisa membuatnya memilih antara kedua implementasi ini dan hanya menggunakan yang kedua sebagai fallback jikauintptr_t
hilang. Beberapa jika dijelaskan dalam jawaban ini: stackoverflow.com/questions/14243971/…Ini bisa menjadi O (1) jika daftar akan menyimpan flag yang memungkinkan bertukar makna pointer "
prev
" dan "next
" yang dimiliki masing-masing node. Jika membalik daftar akan menjadi operasi yang sering, penambahan semacam itu mungkin sebenarnya berguna dan saya tidak tahu alasan mengapa menerapkannya akan dilarang oleh standar saat ini. Namun, memiliki bendera seperti itu akan membuat traversal biasa dari daftar lebih mahal (jika hanya dengan faktor konstan) karena alih-alihdi
operator++
daftar iterator, Anda akan dapatkanyang bukan sesuatu yang akan Anda putuskan untuk ditambahkan dengan mudah. Mengingat bahwa daftar biasanya dilalui jauh lebih sering daripada terbalik, akan sangat tidak bijaksana jika standar mengamanatkan teknik ini. Oleh karena itu, operasi sebaliknya diizinkan untuk memiliki kompleksitas linier. Namun, perhatikan bahwa t ∈ O (1) ⇒ t ∈ O ( n ) jadi, seperti yang disebutkan sebelumnya, menerapkan "optimisasi" Anda secara teknis akan diizinkan.
Jika Anda berasal dari Jawa atau latar belakang serupa, Anda mungkin bertanya-tanya mengapa iterator harus memeriksa bendera setiap kali. Tidak bisakah sebaliknya kita memiliki dua jenis iterator yang berbeda, keduanya berasal dari tipe basis yang sama, dan memiliki
std::list::begin
dan secarastd::list::rbegin
polimorfik mengembalikan iterator yang sesuai? Meskipun mungkin, ini akan membuat semuanya menjadi lebih buruk karena memajukan iterator akan menjadi panggilan fungsi tidak langsung (sulit untuk inline) sekarang. Di Jawa, Anda membayar harga ini secara rutin, tetapi sekali lagi, ini adalah salah satu alasan banyak orang meraih C ++ ketika kinerja sangat penting.Seperti yang ditunjukkan oleh Benjamin Lindley dalam komentar, karena
reverse
tidak diperbolehkan untuk membatalkan iterator, satu-satunya pendekatan yang diizinkan oleh standar tampaknya menyimpan pointer kembali ke daftar di dalam iterator yang menyebabkan akses memori tidak langsung ganda.sumber
std::list::reverse
tidak membatalkan iterator.next
danprev
pointer dalam array, dan simpan arah sebagai a0
atau1
. Untuk beralih ke depan, Anda akan mengikutipointers[direction]
dan mengulanginya secara terbalikpointers[1-direction]
(atau sebaliknya). Ini masih akan menambah sedikit overhead, tetapi mungkin kurang dari satu cabang.swap()
ditetapkan sebagai waktu konstan dan tidak membatalkan iterator apa pun.Tentunya karena semua wadah yang mendukung iterator dua arah memiliki konsep rbegin () dan rend (), pertanyaan ini apakah bisa diperdebatkan?
Itu sepele untuk membangun proxy yang membalik iterator dan mengakses wadah melalui itu.
Non-operasi ini memang O (1).
seperti:
output yang diharapkan:
Mengingat hal ini, bagi saya tampaknya komite standar tidak meluangkan waktu untuk mengamanatkan O (1) memesan kembali wadah karena itu tidak perlu, dan perpustakaan standar sebagian besar dibangun berdasarkan prinsip mandat hanya apa yang benar-benar diperlukan sementara menghindari duplikasi.
Hanya 2c saya.
sumber
Karena harus melintasi setiap node (
n
total) dan memperbarui datanya (langkah pembaruan memangO(1)
). Ini membuat seluruh operasiO(n*1) = O(n)
.sumber
Ini juga menukar pointer sebelumnya dan berikutnya untuk setiap node. Karena itulah dibutuhkan Linear. Meskipun dapat dilakukan dalam O (1) jika fungsi menggunakan LL ini juga mengambil informasi tentang LL sebagai input seperti apakah itu mengakses secara normal atau terbalik.
sumber
Hanya penjelasan algoritma. Bayangkan Anda memiliki array dengan elemen, maka Anda perlu membalikkannya. Ide dasarnya adalah untuk beralih pada setiap elemen mengubah elemen di posisi pertama ke posisi terakhir, elemen di posisi kedua ke posisi kedua dari belakang, dan sebagainya. Ketika Anda mencapai di tengah array Anda akan memiliki semua elemen berubah, sehingga dalam (n / 2) iterasi, yang dianggap O (n).
sumber
Ini O (n) hanya karena perlu menyalin daftar dalam urutan terbalik. Setiap operasi item individu adalah O (1) tetapi ada n dari mereka di seluruh daftar.
Tentu saja ada beberapa operasi waktu konstan yang terlibat dalam mengatur ruang untuk daftar baru, dan mengubah pointer sesudahnya, dll. Notasi O tidak mempertimbangkan konstanta individual begitu Anda memasukkan faktor n orde pertama.
sumber