Ne tür bir algoritma bir set gerektirir?


10

İlk programlama derslerimde, bir şeyin kopyalarını kaldırmak gibi şeyler yapmam gerektiğinde bir set kullanmam gerektiği söylendi. Örn: bir vektördeki tüm kopyaları kaldırmak için adı geçen vektörde yineleme yapın ve her öğeyi bir kümeye ekleyin, ardından benzersiz oluşumlarla kalırsınız. Bununla birlikte, her bir elementi başka bir vektöre ekleyerek ve öğenin zaten var olup olmadığını kontrol ederek de yapabilirim. Kullanılan dile bağlı olarak performansta bir fark olabileceğini varsayıyorum. Ama bunun dışında bir set kullanmak için bir neden var mı?

Temel olarak: ne tür algoritmalar bir set gerektirir ve başka bir kap tipi ile yapılmamalıdır?


2
"Set" terimini kullandığınızda ne demek istediğiniz konusunda daha açık olabilir misiniz? Bir C ++ setine mi başvuruyorsunuz?
Robert Harvey

Evet, aslında, "set" tanımı çoğu dilde oldukça benzer görünmektedir: sadece benzersiz öğeleri kabul eden bir kap.
Floella

6
"her öğeyi başka bir vektöre eklemek ve öğenin zaten var olup olmadığını kontrol etmek" - bu sadece bir kümeyi kendiniz uygulamaktır. Öyleyse neden kendiniz elle yazabileceğiniz zaman yerleşik bir özellik kullandığınızı soruyorsunuz?
JacquesB

Yanıtlar:


8

Özellikle setler hakkında soruyorsunuz ama bence sorunuz daha büyük bir kavram: soyutlama. Bunu yapmak için Vector öğesini kullanabileceğinizden kesinlikle haklısınız (Java kullanıyorsanız ArrayList kullanın.) Ama neden burada duruyorsunuz? Vector için ne gerekiyor? Tüm bunları dizilerle yapabilirsiniz.

Diziye bir öğe eklemeniz gerektiğinde, her öğenin üzerinde döngü yapabilirsiniz ve orada değilse, sonunda ekleyebilirsiniz. Ancak, aslında, önce dizide yer olup olmadığını kontrol etmeniz gerekir. Yoksa, daha büyük olan yeni bir dizi oluşturmanız ve eski dizideki tüm mevcut öğeleri yeni diziye kopyalamanız gerekir ve ardından yeni öğeyi ekleyebilirsiniz. Tabii ki, yeni diziyi göstermek için eski diziye yapılan her referansı da güncellemeniz gerekir. Bunların hepsini yaptın mı? Harika! Şimdi tekrar neyi başarmaya çalışıyorduk?

Bunun yerine, bir Set örneği kullanabilir ve yalnızca arayabilirsiniz add(). Kümelerin var olmasının nedeni, birçok ortak sorun için yararlı olan bir soyutlama olmasıdır. Örneğin, yeni bir öğe eklendiğinde öğeleri izlemek ve tepki vermek istediğinizi varsayalım. Sen buna add()bir sette ve döndürdüğü trueveya falseset değiştirildiği olup olmadığına bağlı. Bunları ilkel kullanarak elle yazabilirsiniz ama neden?

Aslında bir Listeniz olduğu ve kopyaları kaldırmak istediğiniz bir durum olabilir. Önerdiğiniz algoritma aslında bunu yapabileceğiniz en yavaş yoldur. Birkaç yaygın daha hızlı yol vardır: onları kovalamak veya sıralamak. Veya bunları, bu algoritmalardan birini uygulayan bir kümeye ekleyebilirsiniz.

Kariyerinizde / eğitiminizde daha önce odak noktası bu algoritmaları oluşturmak ve anlamaktır ve bunu yapmak önemlidir. Ancak profesyonel geliştiricilerin normalde yaptıkları bu değildir. Bu yaklaşımları çok daha ilginç şeyler oluşturmak için kullanıyorlar ve önceden oluşturulmuş ve güvenilir uygulamalar kullanmak tekne yüklerinden tasarruf sağlıyor.


23

Kullanılan dile bağlı olarak performansta bir fark olabileceğini varsayıyorum. Ama bunun dışında bir set kullanmak için bir neden var mı?

Oh evet, (ama performans değil.)

