การดึงค่าสูงสุดจากช่วงในอาร์เรย์ที่ไม่เรียงลำดับ


9

ฉันมีอาร์เรย์ไม่ได้เรียงลำดับ ฉันมีข้อความค้นหาที่ให้ช่วงและค่าสูงสุดจากช่วงนั้นต้องส่งคืน ตัวอย่างเช่น:

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

ฉันจะสร้างอัลกอริทึมหรือโครงสร้างข้อมูลใดเพื่อดึงค่าสูงสุดอย่างรวดเร็วจากช่วงใดก็ได้ (มีข้อความค้นหาจำนวนมาก)

แก้ไข: นี่เป็นปัญหาจริงที่เรียบง่าย ฉันสามารถมีขนาดอาร์เรย์ที่มีขนาดใหญ่ถึง 100,000 และจำนวนการสืบค้นสูงสุดถึง 100,000 ดังนั้นฉันต้องมีการประมวลผลล่วงหน้าซึ่งจะช่วยให้การตอบแบบสอบถามรวดเร็วขึ้น


5
ทำไมมันถึงไม่เรียงกัน? ปัญหาเป็นเรื่องไม่สำคัญหากมีการเรียงลำดับดังนั้นวิธีที่ชัดเจนคือการจัดเรียง

1
@delnan หากไม่มีกลไกพิเศษคุณจะสูญเสียการติดตามว่าคุณค่าใดที่อยู่ในช่วงของการสอบถาม ...
Thijs van Dien

ระบุปัญหาทั้งหมดของคุณ หากความรู้นี้ (หรือข้อมูลอื่น ๆ ) มีความสำคัญเราต้องรู้ถึงปัจจัยที่มีผลต่อการแก้ปัญหา

1
ฉันขาดอะไรบางอย่างหรือนี่เป็นเพียงแค่การเยี่ยมชมรายการ 2 ถึง 6 และค้นหาคุณค่าสูงสุดขององค์ประกอบเหล่านั้นหรือไม่
Blrfl

@Blrfl: ผมไม่คิดว่าคุณกำลังขาดหายไปอะไรยกเว้นอาจจะเป็นส่วนหนึ่งเกี่ยวกับหลายคำสั่ง ไม่ชัดเจนว่ามีจุดใดในการสร้างโครงสร้างที่ทำให้การสืบค้นมีราคาถูกกว่าการค้นหาตามลำดับ (แม้ว่าจะไม่มีประเด็นในการถามคำถามที่นี่หากไม่ใช่ความคิด)
Mike Sherrill 'Cat Recall'

คำตอบ:


14

ฉันคิดว่าคุณสามารถสร้างต้นไม้ไบนารีบางชนิดที่แต่ละโหนดแสดงถึงค่าสูงสุดของลูก:

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

จากนั้นคุณจะต้องหาวิธีในการกำหนดว่าโหนดใดที่คุณต้องการตรวจสอบน้อยที่สุดเพื่อหาค่าสูงสุดในช่วงที่สอบถาม ในตัวอย่างนี้จะได้รับค่าสูงสุดอยู่ในช่วงดัชนี[2, 6](รวม) คุณจะต้องแทนmax(45, 78, 4) max(9, 45, 78, 2, 4)เมื่อต้นไม้โตขึ้นกำไรจะเพิ่มขึ้น


1
เพื่อให้การทำงานมีข้อมูลหายไปจากแผนผังตัวอย่างของคุณ: แต่ละโหนดภายในต้องมีทั้งจำนวนสูงสุดและจำนวนโหนดย่อยทั้งหมดที่มี ไม่เช่นนั้นการค้นหาจะไม่มีทางรู้ได้ว่า (ตัวอย่าง) ไม่จำเป็นต้องดูลูกทั้งหมดของ78(และข้าม2) เพราะทุกคนรู้ว่าดัชนี6อยู่ในทรีย่อย
Izkata

มิฉะนั้น +1 เพราะฉันคิดว่ามันค่อนข้างสร้างสรรค์
Izkata

+1: นี่เป็นเทคนิคที่มีประสิทธิภาพสำหรับการตอบแบบสอบถามเกี่ยวกับช่วงย่อยของรายการในบันทึก (N) เวลาที่สามารถใช้งานข้อมูลที่โหนดรากสามารถคำนวณได้ในเวลาคงที่จากข้อมูลที่เด็ก ๆ
วินไคลน์

