vektör ve STL'deki liste


238

Etkili STL'de şunu fark ettim:

vektör, varsayılan olarak kullanılması gereken dizi türüdür.

Bu ne anlama geliyor? Verimliliği göz ardı etmek vectorher şeyi yapabilir gibi görünüyor .

Biri bana vectoruygulanabilir bir seçenek olmayan ancak listkullanılması gereken bir senaryo önerebilir mi?


6
Sorduğunuz şey olmasa da, vektörü varsayılan yapmanın aynı zamanda daha eski kod, C kütüphaneleri veya şablon olmayan kütüphanelerle kolayca etkileşime girebileceğiniz anlamına geldiğini belirtmek gerekir, çünkü vektör bir işaretçinin "geleneksel" dinamik dizisi etrafında ince bir sargıdır ve boyut.

18
Bjarne Strostrup aslında rastgele sayılar ürettiği bir test yaptı ve bunları sırasıyla bir listeye ve bir vektöre ekledi. Eklemeler, liste / vektör her zaman sipariş edilecek şekilde yapıldı. Bu genellikle "liste alanı" olmasına rağmen, vektör listeden daha büyük bir farkla daha iyi performans gösterdi. Bunun nedeni, bellek erişiminin yavaş olması ve önbelleğe almanın sıralı veriler için daha iyi çalışmasıdır. Her şey "GoingNative 2012" den açılış konuşmasında mevcut
kaçmak


1
@Evading'in bahsettiği Bjarne Stroustrup'un açılış konuşmasını görmek istiyorsanız, burada buldum: youtu.be/OB-bdWKwXsU?t=2672
brain56

Yanıtlar:


98

Bir dizinin sonuna kadar art arda çok fazla öğe eklemek istediğiniz durumlar.

Her farklı konteyner türü için karmaşıklık garantilerine göz atın:

Standart konteynerlerin karmaşıklık garantileri nelerdir?


2
Sonuna öğe eklemek de önemlidir çünkü bellek ayırma ve öğe kopyalama maliyetlerine yol açabilir. Ve ayrıca, bir vektörün başlangıcında elenets eklemek imkansızdır, listvardırpush_front
Notinlist

9
Hayır, bir vektörün sonuna eleman eklemek sabit bir süre için itfa edilir. Bellek ayırma işlemleri yalnızca ara sıra gerçekleşir ve vektörü önlemek için önceden konumlandırabilirsiniz. Tabii ki, sürekli sabit zaman eklemelerini garanti etmelisiniz ZORUNLU , sanırım bu hala bir sorun.
Brian

16
@Notinlist - Aşağıdaki "yanında imkansız" sizin için mi? v.insert (v.begin (), i)
Manuel

5
@Notinlist - Sana katılıyorum, sadece OP'nin (performans) ayağında kendini vurmak istemesi durumunda arayüzün orada olmadığını düşünmesini istemedim.
Manuel

16
Bjarne Strostrup aslında rastgele sayılar ürettiği bir test yaptı ve bunları sırasıyla bir listeye ve bir vektöre ekledi. Eklemeler, liste / vektör her zaman sipariş edilecek şekilde yapıldı. Bu genellikle "liste alanı" olmasına rağmen, vektör listeden daha büyük bir farkla daha iyi performans gösterdi. Bunun nedeni, bellek erişiminin yavaş olması ve önbelleğe almanın sıralı veriler için daha iyi çalışmasıdır. Bu "GoingNative 2012" dan yaptığı açılış içinde tüm bu imkanlardan
evading

409