Bir kümeyi kullanabildiğinizde bir küme kullanın, çünkü kullanmamanız ekstra kod yazmanız gerektiği anlamına gelir. Bir set kullanmak, yaptıklarınızı okumayı kolaylaştırır. Teklik mantığı için yapılan tüm testler, onu düşünmek zorunda olmadığınız başka bir yerde gizlidir. Zaten test edilmiş bir yerde ve işe yaradığına güvenebilirsiniz.

Bunu yapmak için kendi kodunuzu yazın ve endişelenmeniz gerekir. Bleh. Bunu kim yapmak ister?

Temel olarak: ne tür algoritmalar bir set gerektirir ve başka bir kap tipi ile yapılmamalıdır?

"Başka bir kapsayıcı türüyle yapılmaması gereken" hiçbir algoritma yoktur. Kümelerden yararlanabilecek algoritmalar vardır. Fazladan kod yazmak zorunda kalmamanız iyi olur.

Şimdi bu konuda belirlenmiş özel bir şey yok. Her zaman ihtiyaçlarınıza en uygun koleksiyonu kullanmalısınız. Java'da bu resmi karar vermede yardımcı buldum. Üç çeşit set olduğunu fark edeceksiniz.

resim açıklamasını buraya girin

@Germi haklı olarak işaret ettiği gibi, eğer iş için doğru koleksiyonu kullanırsanız, kodunuzun başkaları tarafından okunması kolaylaşır.


6
Zaten bahsetmiştiniz, ancak bir set kullanmak, diğer kişilerin kod hakkında akıl yürütmesini de kolaylaştırıyor; yalnızca benzersiz öğeler içerdiğini bilmek için nasıl doldurulduğuna bakmak zorunda değiller.
germi

14

Bununla birlikte, her bir elementi başka bir vektöre ekleyerek ve öğenin zaten var olup olmadığını kontrol ederek de yapabilirim.

Bunu yaparsanız , bir kümenin anlambilimini vektör veri yapısının üstüne uygularsınız . Ekstra kod yazıyorsunuz (hatalar içerebilir) ve çok fazla girişiniz varsa sonuç son derece yavaş olacaktır.

Neden bunu mevcut, test edilmiş, verimli bir set uygulaması kullanarak yapmak istersiniz?


6

Gerçek dünya varlıklarını temsil eden yazılım varlıkları genellikle mantıksal olarak ayarlanır. Örneğin, bir Araba düşünün. Otomobiller benzersiz tanımlayıcılara sahiptir ve otomobil grubu bir set oluşturur. Belirlenen kavram, bir programın bildiği Otomobil koleksiyonunda bir kısıtlama görevi görür ve veri değerlerini kısıtlamak çok değerlidir.

Ayrıca, kümeler çok iyi tanımlanmış bir cebire sahiptir. George'a ait bir dizi Otomobiliniz ve Alice'in sahip olduğu bir setiniz varsa, George ve Alice'in ikisi de aynı araca sahip olsa bile sendika açıkça hem George hem de Alice'in sahip olduğu settir. Dolayısıyla, kümeleri kullanması gereken algoritmalar, ilgili varlıkların mantığının küme özellikleri sergilediği algoritmalardır. Bu oldukça yaygın olduğu ortaya çıkıyor.

Kümelerin nasıl uygulandığı ve benzersizlik kısıtlamasının nasıl garanti edildiği başka bir konudur. Kümeler mantık için çok temel olduğu göz önüne alındığında, yinelenenleri ortadan kaldıran küme mantığı için uygun bir uygulama bulabilmek umulur, ancak uygulamayı kendiniz yapsanız bile, benzersizlik garantisi bir kümeye bir öğenin eklenmesine özgüdür ve "öğenin zaten var olup olmadığını kontrol etmenize" gerek yoktur.


Tekilleştirme için "zaten var olup olmadığını kontrol etmek" genellikle gereklidir. Genellikle nesneler verilerden oluşturulur. Aynı veriler için yalnızca bir nesnenin, aynı verilerden bir nesne oluşturan herkes tarafından yeniden kullanılmasını istiyorsunuz. Böylece yeni bir nesne oluşturursunuz, kümede olup olmadığını kontrol edin, eğer orada ise nesneyi kümeden alırsınız, aksi takdirde nesnenizi eklersiniz. Nesneyi yeni eklediyseniz, yine de birçok özdeş nesneniz olur.
gnasher729

