Sıralanmamış dizideki bir aralıktan maksimum değer alınıyor


9

Bir sahip sıralanmamış dizi . Ben bir aralık vermek sorguları var ve sonra bu aralıktan maksimum değer döndürmek zorunda. Örneğin:

array[]={23,17,9,45,78,2,4,6,90,1};
query(both inclusive): 2 6
answer: 78

Herhangi bir aralıktan maksimum değeri hızlı bir şekilde almak için hangi algoritmayı veya veri yapısını oluşturuyorum. (Çok fazla sorgu var)

EDIT: Bu gerçekten asıl sorunun basit bir sürümüdür. Dizi boyutu 100000 kadar büyük olabilir ve 100000 kadar sorgu sayısı olabilir. Bu yüzden kesinlikle hızlı bir sorgu yanıtı kolaylaştıracak bazı önişleme gerektirir.


5
Neden ayrılmıyor? Sıralıysa sorun önemsizdir, bu yüzden bariz yaklaşım sıralamaktır.

1
@delnan Ekstra bir mekanizma olmadan, hangi değerlerin orijinal olarak sorgulanacak aralıkta olduğu izini kaybedersiniz ...
Thijs van Dien

Tüm probleminizi belirtin. Eğer bu bilgi (veya başka herhangi bir bilgi) önemliyse, bunu çözüme katmak için bilmek gerekir.

1
Bir şey mi kaçırıyorum, yoksa bu sadece 2'den 6'ya kadar olan öğeleri ziyaret etme ve bu öğelerin maksimum değerini bulma meselesi mi?
Blrfl

@Blrfl: Pek çok sorgunun bir kısmı dışında, hiçbir şey eksik olduğunu düşünmüyorum . Sorguları sıralı bir aramadan önemli ölçüde daha ucuz hale getiren bir yapı oluşturmanın herhangi bir anlamı olup olmadığı gerçekten açık değildir. (Her ne kadar bu fikrin bu olmadığını sorduğumda soru sormanın pek bir anlamı olmaz.)
Mike Sherrill 'Cat Recall'

Yanıtlar:


14

Her düğümün çocuklarının maksimum değerini temsil ettiği bir tür ikili ağaç oluşturabileceğinizi düşünüyorum:

            78           
     45            78     
  23    45     78      6  
23 17  9 45   78 2    4 6   

O zaman sadece sorgulanan aralıktaki maksimum değeri bulmak için en az hangi düğümleri kontrol etmeniz gerektiğini belirlemenin bir yolunu bulmanız gerekir. Bu örnekte, bunun yerine dizin aralığında [2, 6](dahil) maksimum değeri elde etmek için . Ağaç büyüdükçe, kazanç daha büyük olacaktır.max(45, 78, 4)max(9, 45, 78, 2, 4)


1
Bunun çalışması için örnek ağacınızda eksik bilgiler var: Her iç düğümde hem maksimum hem de toplam alt düğüm sayısı bulunmalıdır. Aksi takdirde aramanın (örneğin) tüm alt öğelerine bakması 78(ve atlaması 2) gerekmediğini bilmenin bir yolu yoktur , çünkü her şey için dizin 6bu alt ağaçta olduğunu bilir .
Izkata

Aksi takdirde, bu oldukça yaratıcı bulduğum için +1
Izkata

+1: Bu, bir listenin alt sıraları hakkındaki sorguları yanıtlamak için güçlü bir tekniktir (N) zamanında, kök düğümdeki veriler, çocuklardaki verilerden sabit zamanda hesaplanabiliyorsa kullanılabilir.
kevin cline

Bu fikir harika. O (logn) sorgu süresi verir. Bence @Izkata da iyi bir noktaya değindi. Ağaç düğümünü kapsadığı sol ve sağ aralıklar hakkında bilgilerle arttırabiliriz. Yani bir aralık verildiğinde, sorunun ikiye nasıl bölüneceğini bilir. Uzay açısından, tüm veriler yaprak düzeyinde saklanır. Bu yüzden depolamak için O (N) olan 2 * N alan gerektirir. Segment ağacının ne olduğunu bilmiyorum, ama bu segment ağacının arkasındaki fikir mi?
Kay