vektör:

  • Bitişik bellek.
  • Gelecekteki öğeler için alan önceden ayırır, bu nedenle öğelerin kendileri için gerekli olandan daha fazla alan gerekir.
  • Her öğe yalnızca öğe türünün kendisi için alan gerektirir (fazladan işaretçi yok).
  • Bir öğe eklediğinizde, tüm vektör için belleği yeniden ayırabilir.
  • Sondaki yerleştirmeler sabit, amortisman süresidir, ancak başka bir yere yerleştirmeler maliyetli bir O (n) 'dir.
  • Vektörün sonundaki silmeler sabit süredir, ancak geri kalanı için O (n) 'dir.
  • Öğelerine rastgele erişebilirsiniz.
  • Vektöre veya vektörden eleman eklerseniz veya kaldırırsanız yineleyiciler geçersiz kılınır.
  • Öğelerin bir dizisine ihtiyacınız varsa, temel diziye kolayca ulaşabilirsiniz.

liste:

  • Bitişik olmayan bellek.
  • Önceden ayrılmış bellek yok. Listenin kendisi için bellek ek yükü sabittir.
  • Her öğe, öğeyi tutan düğüm için, listedeki sonraki ve önceki öğelere işaretçiler de dahil olmak üzere ek alan gerektirir.
  • Bir öğe eklediğiniz için hiçbir zaman tüm liste için belleği yeniden ayırmak zorunda kalmazsınız.
  • Ekleme ve silme işlemleri listenin neresinde olursa olsun ucuzdur.
  • Listeleri ekleme ile birleştirmek ucuzdur.
  • Öğelere rastgele erişemezsiniz, bu nedenle listedeki belirli bir öğeye erişmek pahalı olabilir.
  • Yineleyiciler, listeye öğe ekleseniz veya listeden öğeler kaldırsanız bile geçerli kalır.
  • Bir dizi öğeye ihtiyacınız varsa, temel bir dizi olmadığından yeni bir tane oluşturmanız ve hepsini buna eklemeniz gerekir.

Genel olarak, ne tür bir sıralı kap kullandığınızı umursamadığınızda vektör kullanın, ancak kapta uçtan başka herhangi bir yere ve uçtan çok sayıda ekleme veya silme yapıyorsanız, liste kullanmak. Veya rastgele erişime ihtiyacınız varsa, liste değil, vektör isteyeceksiniz. Bunun dışında, uygulamanıza dayanarak birine veya diğerine ihtiyaç duyacağınız doğal durumlar vardır, ancak genel olarak, bunlar iyi yönergelerdir.


2
Ayrıca, ücretsiz mağazadan tahsis ücretsiz değildir. :) Bir vektöre yeni öğeler eklemek O (log n) ücretsiz mağaza tahsisleri gerçekleştirir, ancak bunu reserve()O (1) değerine indirmek için arayabilirsiniz . Bir listeye yeni öğeler eklemek (yani bunları birleştirmemek) O (n) ücretsiz mağaza tahsisleri gerçekleştirir.
bk1e

7
Başka bir husus, listöğeleri sildiğinizde belleği serbest bırakması, ancak vectoraçmamasıdır. A vector, swap()numarayı düşürdüğünüzde , numarayı kullanmazsanız kapasitesini azaltmaz .
bk1e

@ bk1e: Gerçekten rezerv () ve takas () :) hile bilmek istiyorum
Dzung Nguyen

2
@nXqd: bir vektöre N öğesi eklemeniz gerekiyorsa, v.reserve (v.size () + N) öğesini çağırın, böylece yalnızca bir ücretsiz mağaza ayırma gerçekleştirir. Takas () hilesi burada: stackoverflow.com/questions/253157/how-to-downsize-stdvector
bk1e

1
@simplename Hayır. Söylediği doğru. vektör, o anda vektörde bulunan elemanlar için alanın ötesinde fazladan alan tahsis eder; daha sonra bu vektörü büyütmek için fazladan kapasite kullanılır, böylece büyütme O (1) olur.
Jonathan M Davis

35

Sık sık eleman eklemeniz gerekmiyorsa, bir vektör daha verimli olacaktır. Bir listeden çok daha iyi CPU önbellek yeri vardır. Başka bir deyişle, bir elemanını erişen yapar çok sonraki eleman önbellek mevcut olduğunu ve yavaş RAM okumak zorunda kalmadan alınabilir muhtemelen.