1
@ gnasher729 Set uygulayıcısının sorumluluğu varlığını kontrol etmeyi içerir, ancak Set'in bir kullanıcısıfor 1..100: set.insert(10) sette sadece bir tane olduğunu biliyor ve hala biliyor
Caleth

Kullanıcı, eşit nesne grubundan yüz farklı nesne oluşturabilir. Ekledikten sonra sette on nesne var, ancak hala etrafında 100 nesne var. Tekilleştirme, sette on nesne olduğu ve herkesin bu on nesneyi kullandığı anlamına gelir. Açıkçası sadece bir teste ihtiyacınız yok - bir nesne veren, kümedeki eşleşen nesneyi döndüren bir işleve ihtiyacınız var.
gnasher729

4

Performans karakteristiklerinin yanı sıra (çok önemli ve kolayca göz ardı edilmemesi gereken) Setler soyut bir koleksiyon olarak çok önemlidir.

Dizi ile Set davranışı (performansı yoksayarak) taklit edebilir misiniz? Evet kesinlikle! Her eklediğinizde, öğenin zaten dizide olup olmadığını kontrol edebilir ve ardından öğeyi yalnızca henüz bulunmamışsa ekleyebilirsiniz. Ama bu bilinçli olarak farkında olmanız ve Array-Psuedo-Set'inize her taktığınızda hatırlamanız gereken bir şeydir. Oh bu nedir, önce kopyaları kontrol etmeden bir kez doğrudan taktınız mı? Welp, diziniz değişmezliğini kırdı (tüm öğelerin benzersiz ve eşdeğer olarak hiçbir kopya bulunmadığı).

Peki bunun üstesinden gelmek için ne yapardın? Yeni bir veri türü oluşturacaksınız, diyelim (diyelim ki PsuedoSet), dahili bir Diziyi sarar ve insertöğelerin benzersizliğini zorlayacak bir işlemi genel olarak ortaya koyarsınız. Sarılı diziye yalnızca bu herkese açık insertAPI aracılığıyla erişilebilir olduğundan, kopyaların hiçbir zaman gerçekleşemeyeceğini garanti edersiniz. Şimdi, containsçeklerin performansını artırmak için biraz karma ekleyin ve er ya da geç bir tam çıkış uyguladığınızı anlayacaksınız Set.

Ayrıca bir açıklama ile cevap ve takip soru:

İlk programlama derslerimde, bir şeyin birden fazla sıralı öğesini saklamak gibi şeyler yapmam gerektiğinde bir Dizi kullanmam gerektiği söylendi. Örn: iş arkadaşlarından oluşan bir koleksiyon koleksiyonu saklamak için. Ancak, ham bellek ayırarak ve başlangıç ​​işaretçisi + bazı ofset tarafından verilen bellek adresinin değerini ayarlayarak da bunu yapabilirim.

Bir Diziyi taklit etmek için ham bir işaretçi ve sabit ofsetler kullanabilir misiniz? Evet kesinlikle! Her taktığınızda, ofsetin üzerinde çalıştığınız ayrılmış belleğin sonundan çıkıp çıkmadığını kontrol edebilirsiniz. Ama bu bilinçli olarak farkında olmanız ve Pseudo-Array'ınıza her yerleştirdiğinizde hatırlamanız gereken bir şeydir. Oh bu nedir, önce ofseti kontrol etmeden bir kez doğrudan taktınız mı? Welp, üzerinde isminin yazılı olduğu bir Segmentasyon hatası var!

Peki bunun üstesinden gelmek için ne yapardın? Yeni bir veri türü oluşturacaksınız, onu PsuedoArraybir işaretçi ve bir boyutta saran ve bir insertişlemin herkese açık bir şekilde açığa çıkardığı (diyelim ki ) olarak adlandırırsınız; Sarılan verilere yalnızca bu genel insertAPI aracılığıyla erişilebildiğinden , hiçbir arabellek taşması gerçekleşmeyeceğini garanti edersiniz. Şimdi diğer bazı kullanışlılık işlevlerini (Dizi yeniden boyutlandırma, öğe silme vb.) Ekleyin ve er ya da geç bir tam çıkış uyguladığınızı anlayacaksınız Array.


