Sayı listesinde bir “delik” bulun


14

Belirli bir sıralanmamış tamsayılar listesinde bulunmayan (ve listenin en küçük değerinden daha büyük ) ilk (en küçük) tamsayıyı bulmanın en hızlı yolu nedir ?

İlkel yaklaşımım onları sıralamak ve listeye adım atmak, daha iyi bir yol var mı?


6
@Jodrell Bence sonsuz bir ilerleme sıralamak zor olurdu ;-)
maple_shaft

3
@maple_shaft kabul etti, biraz zaman alabilir.
Jodrell

4
Sıralanmamış bir liste için önce nasıl tanımlarsınız?
Jodrell

1
Bunun kavramsal bir sorun olmadığı için bunun StackOverflow'a ait olduğunu fark ettim.
JasonTrue

2
@JasonTrue SSS'den If you have a question about… •algorithm and data structure conceptsIMHO konusunda.
maple_shaft

Yanıtlar:


29

"Sayı" derken "tamsayı" demek istediğinizi varsayarsak, 2 ^ n boyutunda bir bitvector kullanabilirsiniz; burada n, öğe sayısıdır (aralığınızın 1 ile 256 arasında tamsayılar içerdiğini varsayalım, o zaman 256- bit veya 32 bayt bitvector). Aralıkınızın n konumunda bir tamsayı ile karşılaştığınızda, n'inci biti ayarlayın.

Tamsayıların toplamını listelemeyi bitirdiğinizde, bitvektörünüzdeki bitler üzerinde yinelenir ve 0 setindeki bitlerin konumunu ararsınız. Artık eksik tam sayılarınızın (n) n konumuyla eşleşirler.

Bu O (2 * N), bu nedenle O (N) ve muhtemelen tüm listeyi sıralamaktan daha fazla bellek verimlidir.


6
Doğrudan bir karşılaştırma olarak, tüm pozitif imzasız 32 bit tamsayılarınız varsa, ancak 1, eksik tamsayı sorununu yaklaşık yarım gigabayt bellekte çözebilirsiniz. Bunun yerine sıraladıysanız, 8 gigabayttan fazla bellek kullanmanız gerekir. Ve sıralama, bunun gibi özel durumlar dışında (bir bitvector'unuz olduğunda listeniz sıralanır) neredeyse her zaman n log n veya daha kötüdür, bu nedenle sabitin maliyet karmaşıklığından daha ağır bastığı durumlar dışında doğrusal yaklaşım kazanır.
JasonTrue

1
Aralığı önceden bilmiyorsanız ne olur?
Blrfl

2
Bir tamsayı veri türünüz varsa, Blrfl, daha da daraltmak için yeterli bilginiz olmasa bile, aralığın maksimum kapsamını kesinlikle bilirsiniz. Küçük bir liste olduğunu biliyor ancak tam boyutunu bilmiyorsanız, sıralama daha basit bir çözüm olabilir.
JasonTrue

1
Ya da en küçük ve en büyük elemanı bulmak için önce listede başka bir döngü yapın. Ardından, temel ofset olarak en küçük değere sahip tam boyutlu bir dizi ayırabilirsiniz. Hala açık).
Güvenli

1
@JPatrick: Ödev değil, iş, CS yıl önce mezun oldum :).
Fabian Zeindl

4

Önce listenin tamamını sıralarsanız, en kötü çalışma süresini garanti edersiniz. Ayrıca, sıralama algoritması seçiminiz kritiktir.

Bu soruna nasıl yaklaşacağım:

  1. Listedeki en küçük öğelere odaklanan bir yığın sıralaması kullanın .
  2. Her takastan sonra bir boşluğunuz olup olmadığına bakın.
  3. Bir boşluk bulursanız, o zaman return: Cevabınızı buldunuz .
  4. Bir boşluk bulamazsanız, değiştirmeye devam edin.

İşte bir yığın türünün bir görselleştirmesi .


Bir soru, listenin "en küçük" öğelerini nasıl tanımlarsınız?
Jodrell

4

Ezoterik ve "zeki" olmak için, dizinin sadece bir "deliği" olan özel durumunda, XOR tabanlı bir çözümü deneyebilirsiniz:

  • Dizinizin aralığını belirleyin; bu, dizinin ilk öğesine bir "maks" ve "min" değişkeni ayarlanarak yapılır ve bundan sonraki her öğe için, bu öğe min'den küçük veya maks'den büyükse, min veya maks değerini yeni değer.
  • Aralık kümenin kardinalitesinden bir daha azsa, XOR'u kullanabilmeniz için yalnızca bir "delik" vardır.
  • X tamsayı değişkenini sıfıra başlatın.
  • Min'den maksimuma kadar her tamsayı için, bu değeri X ile XOR yapın ve sonucu X'de saklayın.
  • Şimdi XOR ile dizideki her tamsayıyı X ile takip edin ve her ardışık sonucu daha önce olduğu gibi X'e kaydedin.
  • İşiniz bittiğinde, X "deliğinizin" değeri olacaktır.