32

Buradaki cevapların çoğu önemli bir ayrıntıyı kaçırıyor: ne için?

Toplayıcıda ne tutmak istiyorsunuz?

Eğer bir ints koleksiyonuysa, std::listher senaryoda kaybedersiniz, yeniden tahsis ederseniz, yalnızca önden kaldırırsınız, vb. Listeler daha yavaş hareket eder, her ekleme, bölücü ile bir etkileşime mal olur. list<int>Vuruşların olduğu bir örnek hazırlamak son derece zor olurdu vector<int>. Ve o zaman bile deque<int>, sadece daha fazla bellek yüküne sahip olan listelerin kullanımını değil, daha iyi veya yakın olabilir.

Bununla birlikte, büyük, çirkin veri yığınları ile uğraşıyorsanız - ve bunlardan birkaçı - yerleştirirken fazla yer değiştirmek istemezsiniz ve yeniden tahsis nedeniyle kopyalama bir felaket olur - o zaman belki list<UglyBlob>daha vector<UglyBlob>.

Eğer geçerseniz Yine de, vector<UglyBlob*>hatta vector<shared_ptr<UglyBlob> >tekrar - Liste gerisinde olacaktır.

Dolayısıyla, erişim düzeni, hedef öğe sayısı vb. Hala karşılaştırmayı etkiler, ancak bence - öğe boyutu - kopyalama maliyeti vb.


1
Meyers tarafından "Etkili STL" okurken ben bir daha yansıması: kendine özgü bir özelliği list<T>için olasılık spliceiçinde O (1) . Sabit zamanlı birleştirme işlemine ihtiyacınız varsa, liste de seçim yapısı olabilir;)
Tomasz Gandor

1 - Hatta bir olmak zorunda değildir UglyBlobyeniden tahsisler yüzden, sadece birkaç dize üyeleri kolayca kopyalamak pahalı olabilir hatta bir nesne - edecektir mal oldu. Ayrıca: vectorBirkaç düzine bayt büyüklüğünde bir tutma nesnesinin üstel büyümesini ( reserveönceden yapamıyorsanız) neden olabileceği alanın üstündeki alanı ihmal etmeyin .
Martin Ba

Gelince vector<smart_ptr<Large>>vs. list<Large>- Sana elemanlarına rasgele erişim gerekiyorsa, derim vectormantıklı. Rastgele erişime ihtiyacınız yoksa, listdaha basit görünüyor ve eşit performans göstermelidir.
Martin Ba

19

Std :: list özel bir özelliği ekleme (bir listenin bir bölümünü veya bütün bir listesini farklı bir listeye bağlama veya taşıma).

Veya içeriğinizin kopyalanması çok pahalıysa. Böyle bir durumda, örneğin, koleksiyonu bir listeyle sıralamak daha ucuz olabilir.

Ayrıca, koleksiyon küçükse (ve içeriklerin kopyalanması özellikle pahalı değilse), herhangi bir yere ekleyip silseniz bile bir vektörün listeden daha iyi performans gösterebileceğini unutmayın. Bir liste her düğümü ayrı ayrı tahsis eder ve bu birkaç basit nesneyi hareket ettirmekten çok daha maliyetli olabilir.

Çok zor kurallar olduğunu sanmıyorum. Konteynır ile ne yapmak istediğinize ve konteynerin ne kadar büyük olmasını beklediğinize ve içerdiği tipe bağlıdır. Bir vektör genellikle bir listeyi koyar, çünkü içeriğini tek bir bitişik blok olarak tahsis eder (temel olarak dinamik olarak ayrılmış bir dizidir ve çoğu durumda bir dizi, bir sürü şeyi tutmanın en etkili yoludur).


