STL'deki gerçek deque nedir?


194

STL konteynırlarına bakıyordum ve gerçekte ne olduklarını anlamaya çalışıyordum (yani kullanılan veri yapısı) ve deque beni durdurdu: İlk başta, her iki uçtan da ekleme ve silinmeye izin veren çift bağlantılı bir liste olduğunu düşündüm. ancak zaman içinde yapılması gereken operatör [] tarafından verilen söz beni rahatsız ediyor . Bağlantılı bir listede, rasgele erişim O (n) olmalıdır, değil mi?

Dinamik bir dizi ise, öğeleri sabit zamanda nasıl ekleyebilir ? Yeniden tahsisin olabileceği ve O (1) 'in bir vektör için olduğu gibi itfa edilmiş bir maliyet olduğu belirtilmelidir .

Bu yüzden, sürekli zamanda keyfi erişime izin veren bu yapının ne olduğunu merak ediyorum ve aynı zamanda asla daha büyük bir yere taşınması gerekmiyor.



1
@Graham “dequeue”, “deque” için başka bir yaygın isimdir. Düzenlemeyi hala onayladım çünkü “deque” genellikle standart addır.
Konrad Rudolph

@Konrad Teşekkürler. Soru özellikle daha kısa yazım kullanan C ++ STL deque hakkındaydı.
Graham Borland

2
dequeçift ​​uçlu kuyruk anlamına gelir , ancak O (1) orta elemanlara erişimin sıkı gereksinimi C ++ için
özeldir

Yanıtlar:


186

Bir deque biraz özyinelemeli olarak tanımlanır: dahili olarak sabit boyutlu parçaların çift ​​uçlu bir kuyruğunu tutar . Her yığın bir vektördür ve yığınların kendisinin kuyruğu (aşağıdaki grafikte “harita”) da bir vektördür.

bir deque bellek düzeninin şeması

Orada performans özelliklerinin büyük bir analizi ve o kıyasla nasıl vectoren over CodeProject .

GCC standart kütüphane uygulaması T**haritayı temsil etmek için dahili olarak a kullanır . Her veri bloğu bir T*sabit boyuta __deque_buf_size(hangisine bağlı olarak sizeof(T)) atanır .


28
Bu öğrendiğim gibi bir deque'nin tanımıdır, ancak bu şekilde sürekli zaman erişimini garanti edemez, bu yüzden eksik bir şey olmalı.
stefaanv

14
@stefaanv, @Konrad: C ++ uygulamaları Sabit boyutlu dizilere bir dizi işaretçi kullandığımı gördüm. Bu, push_front ve push_back'in gerçekten sabit süreler olmadığı anlamına gelir, ancak akıllı büyüyen faktörlerle, yine de sabit süreler amortismana tabi tutulur, bu nedenle O (1) çok hatalı değildir ve pratikte vektörden daha hızlıdır, çünkü takas yaptığınız için tüm nesnelerden ziyade tek işaretçiler (ve nesnelerden daha az işaretçiler).
Matthieu M.Haziran

5
Sabit zamanlı erişim hala mümkündür. Önde yeni bir blok tahsis etmeniz gerekiyorsa, ana vektörde yeni bir işaretçi geri itin ve tüm işaretçileri kaydırın.
Xeo

4
Harita (kuyruğun kendisi) çift uçlu bir liste ise, O (1) rastgele erişime nasıl izin vereceğini göremiyorum. Dairesel arabellek yeniden boyutlandırmanın daha verimli olmasını sağlayan dairesel bir arabellek olarak uygulanabilir: Sıradaki tüm öğeler yerine yalnızca işaretçileri kopyalayın. Yine de bu küçük bir fayda gibi görünüyor.
Wernight