Önişleme açısından, ağacı oluşturmak O (n) gerektirir.
Kay

2

Ngoaho91'in cevabını tamamlamak için.

Bu sorunu çözmenin en iyi yolu, Segment Ağacı veri yapısını kullanmaktır. Bu, O (log (n)) içindeki bu tür sorguları yanıtlamanıza izin verir, bu da algoritmanızın toplam karmaşıklığının O (Q logn) olacağı anlamına gelir; burada Q, sorgu sayısıdır. Saf algoritmayı kullandıysanız, toplam karmaşıklık O (Qn ) olacaktır ki bu daha yavaştır.

Bununla birlikte, Segment Ağaçlarının kullanımının bir dezavantajı vardır. Çok fazla bellek gerektirir, ancak çoğu zaman bellekle ilgili hızdan daha az önem verirsiniz.

Bu DS tarafından kullanılan algoritmaları kısaca anlatacağım:

Segment ağacı, her düğümün atandığı aralığın değerini tuttuğu İkili Arama Ağacının özel bir örneğidir. Kök düğüme [0, n] aralığı atanır. Sol çocuğa [0, (0 + n) / 2] ve sağ çocuğa [(0 + n) / 2 + 1, n] atanır. Bu şekilde ağaç inşa edilecek.

Ağaç Yarat :

/*
    A[] -> array of original values
    tree[] -> Segment Tree Data Structure.
    node -> the node we are actually in: remember left child is 2*node, right child is 2*node+1
    a, b -> The limits of the actual array. This is used because we are dealing
                with a recursive function.
*/

int tree[SIZE];

void build_tree(vector<int> A, int node, int a, int b) {
    if (a == b) { // We get to a simple element
        tree[node] = A[a]; // This node stores the only value
    }
    else {
        int leftChild, rightChild, middle;
        leftChild = 2*node;
        rightChild = 2*node+1; // Or leftChild+1
        middle = (a+b) / 2;
        build_tree(A, leftChild, a, middle); // Recursively build the tree in the left child
        build_tree(A, rightChild, middle+1, b); // Recursively build the tree in the right child

        tree[node] = max(tree[leftChild], tree[rightChild]); // The Value of the actual node, 
                                                            //is the max of both of the children.
    }
}

Sorgu Ağacı

int query(int node, int a, int b, int p, int q) {
    if (b < p || a > q) // The actual range is outside this range
        return -INF; // Return a negative big number. Can you figure out why?
    else if (p >= a && b >= q) // Query inside the range
        return tree[node];
    int l, r, m;
    l = 2*node;
    r = l+1;
    m = (a+b) / 2;
    return max(query(l, a, m, p, q), query(r, m+1, b, p, q)); // Return the max of querying both children.
}

Daha fazla açıklamaya ihtiyacınız varsa, bana bildirin.

BTW, Segment Ağacı, O öğesindeki tek bir öğenin veya bir dizi öğenin güncellenmesini de destekler (log n)


ağacı doldurmanın karmaşıklığı nedir?
Pieter B

Tüm öğeleri gözden geçirmeniz gerekir O(log(n))ve her öğenin ağaca eklenmesi gerekir. Bu nedenle, toplam karmaşıklıkO(nlog(n))
Andrés

1

En iyi algoritma O (n) zamanında aşağıdaki gibi başlayacaktır, bitiş aralık sınırlarının endeksi olmalıdır

int findMax(int[] a, start, end) {
   max = Integer.MIN; // initialize to minimum Integer

   for(int i=start; i <= end; i++) 
      if ( a[i] > max )
         max = a[i];

   return max; 
}

4
Yalnızca OP'nin geliştirmeye çalıştığı algoritmayı tekrarlamak için -1.
kevin cline