Bu, bitvector çözümüne benzer şekilde yaklaşık 2N sürede çalışacaktır, ancak herhangi bir N> sizeof (int) için daha az bellek alanı gerektirir. Bununla birlikte, dizinin birden çok "deliği" varsa, X, tüm deliklerin XOR "toplamı" olacaktır; bu, gerçek delik değerlerine ayrılması zor veya imkansız olacaktır. Bu durumda, diğer cevaplardan "pivot" veya "bitvector" yaklaşımları gibi başka bir yönteme geri dönersiniz.

Karmaşıklığı daha da azaltmak için pivot yöntemine benzer bir şey kullanarak bunu tekrarlayabilirsiniz. Diziyi bir pivot noktasına göre yeniden düzenleyin (bu, sol tarafın maksimumu ve sağın min'i olacaktır; dönerken tam dizinin maksimumu ve min'ini bulmak önemsiz olacaktır). Pivotun sol tarafında bir veya daha fazla delik varsa, sadece bu tarafa geri sarın; aksi taktirde diğer tarafa geri çekilir. Yalnızca bir delik olduğunu belirleyebileceğiniz herhangi bir noktada, bulmak için XOR yöntemini kullanın (bu, temel durum olan bilinen bir deliğe sahip iki öğeden oluşan bir koleksiyona kadar devam etmek yerine genel olarak daha ucuz olmalıdır. saf pivot algoritması).


Bu gülünç zekice ve harika! Şimdi bunu değişken sayıda delikle yapmanın bir yolunu bulabilir misiniz? :-D

2

Karşılaşacağınız sayı aralığı nedir? Bu aralık çok büyük değilse, sayıları olduğu kadar çok öğeye sahip bir dizi, zaman için işlem alanı kullanarak iki tarama (doğrusal zaman O (n)) ile çözebilirsiniz. Aralığı bir tarama daha dinamik olarak bulabilirsiniz. Alanı azaltmak için her sayıya 1 bit atayarak bayt başına 8 sayı depolama alanı sağlayabilirsiniz.

Erken senaryolar için daha iyi olabilecek ve bellek kopyalamak yerine insitu olabilecek diğer seçeneğiniz, bir tarama geçişinde bulunan min bulunan son dakikadan 1 daha fazla değilse, seçim sıralamasından erken çıkmak için değiştirmektir.


1

Hayır gerçek değil. Henüz taranmamış herhangi bir sayı her zaman belirli bir "deliği" dolduran bir sayı olabileceğinden, her sayıyı en az bir kez taramaktan ve daha sonra olası komşularıyla karşılaştırmaktan kaçınamazsınız. Muhtemelen bir ikili ağaç oluşturarak ve daha sonra bir delik bulunana kadar soldan sağa doğru hareket ettirerek işleri hızlandırabilirsiniz, ancak bu sıralama ile aynı derecede karmaşıklıktır. Ve muhtemelen Timsort'tan daha hızlı bir şey bulmayacaksınız .


1
Bir listeyi taramanın sıralama ile aynı zamanda karmaşık olduğunu mu söylüyorsunuz?
maple_shaft

@maple_shaft: Hayır, diyorum ki rastgele verilerden bir ikili ağaç oluşturmak ve sonra soldan sağa doğru hareket ettirmek, sıralamak ve daha sonra küçükten büyüğe doğru geçmek demektir.
pillmuncher

1

Buradaki fikirlerin çoğu sadece sıralamaktan başka bir şey değildir. Bitvector sürümü düz Bucketsort'tur. Öbek türünden de bahsedildi. Temel olarak zaman / mekan gereksinimlerine ve ayrıca elemanların aralığına ve sayısına bağlı olarak doğru sıralama algoritmasını seçmeye kadar kaynar.

Bana göre, bir yığın yapısı kullanmak muhtemelen en genel çözümdür (bir yığın temel olarak size en küçük unsurları tam bir tür olmadan verimli bir şekilde verir).

Ayrıca önce en küçük sayıları bulan yaklaşımları analiz edebilir ve sonra bundan daha büyük olan her tam sayı için tarama yapabilirsiniz. Ya da bir boşluk olacağını umarak en küçük 5 rakamı bulabilirsiniz.

Tüm bu algoritmalar, giriş özelliklerine ve program gereksinimlerine bağlı olarak güçlerine sahiptir.


0