15
@JeremyWest Neden olmasın? Endeksli erişim, i / B-th bloğundaki (B = blok boyutu) i% B-th öğesine gider, bu açıkça O (1) 'dir. İtfa edilmiş O (1) 'e yeni bir blok ekleyebilirsiniz, dolayısıyla eleman eklemek sonunda O (1) itfa edilir. Yeni bir blok eklemek gerekmedikçe, başlangıçta yeni bir eleman eklemek O (1) 'dir. Başlangıçta yeni bir blok eklemek O (1) değil, doğru, O (N) ama gerçekte çok küçük bir sabit faktöre sahip, çünkü N elemanları yerine N / B işaretleyicilerini hareket ettirmeniz gerekiyor.
Konrad Rudolph

22

Bir vektör vektörü olarak düşünün. Sadece standart değiller std::vector.

Dış vektör, iç vektörlere işaretçiler içerir. Kapasitesi, tüm boş alanı sonuna kadar tahsis etmek yerine yeniden tahsis yoluyla değiştirildiğinde, boş alanı std::vectorvektörün başında ve sonunda eşit parçalara böler. Bu sayede push_frontve push_backhem de bu vektör üzerinde itfa edilmiş O (1) zaman içinde meydana gelir.

İç vektör davranışı, öğesinin önünde veya arkasında olmasına bağlı olarak değişmelidir deque. Arkada, std::vectorsonunda büyüdüğü ve push_backO (1) zamanında ortaya çıktığı bir standart olarak davranabilir . Önde, her biri ile başlangıçta büyüyen tersini yapması gerekiyor push_front. Pratikte bu, ön elemana bir işaretçi ve boyutla birlikte büyüme yönünü ekleyerek kolayca elde edilebilir. Bu basit modifikasyon push_frontile O (1) zamanı da olabilir.

Herhangi bir elemana erişim, O (1) 'de meydana gelen uygun dış vektör indeksinin dengelenmesini ve bölünmesini ve aynı zamanda O (1) olan iç vektöre indekslenmesini gerektirir. Bu, iç vektörlerin başında veya sonunda olanlar dışında tüm sabit boyutta olduğunu varsayar deque.


1
İç vektörleri sabit kapasiteye
Caleth

18

deque = çift uçlu kuyruk

Her iki yönde de büyüyebilen bir kap.

Deque olduğu tipik bir şekilde uygulanan vectorbir vectors(sabit zaman rasgele erişim veremez vektörlerinin bir listesi). İkincil vektörlerin boyutu uygulamaya bağlı olsa da, ortak bir algoritma bayt cinsinden sabit bir boyut kullanmaktır.


6
Onun değil oldukça içten vektörler. İç yapılar başlangıçta ve sonunda kullanılmamış kapasiteye sahip olabilir
Mooing Duck

@MooingDuck: Gerçekten tanımlanmış bir uygulamadır. Bir dizi dizi veya vektör vektörü veya standardın zorunlu kıldığı davranış ve karmaşıklığı sağlayabilecek herhangi bir şey olabilir.
Alok

1
@Als: Ben amortisman push_front vaat edebilecek bir arrayşey ya vectorda bir şey O(1)düşünmüyorum. En azından iki yapının içi, ne bir ne de bir garanti sağlayamayacak bir itme noktasına sahip olmalıdır . O(1)arrayvector
Mooing Duck

4
@Mooing: İlk yığın aşağıdan yukarıya değil yukarıdan aşağıya doğru büyürse bu gereksinim kolayca karşılanır. Açıkçası bir standart vectorbunu yapmaz, ancak bunu yapmak için yeterince basit bir modifikasyon.
Mark Ransom