3

Özellikle kümelerin birleşimlerini ve birleşimlerini gerçekleştirmeniz ve sonucun bir kümeye sahip olması gereken her türlü küme tabanlı algoritmalar vardır.

Küme tabanlı algoritmalar, çeşitli yol bulma algoritmalarında vb.

Set teorisi üzerine bir astar için şu bağlantıya göz atın : http://people.umass.edu/partee/NZ_2006/Set%20Theory%20Basics.pdf

Belirlenmiş anlambilime ihtiyacınız varsa bir küme kullanın. Sahte kopyalar nedeniyle hataları önleyecektir, çünkü vektör / listeyi bir aşamada budamayı unutmuş ve vektör / listenizi sürekli budayarak yapabileceğinizden daha hızlı olacaktır.


1

Aslında standart set kapları kendim çoğunlukla işe yaramaz buluyorum ve sadece dizileri kullanmayı tercih ediyorum ama farklı bir şekilde yapıyorum.

Küme kavşaklarını hesaplamak için, ilk dizi boyunca yinelenir ve öğeleri tek bir bitle işaretlerim. Sonra ikinci dizi üzerinden yineleme ve işaretli öğeleri arıyorum. Voila, bir karma tablodan çok daha az çalışma ve bellekle doğrusal zamanda kesişim ayarlayın, örneğin Sendikalar ve farklılıklar bu yöntemle uygulamak aynı derecede kolaydır. Kod tabanımın onları çoğaltmak yerine indeksleme öğeleri etrafında dönmesine yardımcı olur (endeksleri öğelerin kendilerine değil, öğelere çoğaltırım) ve nadiren sıralanacak bir şeye ihtiyaç duymaz, ancak yıllardır belirli bir veri yapısı kullanmadım sonuç.

Öğeler bu tür amaçlar için veri alanı sunmasa bile kullandığım bazı kötü bit-ciddling C kodu var. Enine biti (asla kullanmadığım) çaprazlanmış öğeleri işaretlemek için ayarlayarak elemanların belleğini kullanmayı içerir. Bu oldukça iğrenç, gerçekten yakın montaj seviyesinde çalışmadığınız sürece bunu yapmayın, ancak öğelerin geçiş için belirli bir alan sağlamadığı durumlarda bile bunun nasıl uygulanabileceğini belirtmek istedim. bazı bitler asla kullanılmayacaktır. Dinky i7'mde bir saniyeden daha az bir sürede 200 milyon eleman (2.4 gig veri) arasında belirli bir kavşak hesaplayabilir. std::setHer biri aynı anda yüz milyon eleman içeren iki örnek arasında belirli bir kavşak yapmayı deneyin ; yaklaşmıyor bile.

Bu bir yana ...

Bununla birlikte, her bir elementi başka bir vektöre ekleyerek ve öğenin zaten var olup olmadığını kontrol ederek de yapabilirim.

Yeni vektörde bir elemanın mevcut olup olmadığını kontrol etmek, genellikle ayarlanan kavşağın kuadratik bir operasyon olmasını sağlayacak doğrusal bir zaman operasyonu olacaktır (patlayıcı çalışma miktarı giriş boyutu büyüdükçe). Sadece düz eski vektörleri veya dizileri kullanmak ve harika bir şekilde ölçeklendirilecek şekilde yapmak istiyorsanız yukarıdaki tekniği tavsiye ederim.

Temel olarak: ne tür algoritmalar bir set gerektirir ve başka bir kap tipi ile yapılmamalıdır?

Önyargılı görüşümü, konteyner düzeyinde (özellikle set işlemlerini verimli bir şekilde sağlamak için özel olarak uygulanan bir veri yapısında olduğu gibi) konuşuyorsanız sormazsınız, ancak kavramsal düzeyde set mantığı gerektiren çok şey vardır. Örneğin, bir oyun dünyasında hem uçan hem de yüzebilen yaratıkları bulmak istediğinizi ve bir sette (aslında bir set kabı kullanıp kullanmadığınıza bakılmaksızın) ve başka bir sette yüzebilen yaratıklara sahip olduğunuzu varsayalım . Bu durumda, ayarlanmış bir kavşak istersiniz. Uçabilecek ya da büyülü olabilecek yaratıklar istiyorsanız, o zaman belirli bir birlik kullanırsınız. Tabii ki bunu uygulamak için bir set konteynerine ihtiyacınız yoktur ve en uygun uygulama genellikle bir set olarak özel olarak tasarlanmış bir konteynere ihtiyaç duymaz veya bunu istemez.