ความคิดนี้ยอดเยี่ยม มันให้เวลาแบบสอบถาม O (logn) ฉันคิดว่า @Izkata ก็เป็นจุดที่ดีเช่นกัน เราสามารถเพิ่มโหนดต้นไม้ด้วยข้อมูลเกี่ยวกับช่วงซ้ายและขวาที่ครอบคลุม ในช่วงที่กำหนดมันรู้วิธีแบ่งปัญหาออกเป็นสองส่วน ฉลาดหลักแหลมข้อมูลทั้งหมดจะถูกเก็บไว้ที่ระดับลีฟ ดังนั้นจึงต้องใช้พื้นที่ 2 * N ซึ่งก็คือ O (N) ในการจัดเก็บ ฉันไม่รู้ว่าต้นไม้กลุ่มคืออะไร แต่นี่เป็นแนวคิดที่อยู่เบื้องหลังต้นไม้กลุ่มหรือไม่
Kay

และในแง่ของการประมวลผลล่วงหน้าใช้เวลา O (n) ในการสร้างต้นไม้
Kay

2

เพื่อเติมเต็มคำตอบของ ngoaho91

วิธีที่ดีที่สุดในการแก้ปัญหานี้คือการใช้โครงสร้างข้อมูลกลุ่มต้นไม้ สิ่งนี้ช่วยให้คุณสามารถตอบแบบสอบถามใน O (log (n)) ซึ่งหมายถึงความซับซ้อนโดยรวมของอัลกอริทึมของคุณจะเป็น O (Q logn) โดยที่ Q คือจำนวนคิวรี หากคุณใช้อัลกอริทึมไร้เดียงสาความซับซ้อนทั้งหมดจะเป็น O (Q n) ซึ่งช้ากว่าอย่างเห็นได้ชัด

อย่างไรก็ตามมีข้อเสียเปรียบของการใช้เซกเมนต์ทรี ใช้หน่วยความจำมาก แต่หลายครั้งที่คุณสนใจเรื่องความจำน้อยกว่าความเร็ว

ฉันจะอธิบายสั้น ๆ อัลกอริทึมที่ใช้โดย DS นี้:

ทรีเซ็กเมนต์เป็นเพียงกรณีพิเศษของทรีการค้นหาแบบไบนารีซึ่งทุกโหนดเก็บค่าของช่วงที่ได้รับมอบหมาย โหนดรูทถูกกำหนดช่วง [0, n] เด็กด้านซ้ายถูกกำหนดช่วง [0, (0 + n) / 2] และเด็กด้านขวา [(0 + n) / 2 + 1, n] วิธีนี้ต้นไม้จะถูกสร้างขึ้น

สร้างต้นไม้ :