3
@ Mooing Ördek, İtme O (1) 'de hem push_front hem de push_back kolayca tek bir vektör yapısı ile yapılabilir. Dairesel bir tamponun biraz daha defter tutulması, başka bir şey değil. 0'dan 99'a kadar konumlarda 100 elemanlı 1000 kapasiteye sahip normal bir vektörünüz olduğunu varsayın. Şimdi bir push_Front gerçekleştiğinde, iki uç buluşana kadar sonuna kadar yani 999, ardından 998 vb. Daha sonra, sıradan bir vektörde yaptığınız gibi, amortizet sabit zamanlarını garanti etmek için üstel büyüme ile yeniden tahsis edersiniz. Yani etkili bir şekilde ilk el için sadece bir ek işaretçiye ihtiyacınız var.
plamenko

14

(Bu başka bir iş parçacığında verdiğim bir yanıttır . Aslında, tek saf kullanarak oldukça naif uygulamaların bile vector"sürekli itfa edilmemiş push_ {front, back}" şartlarına uyduğunu iddia ediyorum. ve bunun imkansız olduğunu düşünüyorum, ancak bağlamda şaşırtıcı bir şekilde tanımlayan diğer ilgili alıntıları buldum. Doğru söyledim ve mantığım nerede bozuldu.)

Bu cevapta, iyi bir uygulamayı tanımlamaya çalışmıyorum , sadece C ++ standardındaki karmaşıklık gereksinimlerini yorumlamamıza yardımcı olmaya çalışıyorum. Wikipedia'ya göre en son serbestçe kullanılabilen C ++ 11 standardizasyon belgesi olan N3242'den alıntı yapıyorum . (Nihai standarttan farklı bir şekilde organize edilmiş gibi görünüyor ve bu nedenle tam sayfa numaralarını alıntılamayacağım. Elbette, bu kurallar nihai standartta değişmiş olabilir, ancak bunun olduğunu sanmıyorum.)

A deque<T>, a kullanılarak doğru şekilde uygulanabilir vector<T*>. Tüm elemanlar öbek üzerine kopyalanır ve işaretçiler bir vektörde saklanır. (Daha sonra vektör hakkında daha fazla bilgi).

Neden T*yerine T? Çünkü standart bunu gerektirir

"Deque'nin her iki ucundaki bir ekleme, deque için tüm yineleyicileri geçersiz kılar, ancak deque öğelerine yapılan referansların geçerliliği üzerinde hiçbir etkisi yoktur. "

(benim vurgu). Bunu T*tatmin etmek için yardımcı olur. Ayrıca bunu tatmin etmemize yardımcı olur:

"Her zaman bir deque'nin başına veya sonuna tek bir eleman eklemek ..... bir T yapıcısına tek bir çağrıya neden olur ."

Şimdi (tartışmalı) bit için. Neden kullanmak vectorsaklamak için T*? Bize rastgele erişim sağlar, bu da iyi bir başlangıçtır. Bir an için vektörün karmaşıklığını unutalım ve buna dikkatlice bakalım:

Standart, "içerilen nesneler üzerindeki işlemlerin sayısı" ndan bahsediyor. İçin deque::push_frontbu açıkça 1 tam olarak bir nedeniyle Tnesne oluşturulur ve mevcut sıfır Tnesne oku veya herhangi bir şekilde taranır. Bu sayı, 1, açıkça bir sabittir ve şu anda deque'deki nesnelerin sayısından bağımsızdır. Bu, şunu söylememizi sağlar:

'Bizim deque::push_frontiçin, içerilen nesneler (Ts) üzerindeki işlem sayısı sabittir ve zaten deque'de olan nesnelerin sayısından bağımsızdır.'

Tabii ki, T*irade üzerindeki operasyonların sayısı çok iyi davranmayacak. Çok vector<T*>büyüdüğünde, yeniden dağıtılacak ve birçok T*s kopyalanacaktır. Yani evet, T*irade üzerindeki operasyon sayısı çılgınca değişecek, ancak ondaki operasyon sayısı Tetkilenmeyecek.

İşlemleri Tsayma ve sayma işlemleri arasındaki bu ayrımı neden önemsiyoruz T*? Çünkü standart şöyle diyor:

Bu fıkradaki tüm karmaşıklık gereklilikleri yalnızca içerilen nesneler üzerindeki işlem sayısı açısından belirtilmiştir.

Çünkü dequeiçerilen nesneler T, değil T*, yani kopyalayan (veya reallocs) herhangi bir işlemi göz ardı edebileceğimiz anlamına gelir T*.

Bir vektörün bir deque'de nasıl davranacağı hakkında pek bir şey söylemedim. Belki de bunu dairesel bir tampon olarak yorumlayabiliriz (vektör her zaman maksimumunu alır capacity()ve daha sonra vektör dolduğunda her şeyi daha büyük bir arabelleğe yeniden tahsis ederiz .

Son birkaç paragrafta, deque::push_frontzaten deque içindeki nesnelerin sayısı ile push -front tarafından içerilen T-objects üzerinde gerçekleştirilen işlem sayısı arasındaki ilişkiyi analiz ettik. Ve birbirlerinden bağımsız olduklarını gördük. Standart karmaşıklığın operasyonlar açısından olduğunu zorunlu kıldığından, Tbunun sürekli karmaşıklığa sahip olduğunu söyleyebiliriz.

Evet, İşlemler-T * -Kompleksite amortismana tabi tutulur (nedeniyle vector), ancak biz sadece İşlem-On-Karmaşıklık ile ilgileniriz ve bu sabittir (amortisyonsuz).

Vector :: push_back veya vector :: push_front karmaşıklığı bu uygulamada önemsizdir; bu düşünceler operasyonları içerir T*ve bu nedenle önemsizdir. Standart 'geleneksel' teorik karmaşıklık kavramına atıfta bulunsaydı, kendilerini açıkça "içerilen nesneler üzerindeki işlemlerin sayısı" ile kısıtlamazlardı. Bu cümleyi çok mu yorumluyorum?


8
Bana hile gibi geliyor! Bir işlemin karmaşıklığını belirlediğinizde, bunu yalnızca verilerin bir kısmında yapmazsınız: ne üzerinde çalıştığından bağımsız olarak, aradığınız işlemin beklenen çalışma zamanı hakkında bir fikir edinmek istersiniz. T'deki işlemler hakkındaki mantığınızı takip edersem, her T * değerinin bir işlem gerçekleştirildiğinde her bir asal sayı olup olmadığını kontrol edebileceğiniz ve Ts'ye dokunmadığınız için standarda uyduğunuz anlamına gelir. Tekliflerinizin nereden geldiğini belirtebilir misiniz?
Zonko

2
Standart yazarların geleneksel karmaşıklık teorisini kullanamayacaklarını bildiklerini düşünüyorum, çünkü örneğin bellek ayırmanın karmaşıklığını bildiğimiz tam olarak belirlenmiş bir sistemimiz yok. listListenin geçerli boyutuna bakılmaksızın, belleğin yeni bir üye için ayrılabileceğini iddia etmek gerçekçi değildir ; liste çok büyükse, ayırma yavaş olur veya başarısız olur. Dolayısıyla, görebildiğim kadarıyla, komite yalnızca nesnel olarak sayılabilecek ve ölçülebilecek işlemleri belirlemeye karar vermiştir. (PS: Bu konuda başka bir cevap için başka bir teorim var .)
Aaron McDaid

Eminim ki O(n)operasyon sayısı eleman sayısıyla asimptotik olarak orantılıdır. IE, meta işlem sayısı. Aksi takdirde aramayı sınırlamak mantıklı olmaz O(1). Ergo, bağlantılı listeler uygun değil.
Mooing Duck

8
Bu çok ilginç bir yorumdur, ancak bu mantıkla a işaretçilerden biri listolarak da uygulanabilir vector(ortaya ekleme , liste boyutundan bağımsız olarak tek bir kopya oluşturucu çağrısına O(N)neden olur ve işaretçilerin karıştırılması yok sayılabilir, çünkü bunlar T üzerinde işlem değildir).
12'de Mankarse