Teğetten Çıkmak

Pekala, JimmyJames'ten bu set kavşak yaklaşımıyla ilgili bazı güzel sorular aldım. Konu biraz saptırıyor ama ah, daha fazla insanın sadece temel operasyonlar için dengeli ikili ağaçlar ve hash tabloları gibi tüm yardımcı yapıları inşa etmemeleri için kesişmeyi ayarlamak için bu temel müdahaleci yaklaşımı kullandığını görmekle ilgileniyorum. Belirtildiği gibi, temel gereklilik, listelerin sığ kopya öğelerini, ilk sıralanmamış listeden veya diziden veya daha sonra ikinci olarak alınacak her şeyden geçerken "işaretlenebilen" paylaşılan bir öğeyi dizine ekleyecek veya işaret edecek şekilde olmasıdır. ikinci listeden geçmek.

Bununla birlikte, bu, aşağıdaki unsurlara dokunmadan pratik olarak çok iş parçacıklı bir bağlamda gerçekleştirilebilir:

  1. İki küme elemanlara endeksler içerir.
  2. Endeks aralığı çok fazla değil (diyelim [0, 2 ^ 26), milyarlarca veya daha fazla değil) ve makul derecede yoğun.

Bu, ayarlanan işlemler için paralel bir dizi (öğe başına sadece bir bit) kullanmamıza izin verir. Diyagram:

resim açıklamasını buraya girin

İş parçacığı senkronizasyonu yalnızca havuzdan paralel bir bit dizisi alırken ve onu havuza geri gönderirken (kapsam dışına çıkıldığında örtük olarak yapılır) orada olmalıdır. Ayarlanan işlemi gerçekleştirmek için gerçek iki döngü herhangi bir evre senkronizasyonu gerektirmez. İş parçacığı bitleri yerel olarak tahsis edip serbest bırakabiliyorsa paralel bir bit havuzu kullanmamız bile gerekmez, ancak bit havuzu, merkezi öğelerin sıklıkla başvuruda bulunduğu bu tür bir veri sunumuna uyan kod tabanlarındaki deseni genelleştirmek için kullanışlı olabilir Böylece her bir iş parçacığının verimli bellek yönetimi ile uğraşmasına gerek kalmaz. Bölgem için başlıca örnekler, varlık bileşeni sistemleri ve dizin oluşturulmuş ağ temsilleridir. Her ikisi de sık sık kesişme noktalarına ihtiyaç duyar ve merkezi olarak depolanan her şeye gönderme eğilimindedir (ECS ve köşelerde bileşenler ve varlıklar, kenarlar,

Endeksler yoğun bir şekilde işgal edilmez ve seyrek olarak dağılmazsa, bu, paralel bit / boolean dizisinin makul bir seyrek uygulanmasıyla uygulanabilir, örneğin yalnızca 512 bitlik yığınlarda belleği depolayan (512 bitişik indeksi temsil eden kaydedilmemiş düğüm başına 64 bayt) ) ve tamamen boş bitişik blokların tahsis edilmesini atlar. Eğer merkezi veri yapılarınız elementlerin kendileri tarafından çok az işgal ediliyorsa, zaten böyle bir şey kullanıyorsunuzdur.

resim açıklamasını buraya girin

... seyrek bir bit kümesinin paralel bit dizisi olarak hizmet etmesi için benzer bir fikir. Bu yapılar aynı zamanda değişmezliğe de katkıda bulunur, çünkü yeni bir değişmez kopya oluşturmak için derin kopyalanması gerekmeyen sığ kopya tıknaz blokları kolaydır.

Yine yüz milyonlarca eleman arasındaki kesişimleri çok ortalama bir makinede bu yaklaşım kullanılarak bir saniyenin altında yapılabilir ve bu tek bir iş parçacığının içindedir.

İstemci, sonuçta elde edilen kavşak için bir öğe listesine ihtiyaç duymuyorsa, her iki listede bulunan öğelere sadece bir mantık uygulamak istiyorsa, hangi noktada geçebilecekleri gibi yarıdan daha kısa sürede yapılabilir. bir işlev işaretçisi veya işlevi veya temsilci veya kesişen öğelerin işlem aralığına geri çağrılacak her şey. Bu etki için bir şey:

// 'func' receives a range of indices to
// process.
set_intersection(func):
{
    parallel_bits = bit_pool.acquire()

    // Mark the indices found in the first list.
    for each index in list1:
        parallel_bits[index] = 1

    // Look for the first element in the second list 
    // that intersects.
    first = -1
    for each index in list2:
    {
         if parallel_bits[index] == 1:
         {
              first = index
              break
         }
    }

    // Look for elements that don't intersect in the second
    // list to call func for each range of elements that do
    // intersect.
    for each index in list2 starting from first:
    {
        if parallel_bits[index] != 1:
        {
             func(first, index)
             first = index
        }
    }
    If first != list2.num-1:
        func(first, list2.num)
}

... ya da bu yönde bir şey. İlk diyagramdaki sözde kodun en pahalı kısmı intersection.append(index)ikinci döngüdedir ve bu std::vector, daha küçük listenin boyutuna önceden ayrılmış için bile geçerlidir .

Ya Her Şeyi Derin Kopyalarsam?

Kes şunu! Ayarlı kavşaklar yapmanız gerekiyorsa, kesişmek için verileri çoğalttığınız anlamına gelir. En küçük nesneleriniz bile 32 bitlik bir dizinden daha küçük olmayabilir. Gerçekte ~ 4.3 milyardan fazla öğeye ihtiyaç duyulmadığı sürece, öğelerinizin adresleme aralığını 2 ^ 32 (2 ^ 32 eleman, 2 ^ 32 bayt değil) azaltmak çok mümkündür, bu noktada tamamen farklı bir çözüme ihtiyaç vardır ( ve bu kesinlikle bellekte set kapları kullanmaz).

Anahtar Maçlar

Peki, öğelerin aynı olmadığı ancak eşleşen anahtarları olabileceği ayarlanmış işlemler yapmamız gereken durumlara ne dersiniz? Bu durumda, yukarıdakiyle aynı fikir. Her benzersiz anahtarı bir dizine eşlememiz yeterlidir. Örneğin, anahtar bir dize ise, stajyer dizeler bunu yapabilir. Bu durumlarda, dize anahtarlarını 32 bit dizinlere eşlemek için bir trie veya karma tablo gibi güzel bir veri yapısı çağrılır, ancak sonuçta elde edilen 32 bit dizinlerde ayarlanan işlemleri yapmak için bu tür yapılara ihtiyacımız yoktur.

Makinenin tüm adresleme aralığı değil, çok makul bir aralıktaki elemanlarla endekslerle çalışabildiğimizde, çok sayıda ucuz ve anlaşılır algoritmik çözüm ve veri yapısı bu şekilde açılır ve bu yüzden genellikle buna değmez. her benzersiz anahtar için benzersiz bir dizin elde edebilir.

Endeksleri Seviyorum!

Pizza ve bira kadar endeksleri seviyorum. 20'li yaşlarımdayken, C ++ 'a girdim ve her türlü tam standart uyumlu veri yapısını tasarlamaya başladım (derleme zamanında bir dolum cihazından bir ayırma cihazını ayırmak için gerekli numaralar dahil). Geçmişe bakıldığında bu büyük bir zaman kaybıydı.

Veritabanınızı parçaları diziler halinde merkezi olarak depolamak ve bunları parçalanmış ve potansiyel olarak makinenin tüm adreslenebilir aralığı boyunca depolamak yerine endekslemek etrafında döndürürseniz, algoritmik ve veri yapısı olasılıkları dünyasını sadece düz eski etrafında dönen kaplar ve algoritmalar tasarlama intveya int32_t. Ve sonuçların sürekli olarak bir veri yapısından diğerine öğeleri aktarmadığım yerlerde çok daha verimli ve bakımı kolay buldum.

Bazı örneklerde, herhangi bir benzersiz değerinin Tbenzersiz bir dizine ve merkezi bir dizide yer alan örneklere sahip olacağını varsayabileceğiniz durumlar kullanılır :

