Saya sedang melihat wadah STL dan mencoba untuk mencari tahu apa itu sebenarnya (yaitu struktur data yang digunakan), dan deque menghentikan saya: Saya pikir pada awalnya bahwa itu adalah daftar ditautkan ganda, yang akan memungkinkan penyisipan dan penghapusan dari kedua ujungnya di waktu yang konstan, tetapi saya merasa terganggu oleh janji yang dibuat oleh operator [] untuk dilakukan dalam waktu yang konstan. Dalam daftar tertaut, akses sewenang-wenang seharusnya O (n), kan?
Dan jika itu array dinamis, bagaimana bisa menambahkan elemen dalam waktu yang konstan? Harus disebutkan bahwa realokasi dapat terjadi, dan bahwa O (1) adalah biaya diamortisasi, seperti untuk vektor .
Jadi saya bertanya-tanya apa struktur ini yang memungkinkan akses sewenang-wenang dalam waktu yang konstan, dan pada saat yang sama tidak perlu dipindahkan ke tempat baru yang lebih besar.
deque
singkatan antrian ujung ganda , meskipun jelas persyaratan ketat dari O (1) akses ke elemen tengah khusus untuk C ++Jawaban:
Deque didefinisikan secara rekursif: secara internal ia memelihara antrian ujung ganda dari ukuran tetap. Setiap chunk adalah vektor, dan antrian ("peta" dalam grafik di bawah) dari chunk itu sendiri juga merupakan vektor.
Ada analisis yang hebat tentang karakteristik kinerja dan bagaimana membandingkannya dengan
vector
lebih di CodeProject .Implementasi perpustakaan standar GCC secara internal menggunakan a
T**
untuk mewakili peta. Setiap blok data adalahT*
yang dialokasikan dengan ukuran tetap__deque_buf_size
(yang tergantung padasizeof(T)
).sumber
Bayangkan itu sebagai vektor vektor. Hanya saja itu bukan standar
std::vector
.Vektor luar berisi pointer ke vektor bagian dalam. Ketika kapasitasnya diubah melalui realokasi, alih-alih mengalokasikan semua ruang kosong ke akhir seperti
std::vector
halnya, itu membagi ruang kosong ke bagian yang sama di awal dan akhir vektor. Ini memungkinkanpush_front
danpush_back
pada vektor ini keduanya terjadi dalam waktu O (1) diamortisasi.Perilaku vektor bagian dalam perlu diubah tergantung pada apakah itu di bagian depan atau belakang
deque
. Di belakangnya ia dapat berperilaku sebagai standar distd::vector
mana ia tumbuh pada akhirnya, danpush_back
terjadi dalam waktu O (1). Di depan itu perlu melakukan yang sebaliknya, tumbuh di awal dengan masing-masingpush_front
. Dalam praktiknya ini mudah dicapai dengan menambahkan pointer ke elemen depan dan arah pertumbuhan bersama dengan ukuran. Dengan modifikasi sederhanapush_front
ini juga bisa O (1) kali.Akses ke elemen apa pun membutuhkan penyeimbangan dan pembagian ke indeks vektor luar yang tepat yang terjadi pada O (1), dan pengindeksan ke dalam vektor bagian dalam yang juga O (1). Ini mengasumsikan bahwa vektor bagian dalam semua ukuran tetap, kecuali yang di awal atau akhir
deque
.sumber
Wadah yang bisa tumbuh di kedua arah.
Deque biasanya diimplementasikan sebagai a
vector
ofvectors
(daftar vektor tidak dapat memberikan akses acak waktu konstan). Sementara ukuran vektor sekunder bergantung pada implementasi, algoritma yang umum adalah menggunakan ukuran konstan dalam byte.sumber
array
apa pun atauvector
apa pun yang bisa menjanjikanO(1)
push_front yang diamortisasi . Bagian dalam dari dua struktur setidaknya, harus dapat memilikiO(1)
push_front, yang tidak dapat dijaminarray
maupun yang tidakvector
.vector
tidak melakukan itu, tetapi itu modifikasi yang cukup sederhana untuk membuatnya begitu.(Ini adalah jawaban yang saya berikan di utas lain . Pada dasarnya saya berpendapat bahwa implementasi yang bahkan cukup naif, menggunakan satu
vector
, sesuai dengan persyaratan "push_ non-diamortisasi konstan {depan, belakang}". Anda mungkin akan terkejut , dan saya pikir ini tidak mungkin, tetapi saya telah menemukan kutipan lain yang relevan dalam standar yang mendefinisikan konteks dengan cara yang mengejutkan. Mohon bersabar, jika saya telah membuat kesalahan dalam jawaban ini, akan sangat membantu untuk mengidentifikasi hal-hal yang mana Saya telah mengatakan dengan benar dan di mana logika saya rusak.)Dalam jawaban ini, saya tidak mencoba mengidentifikasi implementasi yang baik , saya hanya mencoba untuk membantu kami menafsirkan persyaratan kompleksitas dalam standar C ++. Saya mengutip dari N3242 , yang menurut Wikipedia , dokumen standardisasi C ++ 11 terbaru yang tersedia secara gratis. (Tampaknya diatur secara berbeda dari standar akhir, dan karenanya saya tidak akan mengutip nomor halaman yang tepat. Tentu saja, aturan ini mungkin telah berubah dalam standar akhir, tetapi saya rasa itu tidak terjadi.)
A
deque<T>
dapat diimplementasikan dengan benar dengan menggunakan avector<T*>
. Semua elemen disalin ke tumpukan dan pointer disimpan dalam vektor. (Lebih lanjut tentang vektor nanti).Kenapa
T*
bukannyaT
? Karena standar mengharuskan itu(Penekanan saya). The
T*
membantu untuk memenuhi itu. Ini juga membantu kita untuk memuaskan ini:Sekarang untuk bit (kontroversial). Mengapa menggunakan a
vector
untuk menyimpanT*
? Ini memberi kami akses acak, yang merupakan awal yang baik. Mari kita lupakan kerumitan vektor untuk sesaat dan membangunnya dengan hati-hati:Standar berbicara tentang "jumlah operasi pada objek yang terkandung.". Untuk
deque::push_front
ini jelas 1 karena tepat satuT
objek dibangun dan nol dariT
objek yang ada dibaca atau dipindai dengan cara apa pun. Angka ini, 1, jelas merupakan konstanta dan tidak tergantung pada jumlah objek yang ada dalam deque. Ini memungkinkan kita untuk mengatakan bahwa:'Untuk kami
deque::push_front
, jumlah operasi pada objek yang terkandung (Ts) adalah tetap dan tidak tergantung pada jumlah objek yang sudah ada dalam deque.'Tentu saja, jumlah operasi pada
T*
tidak akan berperilaku baik. Ketikavector<T*>
tumbuh terlalu besar, itu akan dialokasikan kembali dan banyakT*
yang akan disalin. Jadi ya, jumlah operasi diT*
akan sangat bervariasi, tetapi jumlah operasi diT
tidak akan terpengaruh.Mengapa kita peduli tentang perbedaan antara penghitungan operasi
T
dan penghitungan operasiT*
? Itu karena standar mengatakan:Untuk
deque
, objek yang terkandung adalahT
, bukanT*
, artinya kita dapat mengabaikan operasi apa pun yang menyalin (atau meng-reallocs) aT*
.Saya belum banyak bicara tentang bagaimana vektor akan berperilaku deque. Mungkin kita akan menafsirkannya sebagai penyangga bundar (dengan vektor selalu mengambil maksimumnya
capacity()
, dan kemudian realokasi semuanya menjadi penyangga yang lebih besar ketika vektor penuh. Detailnya tidak masalah.Dalam beberapa paragraf terakhir, kami telah menganalisis
deque::push_front
dan hubungan antara jumlah objek dalam deque sudah dan jumlah operasi yang dilakukan oleh push_front pada -objects yang terkandungT
. Dan kami menemukan mereka tidak saling tergantung satu sama lain. Karena standar mengamanatkan kompleksitas dalam hal operasi-onT
, maka kita dapat mengatakan ini memiliki kompleksitas konstan.Ya, Operasional-On-T * -Kompleksitas diamortisasi (karena
vector
), tetapi kami hanya tertarik pada Kompleksitas Operasional-On-T dan ini konstan (tidak-diamortisasi).Kompleksitas vektor :: push_back atau vektor :: push_front tidak relevan dalam implementasi ini; Pertimbangan tersebut melibatkan operasi
T*
dan karenanya tidak relevan. Jika standar mengacu pada gagasan teoritis 'konvensional' tentang kompleksitas, maka mereka tidak akan secara eksplisit membatasi diri pada "jumlah operasi pada objek yang terkandung". Apakah saya terlalu menafsirkan kalimat itu?sumber
list
terlepas dari ukuran daftar sekarang; jika daftar terlalu besar, alokasi akan lambat atau akan gagal. Oleh karena itu, sejauh yang saya bisa lihat, panitia membuat keputusan untuk hanya menentukan operasi yang dapat dihitung dan diukur secara objektif. (PS: Saya punya teori lain tentang ini untuk jawaban lain.)O(n)
berarti jumlah operasi sebanding dengan jumlah elemen asimtotik. IE, jumlah meta-operasi. Kalau tidak, tidak masuk akal untuk membatasi pencarianO(1)
. Ergo, daftar tertaut tidak memenuhi syarat.list
dapat diimplementasikan sebagaivector
pointer juga (penyisipan ke tengah akan menghasilkan doa penyalin konstruktor tunggal , terlepas dari ukuran daftar, danO(N)
pengocokan pointer dapat diabaikan karena mereka bukan operasi-on-T).deque
cara ini dan (2) "menipu" dengan cara ini (bahkan jika diizinkan oleh standar) ketika menghitung kompleksitas algoritme tidak membantu dalam menulis program yang efisien .Dari gambaran umum, Anda dapat berpikir
deque
sebagai adouble-ended queue
Data dalam
deque
disimpan oleh potongan-potongan vektor ukuran tetap, yangditunjukkan oleh
map
(yang juga merupakan potongan vektor, tetapi ukurannya dapat berubah)Kode bagian utama
deque iterator
adalah sebagai berikut:Kode bagian utama
deque
adalah sebagai berikut:Di bawah ini saya akan memberi Anda kode inti
deque
, terutama sekitar tiga bagian:iterator
Cara membangun a
deque
1. iterator (
__deque_iterator
)Masalah utama dari iterator adalah, ketika ++, - iterator, ia dapat melompat ke chunk lain (jika pointer ke edge of chunk). Sebagai contoh, ada tiga data yang potongan:
chunk 1
,chunk 2
,chunk 3
.The
pointer1
pointer ke mulai darichunk 2
, ketika operator--pointer
akan pointer ke akhirchunk 1
, sehingga kepointer2
.Di bawah ini saya akan memberikan fungsi utama
__deque_iterator
:Pertama, lewati ke bagian mana pun:
Perhatikan bahwa,
chunk_size()
fungsi yang menghitung ukuran chunk, Anda dapat menganggapnya mengembalikan 8 untuk disederhanakan di sini.operator*
dapatkan data di chunkoperator++, --
// bentuk awalan peningkatan
iterator lewati n langkah / akses acak2. Cara membangun a
deque
fungsi umum dari
deque
Mari kita asumsikan
i_deque
memiliki 20 elemen int0~19
yang ukuran chunk-nya adalah 8, dan sekarang push_back 3 elemen (0, 1, 2) kei_deque
:Ini struktur internal seperti di bawah ini:
Kemudian push_back lagi, itu akan memanggil mengalokasikan potongan baru:
Jika kita
push_front
, itu akan mengalokasikan potongan baru sebelum prevstart
Perhatikan ketika
push_back
elemen menjadi deque, jika semua peta dan potongan diisi, itu akan menyebabkan mengalokasikan peta baru, dan menyesuaikan potongan. Tapi kode di atas mungkin cukup bagi Anda untuk mengertideque
.sumber
Saya sedang membaca "Struktur data dan algoritma dalam C ++" oleh Adam Drozdek, dan menemukan ini berguna. HTH.
Anda dapat melihat di tengah adalah array pointer ke data (potongan di sebelah kanan), dan Anda juga dapat melihat bahwa array di tengah berubah secara dinamis.
Sebuah gambar bernilai ribuan kata.
sumber
deque
bagian itu dan itu cukup bagus.Sementara standar tidak mengamanatkan implementasi tertentu (hanya akses acak waktu konstan), sebuah deque biasanya diimplementasikan sebagai kumpulan "halaman" memori yang berdekatan. Halaman baru dialokasikan sesuai kebutuhan, tetapi Anda masih memiliki akses acak. Tidak seperti
std::vector
, Anda tidak dijanjikan bahwa data disimpan secara bersebelahan, tetapi seperti vektor, penyisipan di tengah membutuhkan banyak pemindahan.sumber
insert
membutuhkan banyak relokasi, bagaimana eksperimen 4 di sini menunjukkan perbedaan yang mengejutkan antaravector::insert()
dandeque::insert()
?