1
Bu güzel bir dil-avukatlık (gerçekten doğru olup olmadığını veya standartta bu uygulamayı yasaklayan ince bir nokta olup olmadığını suss etmeye çalışmayacağım). Ancak pratikte yararlı bilgi değildir, çünkü (1) ortak uygulamalar dequebu şekilde uygulanmaz ve (2) algoritmik karmaşıklığı hesaplarken (standart tarafından izin verilse bile) bu şekilde "hile" etkili programlar yazarken yardımcı olmaz .
Kyle Strand

13

Değerlendirmesine göre, aklınıza gelebilecek dequea kadardouble-ended queue

deque genel bakış

İçindeki veriler deque, sabit boyutlu vektör parçaları tarafından saklanır.

ile işaretlenmiş map(aynı zamanda bir vektör yığınıdır , ancak boyutu değişebilir)

deque iç yapı

Ana parça kodu deque iteratoraşağıdaki gibidir:

/*
buff_size is the length of the chunk
*/
template <class T, size_t buff_size>
struct __deque_iterator{
    typedef __deque_iterator<T, buff_size>              iterator;
    typedef T**                                         map_pointer;

    // pointer to the chunk
    T* cur;       
    T* first;     // the begin of the chunk
    T* last;      // the end of the chunk

    //because the pointer may skip to other chunk
    //so this pointer to the map
    map_pointer node;    // pointer to the map
}

Ana parça kodu dequeaşağıdaki gibidir:

/*
buff_size is the length of the chunk
*/
template<typename T, size_t buff_size = 0>
class deque{
    public:
        typedef T              value_type;
        typedef T&            reference;
        typedef T*            pointer;
        typedef __deque_iterator<T, buff_size> iterator;

        typedef size_t        size_type;
        typedef ptrdiff_t     difference_type;

    protected:
        typedef pointer*      map_pointer;

        // allocate memory for the chunk 
        typedef allocator<value_type> dataAllocator;

        // allocate memory for map 
        typedef allocator<pointer>    mapAllocator;

    private:
        //data members

        iterator start;
        iterator finish;

        map_pointer map;
        size_type   map_size;
}

Aşağıda, temel dequeolarak yaklaşık üç bölümün temel kodunu vereceğim :

  1. yineleyici

  2. Nasıl inşa edilir deque

1. yineleyici ( __deque_iterator)

Yineleyicinin ana problemi, ++, - yineleyicisi olduğunda, diğer parçalara atlayabilir (parçaya kenara işaret ediyorsa). Örneğin, üç veri parçaları vardır: chunk 1, chunk 2, chunk 3.

Başlangıcının pointer1işaretçileri chunk 2, operatör ne zaman --pointersonuna chunk 1kadar işaret edecektir pointer2.

resim açıklamasını buraya girin

Aşağıda ana işlevini vereceğim __deque_iterator:

İlk olarak, herhangi bir parçaya atlayın:

void set_node(map_pointer new_node){
    node = new_node;
    first = *new_node;
    last = first + chunk_size();
}

O Not chunk_size()bunu basitleştirmek için burada 8 döndürür ait yığın boyutunu hesaplamak fonksiyonu, sen düşünebiliriz.

operator* verileri yığın içine almak

reference operator*()const{
    return *cur;
}

operator++, --

// artım önek biçimleri

self& operator++(){
    ++cur;
    if (cur == last){      //if it reach the end of the chunk
        set_node(node + 1);//skip to the next chunk
        cur = first;
    }
    return *this;
}

// postfix forms of increment
self operator++(int){
    self tmp = *this;
    ++*this;//invoke prefix ++
    return tmp;
}
self& operator--(){
    if(cur == first){      // if it pointer to the begin of the chunk
        set_node(node - 1);//skip to the prev chunk
        cur = last;
    }
    --cur;
    return *this;
}