/*
    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.
    }
}

ต้นไม้แบบสอบถาม

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.
}

หากคุณต้องการคำอธิบายเพิ่มเติมเพียงแจ้งให้เราทราบ

BTW, Segment Tree ยังรองรับการอัพเดทองค์ประกอบเดี่ยวหรือช่วงขององค์ประกอบใน O (log n)


ความซับซ้อนของการเติมต้นไม้คืออะไร?
ปีเตอร์ B

คุณต้องผ่านองค์ประกอบทั้งหมดและจะใช้เวลาO(log(n))ในการเพิ่มองค์ประกอบแต่ละรายการลงในต้นไม้ ดังนั้นความซับซ้อนโดยรวมคือO(nlog(n))
Andrés

1

อัลกอริทึมที่ดีที่สุดจะอยู่ในช่วงเวลา O (n) ดังต่อไปนี้ให้เริ่มต้นแล้วสิ้นสุดคือดัชนีของขอบเขตของช่วง

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
-1 สำหรับเพียงทำซ้ำอัลกอริทึมที่ OP พยายามปรับปรุง
วินไคลน์

1
+1 สำหรับการโพสต์วิธีแก้ไขปัญหาตามที่ระบุไว้ นี้จริงๆเป็นวิธีเดียวที่จะทำมันได้ถ้าคุณมีอาร์เรย์และไม่ทราบว่าสิ่งที่ขอบเขตเป็นไปได้เบื้องต้น (ถึงแม้ว่าผมจะเริ่มต้นmaxไปa[i]และเริ่มต้นforห่วงi+1.)
Blrfl

@ kevincline มันไม่เพียงแค่คืนค่า - มันยังบอกว่า "ใช่แล้วคุณมีอัลกอริธึมที่ดีที่สุดสำหรับงานนี้" พร้อมการปรับปรุงเล็กน้อย (ข้ามไปstartหยุดที่end) และฉันยอมรับว่านี่เป็นสิ่งที่ดีที่สุดสำหรับการค้นหาแบบครั้งเดียว @ คำตอบของ ThijsvanDien ดีกว่าก็ต่อเมื่อการค้นหาเกิดขึ้นหลายครั้งเนื่องจากการตั้งค่าเริ่มต้นใช้เวลานานกว่า
Izkata

ได้รับในขณะที่โพสต์คำตอบคำถามนี้ไม่ได้รวมการแก้ไขยืนยันว่าเขาจะทำแบบสอบถามจำนวนมากมากกว่าข้อมูลเดียวกัน
Izkata

1

โซลูชันฐานต้นไม้แบบทวิภาค / ส่วนต้นไม้นั้นชี้ไปในทิศทางที่ถูกต้อง หนึ่งอาจคัดค้านว่าพวกเขาต้องการหน่วยความจำเพิ่มเติมจำนวนมาก มีสองวิธีแก้ไขปัญหาเหล่านี้:

  1. ใช้โครงสร้างข้อมูลโดยนัยแทนต้นไม้ไบนารี
  2. ใช้ต้นไม้ M-ary แทนต้นไม้ไบนารี

จุดแรกคือเนื่องจากต้นไม้มีโครงสร้างสูงคุณสามารถใช้โครงสร้างคล้ายกองเพื่อกำหนดต้นไม้โดยไม่ต้องแทนต้นไม้ด้วยโหนดตัวชี้ซ้ายและขวาช่วงเวลา ฯลฯ ที่ช่วยประหยัดหน่วยความจำจำนวนมากด้วย ไม่มีการเข้าชมที่มีประสิทธิภาพ - คุณต้องทำการคำนวณทางคณิตศาสตร์อีกเล็กน้อย

จุดที่สองคือว่าในการทำงานอีกเล็กน้อยในระหว่างการประเมินผลคุณสามารถใช้ต้นไม้ M-ary มากกว่าต้นไม้ไบนารี ตัวอย่างเช่นถ้าคุณใช้ทรี 3-ary คุณจะคำนวณองค์ประกอบได้สูงสุด 3 รายการต่อครั้งจากนั้นเลือกองค์ประกอบ 9 รายการต่อครั้งจากนั้นเลือก 27 รายการและพื้นที่เก็บข้อมูลเพิ่มเติมที่จำเป็นคือ N / (M-1) - พิสูจน์โดยใช้สูตรชุดเรขาคณิต ตัวอย่างเช่นถ้าคุณเลือก M = 11 คุณจะต้องใช้หน่วยเก็บข้อมูลของวิธีต้นไม้แบบ 1 / 10th

คุณสามารถตรวจสอบว่าการปรับใช้ที่ไร้เดียงสาและปรับให้เหมาะสมเหล่านี้ใน Python ให้ผลลัพธ์เหมือนกัน:

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

เมื่อเทียบกับ

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

ลองโครงสร้างข้อมูล "ส่วนต้นไม้"
มี 2 ​​ขั้นตอน
build_tree () O (n)
แบบสอบถาม (int ขั้นต่ำ int สูงสุด) O (nlogn)

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

แก้ไข:

พวกคุณไม่อ่านวิกิที่ฉันส่งไป!

อัลกอริทึมนี้คือ:
- คุณสำรวจอาร์เรย์ 1 ครั้งเพื่อสร้างทรี O (n)
- ถัดไป 100000000+ ครั้งที่คุณต้องการทราบจำนวนสูงสุดของส่วนใด ๆ ของอาร์เรย์เพียงแค่เรียกใช้ฟังก์ชันการสืบค้น O (logn) สำหรับทุกข้อความค้นหา
- c ++ ใช้ที่นี่ geeksforgeeks.org/segment-tree-set-1-range-minimum-query/
อัลกอริทึมเก่าคือ:
ทุกแบบสอบถามเพียงสำรวจพื้นที่ที่เลือกและค้นหา

ดังนั้นถ้าคุณจะใช้อัลกอริทึมนี้ในการประมวลผลหนึ่งครั้งตกลงมันช้ากว่าแบบเก่า แต่ถ้าคุณจะประมวลผลคำค้นหาจำนวนมาก (พันล้าน) มันมีประสิทธิภาพมากที่คุณสามารถสร้างไฟล์ข้อความเช่นนี้สำหรับการทดสอบ

บรรทัดที่ 1: 50,000 หมายเลขสุ่มจาก 0-1000000 แยกโดย '(ช่องว่าง)'
บรรทัด 2: 2 สุ่มตัวเลขจาก 1 ถึง 50,000 แยกโดย '(ช่องว่าง)' (เป็นข้อความค้นหา)
...
บรรทัด 200000: กดไลค์ 2 มันเป็นข้อความสุ่มด้วย

นี่คือปัญหาตัวอย่างขออภัย แต่นี่เป็น vietnamese ใน
http://vn.spoj.com/problems/NKLINEUP/
หากคุณแก้ปัญหาด้วยวิธีเก่าคุณจะไม่ผ่าน


3
ฉันไม่คิดว่ามันเกี่ยวข้อง ต้นไม้ช่วงเวลาถือช่วงเวลาไม่ใช่จำนวนเต็มและการดำเนินการที่พวกเขาอนุญาตให้ดูไม่เหมือนสิ่งที่ OP ขอ แน่นอนคุณสามารถสร้างช่วงเวลาที่เป็นไปได้ทั้งหมดและเก็บไว้ในทรีช่วงเวลา แต่ (1) มีจำนวนมากแทนพวกเขาดังนั้นนี่ไม่ได้ปรับขนาดและ (2) การดำเนินการยังไม่เหมือน OP ถามหา

ความผิดพลาดของฉันฉันหมายถึงต้นไม้เซ็กเมนต์ไม่ใช่ต้นไม้ช่วงเวลา
ngoaho91

น่าสนใจฉันคิดว่าฉันไม่เคยเจอต้นไม้ต้นนี้! IIUC สิ่งนี้ยังต้องการการจัดเก็บช่วงเวลาที่เป็นไปได้ทั้งหมด ฉันคิดว่ามี O (n ^ 2) ของสิ่งเหล่านี้ซึ่งค่อนข้างแพง (นอกจากนี้ไม่ควรใช้คำค้นหา O (log n + k) สำหรับผลลัพธ์ k

ใช่โมฆะ build_tree () จะต้องเดินทางข้ามอาร์เรย์ และเก็บค่าสูงสุด (หรือต่ำสุด) สำหรับทุกโหนด แต่ในหลายกรณีค่าใช้จ่ายหน่วยความจำไม่สำคัญกว่าความเร็ว
ngoaho91

2
ฉันไม่สามารถจินตนาการได้ว่านี่จะเร็วกว่าการO(n)ค้นหาอาเรย์ธรรมดาดังที่อธิบายไว้ในคำตอบของ tarun_telang สัญชาตญาณแรกคือว่าO(log n + k)เร็วกว่าO(n)แต่O(log n + k)เป็นเพียงการเรียกคืนของอาเรย์ย่อย - เทียบเท่ากับO(1)การเข้าถึงอาเรย์ที่กำหนดจุดเริ่มต้นและจุดสิ้นสุด คุณยังคงต้องทำการสำรวจเพื่อหาค่าสูงสุด
Izkata

0

คุณสามารถบรรลุ O (1) ต่อแบบสอบถาม (ด้วยการก่อสร้าง O (n log n)) โดยใช้โครงสร้างข้อมูลที่เรียกว่าตารางกระจัดกระจาย สำหรับกำลังสองแต่ละครั้งขอประหยัดสูงสุดสำหรับแต่ละเซ็กเมนต์ของความยาวนี้ ตอนนี้ที่ได้รับส่วน [l, r) คุณจะได้รับสูงสุดสูงสุดใน [l + 2 ^ k) และ [r-2 ^ k, r) สำหรับ k ที่เหมาะสม พวกมันซ้อนทับกัน แต่ก็โอเค

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.