Endeksler için işaretsiz tamsayılarla iyi çalışan çok iş parçacıklı sayı tabanı sıralamaları . Aslında, Intel'in kendi paralel sıralaması olarak yüz milyon elemanı sıralamak için yaklaşık 1/10 zaman alan çok iş parçacıklı bir radyan türüne sahibim ve Intel'in zaten std::sortbu kadar büyük girdilere göre 4 kat daha hızlı . Elbette Intel'in karşılaştırma tabanlı bir türü olduğu ve şeyleri sözlükbilimsel olarak sıralayabildiği için çok daha esnektir, bu yüzden elmaları portakallarla karşılaştırır. Ama burada sadece portakallara ihtiyacım var, sadece önbellek dostu bellek erişim kalıplarını elde etmek veya kopyaları hızlı bir şekilde filtrelemek için bir sayı tabanı sıralaması yapabilirim.

Düğüm başına yığın tahsisi olmadan bağlantılı listeler, ağaçlar, grafikler, ayrı zincirleme karma tabloları vb . Düğümleri, elemanlara paralel olarak toplu olarak tahsis edebilir ve bunları endekslerle birbirine bağlayabiliriz. Düğümlerin kendileri bir sonraki düğüme 32 bitlik bir dizin haline gelir ve şöyle büyük bir dizide saklanır:

resim açıklamasını buraya girin

Paralel işleme için kolay. Bağlantılı yapılar genellikle paralel işleme için o kadar kolay değildir, çünkü en azından ağaçta veya bağlantılı liste geçişinde paralellik elde etmeye çalışmak, örneğin bir dizi boyunca döngü için bir paralel yapmak yerine getirmek oldukça gariptir. Dizin / merkezi dizi gösterimi ile her zaman bu merkezi diziye gidebilir ve her şeyi tıknaz paralel döngülerde işleyebiliriz. Her zaman bu şekilde işleyebileceğimiz tüm öğelerin merkezi dizisine sahibiz, ancak sadece bazılarını işlemek istesek bile (hangi noktada merkezi diziden önbellek dostu erişim için sayı tabanı sıralı bir liste tarafından dizine alınan öğeleri işleyebilirsiniz).

Verileri anında her öğeye sabit zamanda ilişkilendirebilir . Yukarıdaki paralel bit dizisinde olduğu gibi, paralel verileri, örneğin geçici işleme için elemanlarla kolayca ve son derece ucuz bir şekilde ilişkilendirebiliriz. Bunun geçici verilerin ötesinde kullanım örnekleri vardır. Örneğin, bir ağ sistemi, kullanıcıların bir ağa istedikleri kadar UV haritası eklemesine izin vermek isteyebilir. Böyle bir durumda, AoS yaklaşımı kullanarak her bir köşe ve yüzde kaç UV haritasının olacağını kodlayamayız. Bu tür verileri anında ilişkilendirebilmeliyiz ve paralel diziler orada kullanışlı ve her türlü karmaşık ilişkisel kaptan, hatta karma tablolardan çok daha ucuzdur.

Tabii ki paralel diziler, paralel dizileri birbirleriyle senkronize tutmada hataya eğilimli olmaları nedeniyle kaşlarını çatır. Örneğin, dizin 7'deki bir öğeyi "kök" dizisinden kaldırdığımızda, aynı şekilde "çocuklar" için de aynı şeyi yapmamız gerekir. Bununla birlikte, çoğu dilde, bu kavramı genel amaçlı bir kapsayıcıya genelleştirmek için yeterince kolaydır, böylece paralel dizileri birbiriyle senkronize tutmak için zor mantık, tüm kod tabanı boyunca tek bir yerde bulunmalıdır ve böyle bir paralel dizi kabı sonraki eklemeler sonrasında dizideki bitişik boş alanlar için çok fazla bellek israfını önlemek için yukarıdaki seyrek dizi uygulamasını kullanın.

Daha Ayrıntılı: Seyrek Bitset Ağacı

Pekala, alaycı olduğunu düşündüğüm biraz daha detaylandırma isteğim var, ama yine de yapacağım çünkü çok eğlenceli! İnsanlar bu fikri tamamen yeni seviyelere taşımak istiyorsa, N + M öğeleri arasında doğrusal olarak döngü yapmadan ayarlanmış kavşaklar gerçekleştirmek mümkündür. Bu, asırlardır ve temel olarak modeller için kullandığım nihai veri yapım set<int>:

resim açıklamasını buraya girin