self operator--(int){
    self tmp = *this;
    --*this;
    return tmp;
}
yineleyici atla n adım / rasgele erişim
self& operator+=(difference_type n){ // n can be postive or negative
    difference_type offset = n + (cur - first);
    if(offset >=0 && offset < difference_type(buffer_size())){
        // in the same chunk
        cur += n;
    }else{//not in the same chunk
        difference_type node_offset;
        if (offset > 0){
            node_offset = offset / difference_type(chunk_size());
        }else{
            node_offset = -((-offset - 1) / difference_type(chunk_size())) - 1 ;
        }
        // skip to the new chunk
        set_node(node + node_offset);
        // set new cur
        cur = first + (offset - node_offset * chunk_size());
    }

    return *this;
}

// skip n steps
self operator+(difference_type n)const{
    self tmp = *this;
    return tmp+= n; //reuse  operator +=
}

self& operator-=(difference_type n){
    return *this += -n; //reuse operator +=
}

self operator-(difference_type n)const{
    self tmp = *this;
    return tmp -= n; //reuse operator +=
}

// random access (iterator can skip n steps)
// invoke operator + ,operator *
reference operator[](difference_type n)const{
    return *(*this + n);
}

2. Nasıl inşa edilir deque

ortak işlevi deque

iterator begin(){return start;}
iterator end(){return finish;}

reference front(){
    //invoke __deque_iterator operator*
    // return start's member *cur
    return *start;
}

reference back(){
    // cna't use *finish
    iterator tmp = finish;
    --tmp; 
    return *tmp; //return finish's  *cur
}

reference operator[](size_type n){
    //random access, use __deque_iterator operator[]
    return start[n];
}


template<typename T, size_t buff_size>
deque<T, buff_size>::deque(size_t n, const value_type& value){
    fill_initialize(n, value);
}

template<typename T, size_t buff_size>
void deque<T, buff_size>::fill_initialize(size_t n, const value_type& value){
    // allocate memory for map and chunk
    // initialize pointer
    create_map_and_nodes(n);

    // initialize value for the chunks
    for (map_pointer cur = start.node; cur < finish.node; ++cur) {
        initialized_fill_n(*cur, chunk_size(), value);
    }

    // the end chunk may have space node, which don't need have initialize value
    initialized_fill_n(finish.first, finish.cur - finish.first, value);
}

template<typename T, size_t buff_size>
void deque<T, buff_size>::create_map_and_nodes(size_t num_elements){
    // the needed map node = (elements nums / chunk length) + 1
    size_type num_nodes = num_elements / chunk_size() + 1;

    // map node num。min num is  8 ,max num is "needed size + 2"
    map_size = std::max(8, num_nodes + 2);
    // allocate map array
    map = mapAllocator::allocate(map_size);

    // tmp_start,tmp_finish poniters to the center range of map
    map_pointer tmp_start  = map + (map_size - num_nodes) / 2;
    map_pointer tmp_finish = tmp_start + num_nodes - 1;

    // allocate memory for the chunk pointered by map node
    for (map_pointer cur = tmp_start; cur <= tmp_finish; ++cur) {
        *cur = dataAllocator::allocate(chunk_size());
    }

    // set start and end iterator
    start.set_node(tmp_start);
    start.cur = start.first;

    finish.set_node(tmp_finish);
    finish.cur = finish.first + num_elements % chunk_size();
}

Öbek boyutu 8 olan i_deque20 int öğesinin olduğunu varsayalım 0~19ve şimdi 3 öğeyi (0, 1, 2) itmek için i_deque:

i_deque.push_back(0);
i_deque.push_back(1);
i_deque.push_back(2);

İç yapısı aşağıdaki gibi:

resim açıklamasını buraya girin

Sonra tekrar push_back, yeni yığın ayırmayı çağırır:

push_back(3)

resim açıklamasını buraya girin