1
Belirtilen soruna bir çözüm göndermek için +1. Bu gerçekten bir dizi varsa ve sınırların bir a priori olacağını bilmiyorsanız bunu yapmanın tek yoludur . (I başlatmak olsa da maxhiç a[i]ve başlangıç forolarak döngü i+1.)
Blrfl

@kevincline Sadece yeniden ifade etmekle kalmıyor, aynı zamanda "Evet, bu görev için zaten en iyi algoritmaya sahipsiniz" diyor, küçük bir gelişme (atla start, dur end). Ve bu, kabul ediyorum olan bir kerelik arama için en iyisi. @ ThijsvanDien'in cevabı, yalnızca başlangıçta kurulum daha uzun sürdüğü için, arama birden çok kez yapılacaksa daha iyidir.
Izkata

Bu cevabı gönderirken, soru aynı veriler üzerinde birçok sorgu yapacağını doğrulayan düzenlemeyi içermiyordu.
Izkata

1

İkili ağaç / segment ağaç bazlı çözümler gerçekten doğru yönü gösteriyor. Bununla birlikte, çok fazla ek bellek gerektirdiklerine itiraz edilebilir. Bu sorunların iki çözümü vardır:

  1. İkili ağaç yerine örtük veri yapısı kullanma
  2. İkili ağaç yerine M-ary ağacı kullanın

İlk nokta, ağaç çok yapılandırılmış olduğundan, ağacı düğümler, sol ve sağ işaretçiler, aralıklar vb. İle temsil etmek yerine ağacı örtülü olarak tanımlamak için yığın benzeri bir yapı kullanabilirsiniz. hiçbir performans isabeti yok - biraz daha işaretçi aritmetiği yapmanız gerekiyor.

İkinci nokta, değerlendirme sırasında biraz daha fazla çalışma pahasına, bir ikili ağaç yerine bir M-ary ağacı kullanabilmenizdir. Örneğin, 3 ary ağacı kullanıyorsanız, bir seferde en fazla 3 eleman, daha sonra bir seferde 9 eleman, daha sonra 27 vb. Hesaplayacaksınız. Gerekli ekstra depolama alanı N / (M-1) - geometrik seri formülü kullanarak ispatlayabilirler. M = 11 seçerseniz, ikili ağaç yönteminin 1 / 10'unu saklamanız gerekir.

Python'daki bu saf ve optimize edilmiş uygulamaların aynı sonuçları verdiğini doğrulayabilirsiniz:

class RangeQuerier(object):
    #The naive way
    def __init__(self):
        pass

    def set_array(self,arr):
        #Set, and preprocess
        self.arr = arr

    def query(self,l,r):
        try:
            return max(self.arr[l:r])
        except ValueError:
            return None

vs.

class RangeQuerierMultiLevel(object):
    def __init__(self):
        self.arrs = []
        self.sub_factor = 3
        self.len_ = 0

    def set_array(self,arr):
        #Set, and preprocess
        tgt = arr
        self.len_ = len(tgt)
        self.arrs.append(arr)
        while len(tgt) > 1:
            tgt = self.maxify_one_array(tgt)
            self.arrs.append(tgt)

    def maxify_one_array(self,arr):
        sub_arr = []
        themax = float('-inf')
        for i,el in enumerate(arr):
            themax = max(el,themax)
            if i % self.sub_factor == self.sub_factor - 1:
                sub_arr.append(themax)
                themax = float('-inf')
        return sub_arr

    def query(self,l,r,level=None):
        if level is None:
            level = len(self.arrs)-1

        if r <= l:
            return None

        int_size = self.sub_factor ** level 

        lhs,mid,rhs = (float('-inf'),float('-inf'),float('-inf'))

        #Check if there's an imperfect match on the left hand side
        if l % int_size != 0:
            lnew = int(ceil(l/float(int_size)))*int_size
            lhs = self.query(l,min(lnew,r),level-1)
            l = lnew
        #Check if there's an imperfect match on the right hand side
        if r % int_size != 0:
            rnew = int(floor(r/float(int_size)))*int_size
            rhs = self.query(max(rnew,l),r,level-1)
            r = rnew

        if r > l:
            #Handle the middle elements
            mid = max(self.arrs[level][l/int_size:r/int_size])
        return max(max(lhs,mid),rhs)