Her iki listedeki her öğeyi bile denetlemeden küme kesişmeleri gerçekleştirebilmesinin nedeni, hiyerarşinin kökündeki tek bir küme bitinin, örneğin kümede bir milyon bitişik öğenin işgal edildiğini gösterebilmesidir. Sadece bir biti inceleyerek, aralıktaki N indekslerinin [first,first+N)N'de çok büyük bir sayı olabileceği sette olduğunu biliyoruz .

Bunu aslında işgal edilmiş endeksleri dolaşırken bir döngü iyileştirici olarak kullanıyorum, çünkü diyelim ki sette 8 milyon endeks var. Normalde bellekte 8 milyon tamsayıya erişmemiz gerekir. Bununla, potansiyel olarak sadece birkaç biti inceleyebilir ve işgal edilen endekslerin indeks aralıkları ile dolaşabilir. Ayrıca, ortaya çıkan endeks aralıkları, örneğin orijinal eleman verilerine erişmek için kullanılan sıralanmamış bir dizi indeks üzerinden yinelemenin aksine, çok önbellek dostu sıralı erişim sağlayan sıralı düzendedir. Tabii ki bu teknik son derece seyrek vakalar için daha kötüdür, en kötü senaryo her bir indeksin çift sayı (veya her biri garip) olması gibi bir durumdadır, bu durumda bitişik bölge yoktur. Ama en azından kullanım durumlarımda,


2
"Set kavşaklarını hesaplamak için, ilk dizi boyunca yinelenir ve öğeleri tek bir bitle işaretlerim. Sonra ikinci dizi boyunca yinelenir ve işaretli öğeleri ararım." Onları ikinci dizide nerede işaretliyorsunuz?
JimmyJames

1
Görüyorum ki, her bir değeri temsil eden tek bir nesneyi veriye 'yerleştiriyorsunuz'. Setler için bir dizi kullanım durumu için ilginç bir tekniktir. Bu yaklaşımı kendi belirlediğiniz sınıfınızda bir operasyon olarak uygulamamanın hiçbir nedeni görmüyorum.
JimmyJames

2
“Bu, bazı durumlarda kapsüllenmeyi ihlal eden müdahaleci bir çözüm ...” Ne demek istediğini anladım, bu benim başıma geldi ama bence buna gerek yok. Bu davranışı yöneten bir sınıfınız varsa, dizin nesneleri tüm öğe verilerinden bağımsız olabilir ve koleksiyon türünüzün tüm örnekleri arasında paylaşılabilir. yani bir ana veri kümesi olacaktır ve sonra her örnek ana kümeye geri dönecektir. Çoklu iş parçacığının daha karmaşık olması gerekir, ancak bence yönetilebilir olur.
JimmyJames

1
Bu bir veritabanı çözümünde potansiyel olarak yararlı olacak gibi görünüyor ama bu şekilde uygulanan herhangi bir olup olmadığını bilmiyorum. Bunu buraya koyduğun için teşekkürler. Aklım çalışıyor.
JimmyJames

1
Biraz daha ayrıntı verebilir misiniz? ;) Biraz (çok) zamanım olduğunda kontrol edeceğim.
JimmyJames

-1

N öğesi içeren bir kümenin başka bir X öğesi içerip içermediğini kontrol etmek tipik olarak sabit bir zaman alır. N öğesi içeren bir dizinin başka bir X öğesi içerip içermediğini kontrol etmek tipik olarak O (n) zaman alır. Bu kötü, ancak kopyaları n öğeden kaldırmak istiyorsanız, aniden O (n ^ 2) yerine O (n) alır; 100.000 ürün bilgisayarınızı dizlerinin üstüne getirir.

Ve daha fazla neden mi istiyorsun? "Çekim dışında akşamın tadını çıkardın mı, Lincoln?"


2
Sanırım bunu tekrar okumak isteyebilirsin. O (n²) yerine O (n) zamanı almak genellikle iyi bir şey olarak kabul edilir.
JimmyJames

Belki bunu okurken kafanın üzerinde durdun? OP "neden sadece bir dizi almıyor" diye sordu.
gnasher729

2
Neden O (n²) 'den O (n)' ye dizlerinin üstüne bir bilgisayar getirecek? Bunu sınıfımda kaçırmış olmalıyım.
JimmyJames
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.