Eğer biz push_front, prev önce yeni yığın tahsis edecekstart

resim açıklamasını buraya girin

Not push_backharitalar ve parçaları tüm doldurulur eğer deque içine eleman, bu yeni harita tahsis neden ve anlamak için yukarıdaki kod yeterli olabilir chunks.But ayarlayacaktır deque.


"Push_back öğesini deque'ye dönüştürdüğünüzde, tüm haritalar ve parçalar doldurulduğunda yeni haritanın tahsis edilmesine ve parçaların ayarlanmasına neden olacağını unutmayın." Acaba C ++ standardının N4713'te "[26.3.8.4.3] Bir deque'nin başına veya sonuna tek bir eleman eklemek her zaman sabit zaman alır" diyor. Bir parça mandreli ayırmak sabit bir zamandan daha uzun sürer. Hayır?
HCSF

7

Adam Drozdek tarafından "C ++ 'da veri yapıları ve algoritmalar" okuyordum ve bunu faydalı buldum. HTH.

STL deque'nin çok ilginç bir yönü uygulamasıdır. STL deque, bağlantılı bir liste olarak değil, veri bloklarını veya dizilerini gösteren bir işaretçi dizisi olarak uygulanır. Blok sayısı, depolama gereksinimlerine bağlı olarak dinamik olarak değişir ve işaretçi dizisinin boyutu da buna göre değişir.

Ortada, verilerin işaretçi dizisi (sağdaki parçalar) olduğunu ve ortadaki dizinin dinamik olarak değiştiğini fark edebilirsiniz.

Bir görüntü bin kelimeye bedeldir.

resim açıklamasını buraya girin


1
Bir kitaba başvurduğunuz için teşekkür ederiz. Parçayı okudum dequeve oldukça iyi.
Rick

@ Bunu duyduğuma sevindim. Bir noktada deque'ye girmeyi hatırlıyorum çünkü O (1) 'de rastgele erişime ([] operatör) nasıl sahip olabileceğinizi anlayamadım. Ayrıca (push / pop) _ (arka / ön) amortismanlı O (1) karmaşıklığının ilginç bir 'aha anı' olduğunu kanıtlamak.
Keloo

6

Standart belirli bir uygulamayı zorunlu kılmasa da (yalnızca sabit zamanlı rasgele erişim), bir deque genellikle bitişik bellek "sayfaları" koleksiyonu olarak uygulanır. Yeni sayfalar gerektiğinde ayrılır, ancak yine de rastgele erişiminiz vardır. Bunun aksine std::vector, verilerin bitişik olarak saklanacağına dair söz vermezsiniz, ancak vektör gibi, ortadaki eklemeler çok fazla yer değiştirme gerektirir.


4
veya ortadaki silme işlemleri çok fazla yer değiştirmeyi gerektirir
Mark Hendrickson

Eğer inserttehcir sürü gerektirir deney 4 yok nasıl burada göstermek şaşırtıcı arasındaki farkı vector::insert()ve deque::insert()?
Bula

1
@Bula: Belki de ayrıntıların yanlış anlaşılması nedeniyle? Deque kesici ucun karmaşıklığı "eklenen elemanların sayısında lineer artı deque'nin başlangıcına ve sonuna kadar olan mesafelerin azalmasıdır." Bu maliyeti hissetmek için mevcut ortaya eklemeniz gerekir; karşılaştırmalı değerlendirmeniz bu mu?
Kerrek SB

@KerrekSB: karşılaştırmalı makaleye yukarıdaki Konrad cevabında atıfta bulunuldu. Aslında aşağıdaki makalenin yorum bölümünü fark etmedim. 'Ama deque'nin doğrusal yerleştirme zamanı var mı?' yazar, tüm testlerde 100 pozisyonuna yerleştirmeyi kullandığını belirtti ve bu da sonuçları biraz daha anlaşılır hale getirdi.
Bula
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.