C ++ vektörlerinde push_back neden sabit amorti oluyor?


23

C ++ öğreniyorum ve vektörler için push_back işlevi için çalışma süresinin "amortize" sabiti olduğunu fark ettim. Belgelerde ayrıca, "Yeniden tahsis olursa, yeniden tahsisatın kendisi tüm boyutta doğrusaldır."

Bu, push_back işlevinin olduğu, n'nin vektörün uzunluğu olduğu anlamına gelmemeli mi? Sonuçta, en kötü vaka analizi ile ilgileniyoruz, değil mi?O(n)n

Sanırım, en önemlisi, "itfa edilen" sıfatının çalışma zamanını nasıl değiştirdiğini anlamıyorum.


Bir RAM makinesinde bayt bellek ayırmak bir O ( n ) işlemi değildir - hemen hemen sabit bir zaman olarak kabul edilir. nO(n)
usul

Yanıtlar:


24

Buradaki önemli kelime "itfa edilir". İtfa edilmiş analiz, bir işlem sırasını inceleyen bir analiz tekniğidir . Tüm dizi T ( n ) zamanında çalışıyorsa, dizideki her işlem T ( n ) / n cinsinden çalışır . Buradaki fikir, sıradaki birkaç işlemin pahalı olmasına rağmen, programı tartmak için yeterince sık gerçekleşemeyecekleridir. Bunun, bazı girdi dağılımı ya da randomize analizler üzerinden ortalama vaka analizinden farklı olduğunu not etmek önemlidir. Amortize edilmiş analiz en kötü durumu tespit ettinT(n)T(n)/nGirdilerden bağımsız olarak bir algoritmanın performansı için sınırlıdır. Program boyunca kalıcı bir duruma sahip olan veri yapılarını analiz etmek için en yaygın şekilde kullanılır.

kO(n)nO(n)O(1)

m

nmlogm(n)imini=1logm(n)minmm1nmm1m1m1.5


12

@Marc (düşündüğüm gibi) mükemmel bir analiz vermesine rağmen, bazı insanlar işleri biraz farklı bir açıdan düşünmeyi tercih edebilir.

Biri, yeniden tahsis etmenin biraz farklı bir yolunu düşünmektir. Tüm öğeleri hemen eski depolama biriminden yeni depolama birimine kopyalamak yerine, bir seferde yalnızca bir öğe kopyalamayı düşünün - yani, bir push_back işlemi yaptığınızda, yeni öğeyi yeni alana ekler ve tam olarak bir tane kopyalar. Eski uzaydan yeni uzaya eleman. 2'lik bir büyüme faktörü varsayarsak, yeni alan dolduğunda, tüm öğeleri eski alandan yeni alana kopyalamayı bitirdiğimizi ve her push_back işleminin tam olarak sabit bir zamanda gerçekleştiğini açık bir şekilde görüyoruz. Bu noktada, eski alanı atıyoruz, iki kat daha büyük kazanç sağlayan yeni bir bellek bloğu ayırıyor ve işlemi tekrarlıyorduk.

Oldukça açık bir şekilde, bunu süresiz olarak devam ettirebiliriz (veya zaten mevcut bellek olduğu sürece) ve her push_back yeni bir öğe eklemek ve eski bir öğeyi kopyalamaktan ibarettir.

Tipik bir uygulama hala tam olarak aynı sayıda kopyaya sahiptir - ancak kopyaları bir kerede bir tane yapmak yerine, mevcut öğelerin tümünü bir kerede kopyalar. Bir yandan haklısınız: Bu, push_back'in bireysel çağrılarına bakarsanız, bazılarının diğerlerinden önemli ölçüde daha yavaş olacağı anlamına gelir. Bununla birlikte, uzun vadeli bir ortalamaya bakarsak, push_back'in başlatılması başına yapılan kopyalamanın miktarı, vektörün boyutundan bağımsız olarak sabit kalır.

İşlemsel karmaşıklıkla ilgisi olmasa da, push_back başına bir öğe kopyalamak yerine neden bir şeyleri yapmanın avantajlı olduğunu belirtmeye değer olduğunu düşünüyorum, bu nedenle push_back başına süre sabit kalıyor. Dikkate alınması gereken en az üç neden var.

Birincisi sadece hafıza kullanılabilirliğidir. Eski bellek, diğer kullanımlar için sadece kopyalama bittikten sonra serbest bırakılabilir. Bir seferde yalnızca bir öğeyi kopyalarsanız, eski bellek bloğu çok daha uzun süre kalır. Aslında, esas olarak her zaman tahsis edilen bir eski bloğa ve bir yeni bloğa sahip olursunuz. İkiden küçük (genellikle istediğiniz) bir büyüme faktörüne karar verirseniz, her zaman daha fazla hafızaya ihtiyacınız olacaktır.

İkincisi, bir kerede yalnızca bir eski öğeyi kopyalarsanız, diziye dizine ekleme biraz daha zor olurdu - her dizine ekleme işleminin, verilen dizindeki öğenin o andaki eski bellek bloğunda mı, yoksa eski bellek bloğunda mı olduğunu bulması gerekir. yenisi. Bu, herhangi bir yöntemle çok karmaşık değildir, ancak bir diziye endeksleme gibi basit bir işlem için neredeyse herhangi bir yavaşlama önemli olabilir.

Üçüncüsü, hepsini bir kerede kopyalayarak, önbelleğe almanın çok daha iyi avantajlarından yararlanın. Tümünü bir kerede kopyalamak, çoğu durumda hem kaynağın hem de hedefin önbellekte kalmasını bekleyebilirsiniz; bu nedenle, bir önbellek kaçırmanın maliyeti, önbellek hattına uyacak öğelerin sayısı üzerinden itfa edilir. Bir kerede bir öğe kopyalarsanız, kopyaladığınız her öğe için kolayca bir önbelleğe sahip olabilirsiniz. Bu sadece karmaşıklığı değil sabit faktörü değiştirir, ancak yine de oldukça önemli olabilir - tipik bir makine için kolayca 10 ila 20 arasında bir faktör bekleyebilirsiniz.

Muhtemelen bir an için diğer yönü de dikkate almaya değer: gerçek zamanlı gereklilikleri olan bir sistem tasarlıyorsanız, aynı anda sadece bir elemanı aynı anda kopyalamak mantıklı gelebilir. Her ne kadar genel hız düşük olsa da (veya olmasa da), tek bir push_back işlemi için harcanan zamanla ilgili zorlu bir üst sınırınız olur - gerçek zamanlı bir tahsisatçı olduğunu düşünürsünüz (tabii ki, çoğu zaman gerçek zamanlı) Sistemler basitçe en azından gerçek zamanlı gereklilikleri olan bölümlerde belleğin dinamik olarak tahsis edilmesini yasaklar).


2
+1 Bu harika bir Feynman tarzı açıklama.
Monica
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.