0

"segment ağacı" veri yapısını deneyin
2 adımlı
build_tree () O (n)
sorgusu vardır (int min, int max) O (nlogn)

http://en.wikipedia.org/wiki/Segment_tree

Düzenle:

siz sadece gönderdiğim wiki'yi okumuyorsunuz!

Bu algoritma:
- Ağaç oluşturmak için diziyi 1 kez geçersiniz. O (n)
- sonraki 100000000+ kez dizinin herhangi bir kısmının maksimum değerini bilmek istiyorsanız, sorgu işlevini çağırmanız yeterlidir. Her sorgu için O (logn)
- c ++ burada uygulayın geeksforgeeks.org/segment-tree-set-1-range-minimum-query/
eski algoritma:
her sorgu, sadece seçilen alanı geç ve bul.

yani, bu algoritmayı bir kez işlemek için kullanacaksanız, tamam, eski yöntemden daha yavaştır. ancak çok sayıda sorgu (milyar)

işleyecekseniz, 0-1000000 arası test satırı 1: 50000 rasgele sayı için '(boşluk)' (dizi)
satırına bölünmüş şekilde böyle bir metin dosyası oluşturabilirsiniz. 2: 2 rasgele sayı 1'den 50000'e, '(boşluk)' ile bölünür (bu sorgu)
...
satır 200000: satır 2'yi sever, rastgele sorgu da

bu örnek problem, üzgünüm ama bu Vietnamca
http://vn.spoj.com/problems/NKLINEUP/
eski yolla çözerseniz , asla geçemezsiniz.


3
Bunun alakalı olduğunu düşünmüyorum. Bir aralık ağacı tamsayıları değil aralıkları tutar ve izin verdikleri işlemler OP'nin istediği gibi görünmez. Elbette, tüm olası aralıkları oluşturabilir ve bunları bir aralık ağacında saklayabilirsiniz, ancak (1) üstel olarak birçoğu vardır, bu yüzden ölçeklenmez ve (2) işlemler hala hangi OP'ye benzemiyor istiyor.

benim hatam, aralık ağacı değil, segment ağacı demek.
ngoaho91

İlginç, sanırım bu ağaca hiç rastlamadım! Ancak bu hala olası tüm aralıkların saklanmasını gerektirir. Ben düşünüyorum oldukça pahalı olanların O (n ^ 2), var. (Ayrıca, k sonuçları için O (log n + k) sorgusu

evet, void build_tree () diziyi geçmelidir. ve her düğüm için maksimum (veya min) değeri depolar. ancak çoğu durumda bellek maliyeti hızdan daha önemli değildir.
ngoaho91

2
O(n)Tarun_telang'ın cevabında açıklandığı gibi, bu dizinin düz bir aramadan daha hızlı olduğunu düşünemiyorum . İlk içgüdüsü O(log n + k)daha hızlıdır O(n), ancak O(log n + k)sadece alt dizinin alınmasıdır - O(1)başlangıç ​​ve bitiş noktaları göz önüne alındığında dizi erişimine eşdeğerdir . Maksimum değeri bulmak için yine de geçmeniz gerekir.
Izkata

0

Seyrek tablo adı verilen veri yapısını kullanarak sorgu başına O (1) elde edebilirsiniz (O (n log n) yapısıyla). Her 2 güç için, bu uzunluktaki her segment için maksimum tasarruf yapalım. Şimdi segment [l, r) verildiğinde, uygun k için [l + 2 ^ k) ve [r-2 ^ k, r) üzerinde maksimumlar elde edersiniz. Üst üste biniyorlar ama sorun değil

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.