1
+1. Ekleme göz ardı edilir, ancak ne yazık ki istendiği gibi sabit zamanlı değildir. : (((Liste :: boyut sabit zaman ise olamaz.)

Eminim listesi :: boyutu bu nedenle doğrusal (izin verilir).
UncleBens


@Potatoswatter: Standardın belirsiz ve sonuç olarak "uyumlu" uygulamalara güvenemeyeceğiniz, onu daha da sorun haline getirir. Taşınabilir ve güvenilir bir garanti almak için stdlib'den tam anlamıyla kaçınmalısınız.

@Roger: evet, maalesef. Benim şu anki proje kuvvetle ekleme işlemleri dayanıyor ve bu yapı N3000 sırayla, Daha da maalesef neredeyse düz C. olduğu splicedoğrusal karmaşıklık olarak belirtilir farklı listeler arasında ve sizeözellikle sabittir. Bu nedenle, yineleme yapan sizeveya başka bir şekilde yeni başlayanlara uyum sağlamak için , tüm algoritma sınıfları STL veya herhangi bir "uyumlu" kap dönemi için erişilemez.
Potatoswatter

13

Sınıfımdaki öğrenciler, vektörleri kullanmanın daha etkili olduğu zaman bana açıklayamıyor gibi görünüyor, ancak listeleri kullanmamı tavsiye ederken oldukça mutlu görünüyorlar.

Ben böyle anlıyorum

Listeler : Her öğe bir sonraki veya bir önceki öğeye bir adres içerir, bu nedenle bu özellik sayesinde öğeleri sıralanmamış olsalar da rastgele sıralayabilirsiniz, sıra değişmez: belleğiniz parçalanmışsa etkilidir. Ancak başka bir büyük avantajı daha var: Öğeleri kolayca ekleyebilir / kaldırabilirsiniz, çünkü yapmanız gereken tek şey bazı işaretçileri değiştirmek. Dezavantajı: Rasgele tek bir öğeyi okumak için, doğru adresi bulana kadar bir öğeden diğerine atlamanız gerekir.

Vektörler : Vektörler kullanılırken, bellek normal diziler gibi çok daha organize olur: her n. Öğe (n-1). Öğeden hemen sonra ve (n + 1). Öğeden önce saklanır. Neden listeden daha iyi? Çünkü hızlı rastgele erişime izin verir. Nasıl yapılır: bir vektördeki bir öğenin boyutunu biliyorsanız ve bunlar bellekte bitişik ise, n'inci öğenin nerede olduğunu kolayca tahmin edebilirsiniz; istediğinizi okumak için bir listenin tüm öğelerine göz atmanız gerekmez, vektör ile doğrudan okuyabilirsiniz, yapamayacağınız bir listeyle. Öte yandan, vektör dizisini değiştirin veya bir değeri değiştirmek çok daha yavaştır.

Listeler, belleğe eklenebilen / bellekte çıkarılabilen nesneleri izlemek için daha uygundur. Çok sayıda tekli öğeden bir öğeye erişmek istediğinizde vektörler daha uygundur.

Listelerin nasıl optimize edildiğini bilmiyorum, ancak hızlı okuma erişimi istiyorsanız vektörleri kullanmanız gerektiğini bilmelisiniz, çünkü STL listeleri ne kadar iyi bağlarsa, okuma erişiminde vektörden daha hızlı olmayacaktır.


"vektör dizisini değiştirmek veya bir değeri değiştirmek çok daha yavaştır" - okuduğu gibi, bu, vektörün düşük seviye ve bitişik doğası nedeniyle iyi performans için yatkınlıktan hemen önce söylediklerinizle çelişiyor gibi görünüyor. nedenini boyutunu değiştirerek yeniden tahsis etmenin yavaş olabileceğini mi söylediniz ? daha sonra kabul etti, ancak kullanılabilecek durumlarda bu sorunlardan kaçınıyor. vectorreserve()
underscore_d

10

Temelde bir vektör, otomatik bellek yönetimine sahip bir dizidir. Veriler bellekte bitişik. Ortada veri eklemeye çalışmak pahalı bir işlemdir.

Bir listede veriler ilişkisiz bellek konumlarında saklanır. Ortaya yerleştirmek, yenisine yer açmak için bazı verilerin kopyalanmasını gerektirmez.

Sorunuzu daha ayrıntılı olarak cevaplamak için bu sayfayı alıntılayacağım

vektörler, elemanlara erişmek ve sekansın sonuna eleman eklemek veya çıkarmak için zaman açısından genellikle en verimlidir. Uç dışındaki konumlara eleman ekleme veya çıkarma işlemlerini içeren işlemler için, deque ve listelerden daha kötü performans gösterir ve listelerden daha az tutarlı yineleyiciler ve referanslara sahiptirler.


9

Yineleyicilerin geçersiz kılınamadığı her zaman.


2
Ancak, kalıcı referansların tek dequebaşına yeterli olup olmadığını sormadan, yineleyiciler hakkındaki bu sonuca asla atlamayın .
Potatoswatter

8

Dizinin ortasında çok fazla ekleme veya silme işlemi olduğunda. örneğin bir bellek yöneticisi.


yani aralarındaki fark, işlevsel bir mesele değil, sadece verimliliktir.
skydoor

Her ikisi de elbette bir dizi öğeyi modelliyor. @Dirkgently tarafından belirtildiği gibi kullanımda küçük bir fark vardır, ancak hangi sırayı seçeceğinize karar vermek için "sık yapılan" işlemlerinizin karmaşıklığına bakmanız gerekir (@Martin cevabı).
AraK

@skydoor - Birkaç işlevsel farklılık var. Örneğin, yalnızca vektör rasgele erişimi destekler (yani dizine eklenebilir).
Manuel

2
@skydoor: verimlilik performansa dönüşür. Kötü performans işlevselliği bozabilir. Sonuçta C ++ avantajıdır.
Potatoswatter

4

Yineleyicilerin geçerliliğini korumak liste kullanmak için bir nedendir. Diğeri ise, öğeleri iterken bir vektörün yeniden tahsis edilmesini istemediğiniz zamandır. Bu, akıllı bir rezerv () kullanımı ile yönetilebilir, ancak bazı durumlarda sadece bir liste kullanmak daha kolay veya daha uygun olabilir.


4

Nesneleri kaplar arasında taşımak istediğinizde kullanabilirsiniz list::splice.

Örneğin, bir grafik bölümleme algoritması, artan sayıda kaplar arasında yinelemeli olarak bölünmüş sabit sayıda nesneye sahip olabilir. Nesneler bir kez başlatılmalı ve daima bellekte aynı yerlerde kalmalıdır. Yeniden bağlayarak yeniden düzenlemek, yeniden tahsis etmekten çok daha hızlıdır.

Düzenleme: kütüphaneler C ++ 0x uygulamak için hazırlanırken, bir dizinin bir listeye ekleme genel durum dizinin uzunluğu ile doğrusal karmaşıklık haline geliyor. Bunun nedeni, splice(şimdi) içindeki öğelerin sayısını saymak için dizinin üzerinde yineleme yapılması gerektiğidir. (Listenin boyutunu kaydetmesi gerektiğinden.) Sadece listeyi saymak ve yeniden bağlamak herhangi bir alternatiften daha hızlıdır ve bir listenin tamamını veya tek bir öğeyi eklemek sabit karmaşıklığa sahip özel durumlardır. Ancak, birleştirmek için uzun dizileriniz varsa, daha iyi, eski moda, uyumlu olmayan bir kap için etrafta kazmanız gerekebilir.


2

Kullanılması gereken tek zor kural list, işaretçileri kabın öğelerine dağıtmanız gerektiğidir.

Bunun aksine vector, elementlerin belleğinin yeniden tahsis edilmeyeceğini biliyorsunuz. Olabilirse, kullanılmayan belleğe işaretçileriniz olabilir, ki bu en iyi hayır-hayır ve en kötü a SEGFAULT.

(Teknik olarak vectorbir *_ptrde olur çalışmaları ancak bu durumda size öykünen listbu sadece semantiğini yüzden.)

Diğer yumuşak kurallar, bir kabın ortasına eleman yerleştirmenin olası performans sorunları ile ilgilidir, bunun üzerine listtercih edilir.


2

Bunu basitleştirin
- C ++ 'da kapsayıcı seçerken kafanız karıştığında günün sonunda bu akış şeması görüntüsünü kullanın (bana teşekkürler deyin): - resim açıklamasını buraya girin

Olan vektör
1. Vektör bulaşıcı bellek dayanmaktadır
2. vektör küçük veri-seti için gitmek yoludur
veri seti üzerinde geçme ise 3. vektör hızlı gerçekleştirmek
4. vektör ekleme silme dev veri kümesi üzerinde yavaş ama hızlı çok küçük için

Liste-
1. liste yığın hafızasına dayanır
2. liste çok büyük veri seti
3 için gitmek için bir yoldur . Liste küçük veri seti çapraz geçiş üzerinde nispeten yavaş ama büyük veri setinde
hızlı 4. liste ekleme silme hızlı çok büyük veri kümesi ancak daha küçük olanlar için yavaş


1

Listeler, stl'de iki kez LinkedList için yalnızca bir sarıcıdır, bu nedenle O (1) ekleme ve silme gibi d-link listesinden bekleyebileceğiniz özellik sunar. Vektörler, dinamik bir dizi gibi çalışan bulaşıcı veri dizisidir.


0

Bir vektör ve liste söz konusu olduğunda, benim için ortaya çıkan ana farklar şunlardır:

vektör

  • Bir vektör, öğelerini bitişik bellekte saklar. Bu nedenle, bir vektör içinde rastgele erişim mümkündür, bu da bir vektörün bir öğesine erişmenin çok hızlı olduğu anlamına gelir, çünkü o öğeye erişmek için temel adresi öğe diziniyle çarpabiliriz. Aslında, bu amaç için sadece O (1) veya sabit zaman alır.

  • Bir vektör temel olarak bir diziyi tamamladığından, vektöre (dinamik dizi) her öğe eklediğinizde, zaman maliyetli olan yeni öğeleri barındırmak için yeni bir bitişik bellek bloğu bularak kendini yeniden boyutlandırmalıdır.

  • İşaretçileri içindeki diğer öğelere depolamak için fazladan bellek tüketmez.

liste

  • Liste, öğelerini bitişik olmayan bellekte saklar. Bu nedenle, bir liste içinde rastgele erişim mümkün değildir, yani öğelerine erişmek için işaretçileri kullanmamız ve vektöre göre daha yavaş olan listeyi geçmemiz gerekir. Bu O (n) veya O (1) 'den daha yavaş olan doğrusal zamanı alır.

  • Bir liste bitişik olmayan bellek kullandığı için, bir öğenin bir listeye eklenmesi için geçen süre, vektör karşılığından çok daha verimlidir çünkü belleğin yeniden tahsisi önlenir.

  • Belirli bir öğeden önce ve sonra öğeye işaretçiler depolamak için ek bellek tüketir.

Bu nedenle, bu farklılıkları göz önünde bulundurarak, belirli bir senaryoda vektör vs listesinin galibine karar vermek için genellikle belleği , sık rastlanan rastgele erişimi ve yerleştirmeyi düşünürüz.


0

Liste İki Katına Bağlı Liste'dir, bu nedenle bir öğe eklemek ve silmek kolaydır. Sadece birkaç işaretçiyi değiştirmeliyiz, oysa vektörde ortada bir eleman eklemek istiyorsak, o zaman her eleman bir indeksle kaydırmak zorunda kalır. Ayrıca vektörün boyutu doluysa, önce boyutunu arttırması gerekir. Yani pahalı bir işlemdir. Bu nedenle, ekleme ve silme işlemlerinin daha sık yapılması gereken yerlerde, böyle bir vaka listesi kullanılmalıdır.

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.