Ek depolama alanı kullanmayan veya tamsayıların genişliğini (32 bit) alan bir çözüm.

  1. Bir lineer geçişte en küçük sayıyı bulun. Buna "min" diyelim. O (n) zaman karmaşıklığı.

  2. Rastgele bir pivot öğesi seçin ve bir hızlı sıralama stili bölümü yapın.

  3. Pivot = ("pivot" - "dk") konumundaysa, bölümün sağ tarafında geri çekilme, bölümün sol tarafında geri çekilme. Buradaki fikir, başlangıçtan itibaren delik yoksa, pivot ("pivot" - "min") konumunda olacaktır, bu nedenle ilk delik bölümün sağında ya da tam tersi olmalıdır.

  4. Temel durum 1 elemanlık bir dizidir ve delik bu eleman ile bir sonraki eleman arasında yer alır.

Beklenen toplam çalışma süresi karmaşıklığı O (n) (sabitlerle 8 * n) ve en kötü durum O (n ^ 2) 'dir. Benzer bir problem için zaman karmaşıklığı analizi burada bulunabilir .


0

Yinelenenlere * sahip olmadığınızı garanti ediyorsanız, genel olarak ve verimli bir şekilde çalışması gereken bir şey bulduğuma inanıyorum (ancak, herhangi bir sayıda delik ve herhangi bir tamsayı aralığına genişletilebilir olmalıdır).

Bu yöntemin arkasındaki fikir, çabuk sıralama gibidir, çünkü etrafında bir pivot ve bölüm buluyoruz, sonra yanlarda bir delikle çentik açıyoruz. Hangi tarafların deliğe sahip olduğunu görmek için en düşük ve en yüksek sayıları buluruz ve bunları o taraftaki pivot ve değer sayısı ile karşılaştırırız. Pivotun 17 ve minimum sayı 11 olduğunu varsayalım. Delik yoksa 6 sayı olmalıdır (11, 12, 13, 14, 15, 16, 17). Eğer 5 tane varsa, o tarafta bir delik olduğunu biliyoruz ve onu bulmak için sadece o tarafta geri çekilebiliriz. Bundan daha açık bir şekilde açıklamakta sorun yaşıyorum, o zaman bir örnek verelim.

15 21 10 13 18 16 22 23 24 20 17 11 25 12 14

Eksen:

10 13 11 12 14 |15| 21 18 16 22 23 24 20 17 25

Şekil 15, borular ( ||) ile gösterilen pivottur . Pivotun sol tarafında 5 (10 - 10) ve sağda 9 (10 - 25 - 15) olması gereken 9 sayı vardır. Bu yüzden sağ tarafta çektik; deliğe bitişik olması durumunda önceki sınırın 15 olduğunu not edeceğiz (16).

[15] 18 16 17 20 |21| 22 23 24 25

Şimdi sol tarafta 4 sayı var ama 5 (21 - 16) olmalı. Bu yüzden oraya gidiyoruz ve yine önceki sınırı (parantez içinde) not edeceğiz.

[15] 16 17 |18| 20 [21]

Sol tarafta doğru 2 sayı (18 - 16) bulunur, ancak sağda 2 yerine 1 (20 - 18) vardır. Son koşullarımıza bağlı olarak, 1 sayısını iki tarafla (18, 20) karşılaştırabilir ve 19'un eksik olduğunu ya da bir kez daha tekrarladığını görebiliriz:

[18] |20| [21]

Sol taraf sıfır boyutuna sahiptir, pivot (20) ile önceki sınır (18) arasında bir boşluk vardır, bu nedenle 19 deliktir.

*: Çiftleri varsa, muhtemelen genel yöntem O (N) tutarak, O (N) zamanında bunları kaldırmak için bir karma kümesi kullanmak, ama bu belki başka bir yöntemi kullanarak daha fazla zaman alır.


1
OP'nin orada sadece bir delik olduğu hakkında bir şey söylediğine inanmıyorum. Girdi sıralanmamış bir sayı listesidir - herhangi bir şey olabilirler. Açıklamanızdan, kaç tane "sayı" olması gerektiğini nasıl belirleyeceğiniz açık değildir.
Caleb

@caleb Kaç delik olduğu önemli değil, sadece kopyalar yok (uygulamada diğer yöntemlerden daha fazla yüke sahip olabilen bir karma kümesiyle O (N) 'de çıkarılabilir). Açıklamayı geliştirmeyi denedim, daha iyi olup olmadığına bakın.
Kevin

Bu doğrusal değil, IMO. Daha çok (logN) ^ 2 gibidir. Her adımda, önem verdiğiniz koleksiyonun alt kümesini (ilk "delik" olarak tanımladığınız önceki alt dizinin yarısı), ardından "delik" varsa sol tarafa geri çekersiniz, veya sol taraf yapmazsa sağ taraf. (logN) ^ 2 hala doğrusaldan daha iyidir; N'nin on katı artarsa, yalnızca 2 (log (N) -1) + 1 adım daha alırsınız.
KeithS

@Keith - maalesef, onları döndürmek için her seviyedeki tüm sayılara bakmanız gerekir, bu yüzden yaklaşık n + n / 2 + n / 4 + ... = 2n (teknik olarak, 2 (nm)) karşılaştırmaları alır .
Kevin
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.