วิธีการค้นหาองค์ประกอบที่ใหญ่ที่สุด kth ในอาร์เรย์ที่มีความยาวไม่เรียงลำดับ n ใน O (n)?


220

ฉันเชื่อว่ามีวิธีการหาองค์ประกอบที่ใหญ่ที่สุด kth ในอาร์เรย์ที่มีความยาว n ใน O (n) หรืออาจเป็น "คาดหวัง" O (n) หรือบางสิ่งบางอย่าง เราจะทำสิ่งนี้ได้อย่างไร


49
อย่างไรก็ตามอัลกอริธึมที่อธิบายไว้ที่นี่จะกลายเป็น O (n ^ 2) หรือ O (n log n) เมื่อ k == n นั่นคือฉันไม่คิดว่าหนึ่งเดียวของพวกเขาคือ O (n) สำหรับค่าทั้งหมดของ k ฉัน modded ลงเพื่อชี้เรื่องนี้ แต่คิดว่าคุณควรรู้อยู่ดี
Kirk Strauser

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

1
ฉันถูกถามคำถามนี้ในการสัมภาษณ์ amazon เป็นกรณีทั่วไปของการค้นหาองค์ประกอบที่ยิ่งใหญ่ที่สุดที่สอง โดยวิธีที่ผู้สัมภาษณ์นำไปสู่การสัมภาษณ์ฉันไม่ได้ถามว่าฉันจะทำลายอาร์เรย์เดิมได้หรือไม่ (เช่นการเรียงลำดับ) ดังนั้นฉันจึงได้คำตอบที่ซับซ้อน
Sambatyon

4
นี่คือคำถามที่ 9 ในคอลัมน์ 11 (การเรียงลำดับ) ของการเขียนโปรแกรม Pearls โดย Jon Bentley
Qiang Xu

3
@KirkStrauser: ถ้า k == n หรือ k == n-1 มันจะกลายเป็นเรื่องไม่สำคัญ เราสามารถรับได้สูงสุดหรือสูงสุดที่ 2 ในการสำรวจเส้นทางเดียว ดังนั้นอัลกอริทึมที่มีให้ที่นี่จะถูกใช้จริงสำหรับค่าของ k ซึ่งไม่ได้เป็นของ {1,2, n-1, n}
Aditya Joshee

คำตอบ:


173

นี้เรียกว่าการหาสถิติการสั่งซื้อที่ k มีอัลกอริทึมแบบสุ่มที่ง่ายมาก (เรียกว่าquickselect ) โดยใช้O(n)เวลาเฉลี่ยเวลาO(n^2)กรณีที่เลวร้ายที่สุดและอัลกอริทึมที่ไม่ได้สุ่มที่ซับซ้อน (เรียกว่าintroselect ) ซึ่งใช้O(n)เวลากรณีที่เลวร้ายที่สุด มีข้อมูลบางอย่างเกี่ยวกับWikipediaแต่มันไม่ค่อยดีนัก

ทุกสิ่งที่คุณจำเป็นต้องอยู่ในสไลด์ PowerPoint เหล่านี้ เพียงเพื่อแยกอัลกอริทึมพื้นฐานของO(n)อัลกอริทึมกรณีที่เลวร้ายที่สุด (introselect):

Select(A,n,i):
    Divide input into ⌈n/5⌉ groups of size 5.

    /* Partition on median-of-medians */
    medians = array of each group’s median.
    pivot = Select(medians, ⌈n/5⌉, ⌈n/10⌉)
    Left Array L and Right Array G = partition(A, pivot)

    /* Find ith element in L, pivot, or G */
    k = |L| + 1
    If i = k, return pivot
    If i < k, return Select(L, k-1, i)
    If i > k, return Select(G, n-k, i-k)

นอกจากนี้ยังมีรายละเอียดที่ดีมากในหนังสือ Introduction to Algorithms โดย Cormen et al


6
ขอบคุณสำหรับสไลด์
Kshitij Banerjee

5
ทำไมต้องทำงานในขนาด 5 ทำไมมันทำงานกับขนาด 3 ไม่ได้?
Joffrey Baratheon

11
@eladv ลิงก์สไลด์ใช้งานไม่ได้ :(
Misha Moroshko

7
@eladv โปรดแก้ไขลิงก์ที่ใช้งานไม่ได้
maxx777

1
@MishaMoroshko ลิงก์ได้รับการแก้ไข
alfasin

118

หากคุณต้องการO(n)อัลกอริธึมที่แท้จริงซึ่งตรงข้ามกับO(kn)หรืออะไรทำนองนั้นคุณควรใช้ quickselect (โดยทั่วไปจะเป็น quicksort ที่คุณทิ้งพาร์ติชันที่คุณไม่สนใจ) ศาสตราจารย์ของฉันมีการเขียนที่ยอดเยี่ยมพร้อมด้วยการวิเคราะห์รันไทม์: ( อ้างอิง )

อัลกอริทึม QuickSelect ค้นหาองค์ประกอบที่เล็กที่สุดใน k-th ของอาร์เรย์ที่ไม่เรียงลำดับnอย่างรวดเร็ว มันเป็นอัลกอริทึมแบบสุ่มดังนั้นเราจึงคำนวณเวลาทำงานที่เลวร้ายที่สุดที่คาดไว้

นี่คืออัลกอริทึม

QuickSelect(A, k)
  let r be chosen uniformly at random in the range 1 to length(A)
  let pivot = A[r]
  let A1, A2 be new arrays
  # split into a pile A1 of small elements and A2 of big elements
  for i = 1 to n
    if A[i] < pivot then
      append A[i] to A1
    else if A[i] > pivot then
      append A[i] to A2
    else
      # do nothing
  end for
  if k <= length(A1):
    # it's in the pile of small elements
    return QuickSelect(A1, k)
  else if k > length(A) - length(A2)
    # it's in the pile of big elements
    return QuickSelect(A2, k - (length(A) - length(A2))
  else
    # it's equal to the pivot
    return pivot

เวลาทำงานของอัลกอริทึมนี้คืออะไร หากคู่ปรับพลิกเหรียญสำหรับเราเราอาจพบว่าเดือยเป็นองค์ประกอบที่ใหญ่ที่สุดkเสมอและเป็น 1 เสมอทำให้เวลาในการวิ่ง

T(n) = Theta(n) + T(n-1) = Theta(n2)

แต่ถ้าตัวเลือกเป็นแบบสุ่มแน่นอนเวลาทำงานที่คาดหวังจะได้รับจาก

T(n) <= Theta(n) + (1/n) ∑i=1 to nT(max(i, n-i-1))

ที่เราจะทำให้สันนิษฐานที่ไม่เหมาะสมอย่างสิ้นเชิงที่เรียกซ้ำเสมอในดินแดนที่มีขนาดใหญ่ของหรือA1A2

Let 's เดาว่าสำหรับบางคนT(n) <= an aจากนั้นเราจะได้รับ

T(n) 
 <= cn + (1/n) ∑i=1 to nT(max(i-1, n-i))
 = cn + (1/n) ∑i=1 to floor(n/2) T(n-i) + (1/n) ∑i=floor(n/2)+1 to n T(i)
 <= cn + 2 (1/n) ∑i=floor(n/2) to n T(i)
 <= cn + 2 (1/n) ∑i=floor(n/2) to n ai

และตอนนี้เราต้องได้ผลบวกที่น่ากลัวทางด้านขวาของเครื่องหมายบวกเพื่อดูดซับcnทางซ้าย ถ้าเราเพียงแค่ผูกไว้ว่ามันเป็นที่เราได้รับประมาณ แต่ตอนนี้มีขนาดใหญ่เกินไป - มีห้องพักที่จะบีบในการพิเศษ ดังนั้นลองขยายผลรวมโดยใช้สูตรชุดเลขคณิต:2(1/n) ∑i=n/2 to n an2(1/n)(n/2)an = ancn

i=floor(n/2) to n i  
 = ∑i=1 to n i - ∑i=1 to floor(n/2) i  
 = n(n+1)/2 - floor(n/2)(floor(n/2)+1)/2  
 <= n2/2 - (n/4)2/2  
 = (15/32)n2

ที่เราใช้ประโยชน์จาก n เป็น "มากพอ" เพื่อแทนที่น่าเกลียดfloor(n/2)ปัจจัยกับทำความสะอาดมาก n/4(และขนาดเล็ก) ตอนนี้เราสามารถดำเนินการต่อด้วย

cn + 2 (1/n) ∑i=floor(n/2) to n ai,
 <= cn + (2a/n) (15/32) n2
 = n (c + (15/16)a)
 <= an

a > 16cหาก

T(n) = O(n)นี้จะช่วยให้ มันเป็นอย่างชัดเจนเพื่อให้เราได้รับOmega(n)T(n) = Theta(n)


12
Quickselect เป็นเพียง O (n) ในกรณีโดยเฉลี่ย อัลกอริทึมมัธยฐานของมัธยฐานสามารถใช้ในการแก้ปัญหาในเวลา O (n) ในกรณีที่เลวร้ายที่สุด
John Kurlak

ความหมายของk > length(A) - length(A2)อะไร
WoooHaaaa

นี่ไม่ใช่ O (n) คุณกำลังเรียกใช้ฟังก์ชันอีกครั้งในฐานะ recursive, T (n) มี O (n) อยู่ในฟังก์ชัน recursive T (n) ดังนั้นโดยไม่ต้องคิดความซับซ้อนโดยรวมจะมากกว่า O (n)
user1735921

3
@MrROY ระบุว่าเรา splitted Aเข้าไปในA1และรอบหมุนเรารู้ว่าA2 length(A) == length(A1)+length(A2)+1ดังนั้นk > length(A)-length(A2)จะเทียบเท่ากับk > length(A1)+1ที่เป็นจริงเมื่อเป็นหนึ่งในk A2
Filipe Gonçalves

@ FilipeGonçalvesใช่ถ้าไม่มีองค์ประกอบที่ซ้ำกันในเดือย len (A1) + len (A2) + K- ซ้ำ = len (A)
d1val

16

Google ฉบับย่อเกี่ยวกับเรื่องนั้น ('องค์ประกอบที่ใหญ่ที่สุดของ kth') ส่งคืนสิ่งนี้: http://discuss.joelonsoftware.com/default.asp?interview.11.509587.17

"Make one pass through tracking the three largest values so far." 

(มันเป็นเฉพาะสำหรับ 3d ที่ใหญ่ที่สุด)

และคำตอบนี้:

Build a heap/priority queue.  O(n)
Pop top element.  O(log n)
Pop top element.  O(log n)
Pop top element.  O(log n)

Total = O(n) + 3 O(log n) = O(n)

15
ดีมันจริง O (n) + O (k log n) ซึ่งไม่ลดลงสำหรับค่าที่สำคัญของ K
จิมมี่

2
แต่การค้นหาจุดแทรกในรายการที่ลิงก์ซ้ำกันคือ O (k)
Kirk Strauser

1
และถ้า k ได้รับการแก้ไข O (k) = O (1)
Tyler McHenry

1
@warren: Big-O ใกล้เคียง แต่คุณมักจะคร่ำครึ Quicksort เป็นจริง O (n ^ 2) เช่นเนื่องจากเป็นกรณีที่เลวร้ายที่สุด อันนี้คือ O (n + k log n)
Claudiu

1
คุณไม่สามารถถือว่า k เป็นค่าคงที่ได้ เป็นไปได้ที่ k = n ซึ่งในกรณีนี้ความซับซ้อนของเวลาคือ O (nlogn)
sabbir

11

คุณชอบ quicksort เลือกองค์ประกอบโดยการสุ่มและผลักทุกอย่างไม่ว่าสูงหรือต่ำ ณ จุดนี้คุณจะรู้ว่าองค์ประกอบใดที่คุณเลือกจริงและถ้ามันเป็นองค์ประกอบ kth ที่คุณทำเสร็จมิฉะนั้นคุณจะทำซ้ำกับถังขยะ (สูงกว่าหรือต่ำกว่า) ว่าองค์ประกอบ kth จะตกหลุมพูดทางสถิติเวลา มันจะใช้เวลาในการหาองค์ประกอบ kth เติบโตขึ้นด้วย n, O (n)


2
นี่คือสิ่งที่เลือกอย่างรวดเร็วคือ FWIW
rogerdpack

6

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

ฉันตอบจดหมายคำถามของคุณ :)


2
ไม่จริงในทุกกรณี ฉันได้ใช้ค่ามัธยฐานของค่ามัธยฐานและเปรียบเทียบกับวิธีการเรียงลำดับในตัวใน. NET และโซลูชันที่กำหนดเองนั้นทำงานได้เร็วขึ้นตามลำดับความสำคัญ ตอนนี้คำถามที่แท้จริงคือ: ไม่สำคัญกับคุณในสถานการณ์ที่กำหนด การเขียนและแก้ไขจุดบกพร่องของรหัส 100 บรรทัดเมื่อเปรียบเทียบกับสายการบินหนึ่งจ่ายออกเฉพาะในกรณีที่รหัสจะถูกดำเนินการหลายครั้งที่ผู้ใช้เริ่มสังเกตเห็นความแตกต่างในเวลาทำงานและรู้สึกไม่สบายรอการดำเนินการให้เสร็จสมบูรณ์
Zoran Horvat

5

ไลบรารีมาตรฐาน C ++ มีการเรียกใช้ฟังก์ชันเกือบทั้งหมดnth_elementแม้ว่ามันจะแก้ไขข้อมูลของคุณ มันคาดว่าจะใช้เวลาเชิงเส้น O (N) และมันก็จะเรียงลำดับบางส่วน

const int N = ...;
double a[N];
// ... 
const int m = ...; // m < N
nth_element (a, a + m, a + N);
// a[m] contains the mth element in a

1
ไม่มันมีรันไทม์ O (n) โดยเฉลี่ยที่คาดไว้ ตัวอย่างเช่น quicksort คือ O (nlogn) โดยเฉลี่ยกับกรณีที่แย่ที่สุดของ O (n ^ 2) ว้าวมีบางอย่างผิดพลาดเกิดขึ้นจริง!
Kirk Strauser

5
ไม่ไม่มีอะไรผิดจริงกับคำตอบนี้ มันใช้งานได้และมาตรฐาน C ++ ต้องการเวลาทำงานแบบเชิงเส้นที่คาดไว้
David Nehme

ฉันถูกถามในการสัมภาษณ์เพื่อสมมติว่ามีพื้นที่ว่างของ O (k) และ 'n' นั้นใหญ่มาก ฉันไม่สามารถบอกวิธีแก้ปัญหา O (n) เขาได้เนื่องจากฉันคิดว่า nth_element จะต้องใช้พื้นที่ o (n) ฉันผิดหรือเปล่าไม่ได้อัลกอริธึมพื้นฐานเป็นแบบรวดเร็วสำหรับ nth_element
Manish Baphna

4

แม้ว่าจะไม่ค่อยแน่ใจเกี่ยวกับความซับซ้อนของ O (n) แต่มันจะต้องแน่ใจว่าอยู่ระหว่าง O (n) และ nLog (n) นอกจากนี้โปรดแน่ใจว่าได้ใกล้ชิดกับ O (n) มากกว่า nLog (n) ฟังก์ชั่นเขียนใน Java

public int quickSelect(ArrayList<Integer>list, int nthSmallest){
    //Choose random number in range of 0 to array length
    Random random =  new Random();
    //This will give random number which is not greater than length - 1
    int pivotIndex = random.nextInt(list.size() - 1); 

    int pivot = list.get(pivotIndex);

    ArrayList<Integer> smallerNumberList = new ArrayList<Integer>();
    ArrayList<Integer> greaterNumberList = new ArrayList<Integer>();

    //Split list into two. 
    //Value smaller than pivot should go to smallerNumberList
    //Value greater than pivot should go to greaterNumberList
    //Do nothing for value which is equal to pivot
    for(int i=0; i<list.size(); i++){
        if(list.get(i)<pivot){
            smallerNumberList.add(list.get(i));
        }
        else if(list.get(i)>pivot){
            greaterNumberList.add(list.get(i));
        }
        else{
            //Do nothing
        }
    }

    //If smallerNumberList size is greater than nthSmallest value, nthSmallest number must be in this list 
    if(nthSmallest < smallerNumberList.size()){
        return quickSelect(smallerNumberList, nthSmallest);
    }
    //If nthSmallest is greater than [ list.size() - greaterNumberList.size() ], nthSmallest number must be in this list
    //The step is bit tricky. If confusing, please see the above loop once again for clarification.
    else if(nthSmallest > (list.size() - greaterNumberList.size())){
        //nthSmallest will have to be changed here. [ list.size() - greaterNumberList.size() ] elements are already in 
        //smallerNumberList
        nthSmallest = nthSmallest - (list.size() - greaterNumberList.size());
        return quickSelect(greaterNumberList,nthSmallest);
    }
    else{
        return pivot;
    }
}

การเข้ารหัสที่ดี +1 แต่ไม่จำเป็นต้องใช้พื้นที่เพิ่มเติม
Hengameh

4

ฉันใช้การค้นหาขั้นต่ำ kth ในองค์ประกอบที่ไม่เรียงลำดับโดยใช้การเขียนโปรแกรมแบบไดนามิกวิธีการทัวร์นาเมนต์โดยเฉพาะ เวลาดำเนินการคือ O (n + klog (n)) กลไกที่ใช้ถูกระบุว่าเป็นหนึ่งในวิธีการในหน้า Wikipedia เกี่ยวกับอัลกอริธึมการเลือก (ตามที่ระบุในการโพสต์ด้านบน) คุณสามารถอ่านเกี่ยวกับขั้นตอนวิธีการและยังพบรหัส (Java) บนหน้าบล็อกของฉันหา Kth ขั้นต่ำ นอกจากนี้ตรรกะสามารถทำการเรียงลำดับบางส่วนของรายการ - ส่งคืน K นาทีแรก (หรือสูงสุด) ในเวลา O (klog (n))

แม้ว่าโค้ดที่ให้ผลลัพธ์ขั้นต่ำ kth จะสามารถใช้ตรรกะที่คล้ายกันเพื่อค้นหา kth สูงสุดใน O (klog (n)) โดยไม่สนใจงานก่อนทำเพื่อสร้างทรีทัวร์นาเมนต์


3

คุณสามารถทำได้ใน O (n + kn) = O (n) (สำหรับค่าคงที่ k) สำหรับเวลาและ O (k) สำหรับช่องว่างโดยติดตามองค์ประกอบที่ใหญ่ที่สุดที่คุณเคยเห็น

สำหรับแต่ละองค์ประกอบในอาร์เรย์คุณสามารถสแกนรายการ k ที่ใหญ่ที่สุดและแทนที่องค์ประกอบที่เล็กที่สุดด้วยองค์ประกอบใหม่ถ้ามันมีขนาดใหญ่กว่า

วิธีแก้ปัญหาฮีปลำดับความสำคัญของวอร์เรนนั้นมีความสำคัญกว่า


3
นี่จะเป็นกรณีที่เลวร้ายที่สุดของ O (n ^ 2) ที่คุณจะขอรายการที่เล็กที่สุด
Elie

2
"รายการที่เล็กที่สุด" หมายถึง k = n ดังนั้น k จึงไม่คงที่อีกต่อไป
Tyler McHenry

หรืออาจเก็บ heap (หรือสลับกลับเป็น heap หรือต้นไม้ที่สมดุล) ของ k ที่ใหญ่ที่สุดที่คุณเคยเห็นมาO(n log k)... ยังคงเสื่อมสภาพไปที่ O (nlogn) ในกรณีของ k ขนาดใหญ่ ฉันคิดว่ามันจะทำงานได้ดีสำหรับค่า k เล็กน้อย แต่ ... อาจเร็วกว่าอัลกอริธึมอื่น ๆ ที่กล่าวถึงที่นี่ [???]
rogerdpack

3

เซ็กซี่อย่างรวดเร็วเลือกใน Python

def quickselect(arr, k):
    '''
     k = 1 returns first element in ascending order.
     can be easily modified to return first element in descending order
    '''

    r = random.randrange(0, len(arr))

    a1 = [i for i in arr if i < arr[r]] '''partition'''
    a2 = [i for i in arr if i > arr[r]]

    if k <= len(a1):
        return quickselect(a1, k)
    elif k > len(arr)-len(a2):
        return quickselect(a2, k - (len(arr) - len(a2)))
    else:
        return arr[r]

วิธีแก้ปัญหาที่ดียกเว้นว่าสิ่งนี้จะส่งกลับองค์ประกอบที่เล็กที่สุด kth ในรายการไม่ได้เรียง การย้อนกลับตัวดำเนินการเปรียบเทียบในรายการความเข้าใจa1 = [i for i in arr if i > arr[r]]และa2 = [i for i in arr if i < arr[r]]จะส่งคืนองค์ประกอบที่ใหญ่ที่สุด kth
gumption

จากเกณฑ์มาตรฐานขนาดเล็กแม้ในอาร์เรย์ขนาดใหญ่มันจะเร็วกว่าในการจัดเรียง (กับnumpy.sortสำหรับnumpy arrayหรือsortedสำหรับรายการ) กว่าที่จะใช้คู่มือการใช้งานนี้
Næreen

2

ค้นหาค่ามัธยฐานของอาร์เรย์ในเวลาเชิงเส้นจากนั้นใช้ขั้นตอนการแบ่งพาร์ทิชันเหมือนกับใน quicksort เพื่อแบ่งอาร์เรย์ในสองส่วนค่าทางด้านซ้ายของค่ามัธยฐานน้อยกว่า (<) มากกว่าค่ามัธยฐานและทางขวามากกว่า (>) ค่ามัธยฐาน , ที่สามารถทำได้ในเวลา lineat, ตอนนี้, ไปที่ส่วนของอาร์เรย์ที่อิลิเมนต์ kth อยู่, ตอนนี้การกำเริบกลายเป็น: T (n) = T (n / 2) + cn ซึ่งให้ O (n)


ไม่จำเป็นต้องหาค่ามัธยฐาน วิธีการของคุณก็ยังคงดีอยู่
Hengameh

2
และคุณจะพบค่ามัธยฐานในเวลาเชิงเส้นได้อย่างไรฉันกล้าถาม? ... :)
rogerdpack

2

ด้านล่างนี้คือลิงก์สำหรับการนำไปใช้งานแบบเต็มรูปแบบโดยมีคำอธิบายที่กว้างขวางว่าอัลกอริทึมสำหรับการค้นหาองค์ประกอบ Kth ในอัลกอริทึมที่ไม่เรียงลำดับทำงานอย่างไร แนวคิดพื้นฐานคือการแบ่งอาร์เรย์เช่นเดียวกับใน QuickSort แต่เพื่อหลีกเลี่ยงกรณีที่รุนแรง (เช่นเมื่อองค์ประกอบที่เล็กที่สุดถูกเลือกเป็น pivot ในทุกขั้นตอนดังนั้นอัลกอริธึมจะเสื่อมสภาพลงในเวลาทำงาน O (n ^ 2)) การเลือกเดือยพิเศษถูกนำไปใช้เรียกว่าอัลกอริทึมมัธยฐาน โซลูชันทั้งหมดทำงานในเวลา O (n) ในกรณีที่แย่ที่สุดและโดยเฉลี่ย

นี่คือลิงก์ไปยังบทความฉบับเต็ม (เป็นเรื่องเกี่ยวกับการหาองค์ประกอบที่เล็กที่สุดของ Kth แต่หลักการเหมือนกันสำหรับการค้นหา Kth ที่ใหญ่ที่สุด ):

การค้นหาองค์ประกอบที่เล็กที่สุดของ Kth ใน Array ที่ไม่เรียงลำดับ


2

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

  1. แบ่งอาร์เรย์ในรายการ n / 5 ของ 5 องค์ประกอบแต่ละรายการ
  2. ค้นหาค่ามัธยฐานในแต่ละอาร์เรย์ย่อยของ 5 องค์ประกอบ
  3. เรียกค่ามัธยฐานของค่ามัธยฐานทั้งหมดให้เรียกมันว่า M
  4. การแบ่งอาร์เรย์ในอาร์เรย์ย่อยสองชุดย่อยที่ 1 ประกอบด้วยองค์ประกอบที่มีขนาดใหญ่กว่า M สมมติว่า sub-array นี้เป็น a1 ในขณะที่ sub-array อื่น ๆ มีองค์ประกอบที่เล็กกว่า M แล้วให้เรียกย่อย a2 นี้
  5. ถ้า k <= | a1 | ให้เลือกการส่งคืน (a1, k)
  6. ถ้า k− 1 = | a1 | ให้ส่งคืน M.
  7. ถ้า k> | a1 | + 1, การเลือกผลตอบแทน (a2, k −a1 - 1)

การวิเคราะห์:ตามที่แนะนำในเอกสารต้นฉบับ:

เราใช้ค่ามัธยฐานเพื่อแบ่งรายการออกเป็นสองส่วน (ครึ่งแรกถ้าk <= n/2และครึ่งหลังเป็นอย่างอื่น) ขั้นตอนวิธีการนี้ต้องใช้เวลาcnในระดับแรกของการเรียกซ้ำสำหรับบางคงc, cn/2ที่ระดับถัดไป (เนื่องจากเรา recurse ในรายการของขนาด n / 2) cn/4ในระดับที่สาม, และอื่น ๆ cn + cn/2 + cn/4 + .... = 2cn = o(n)รวมเวลาดำเนินการคือ

ทำไมขนาดพาร์ติชั่นถึง 5 และไม่ใช่ 3?

ตามที่ระบุไว้ในกระดาษต้นฉบับ:

การแบ่งรายชื่อด้วย 5 ทำให้แบ่งกรณีที่เลวร้ายที่สุดของ 70 - 30 Atleast ครึ่งหนึ่งของค่ามัธยฐานมากกว่าค่ามัธยฐานของมัธยฐานดังนั้นครึ่ง atleast ครึ่งหนึ่งของ n / 5 บล็อกมีองค์ประกอบอย่างน้อย 3 และสิ่งนี้ให้ 3n/10แยกซึ่ง หมายความว่าพาร์ติชันอื่นคือ 7n / 10 ในกรณีที่เลวร้ายที่สุด ที่จะช่วยให้เวลาที่เลวร้ายที่สุดกรณีที่ทำงานเป็นT(n) = T(n/5)+T(7n/10)+O(n). Since n/5+7n/10 < 1O(n)

ตอนนี้ฉันได้ลองใช้อัลกอริทึมด้านบนเป็น:

public static int findKthLargestUsingMedian(Integer[] array, int k) {
        // Step 1: Divide the list into n/5 lists of 5 element each.
        int noOfRequiredLists = (int) Math.ceil(array.length / 5.0);
        // Step 2: Find pivotal element aka median of medians.
        int medianOfMedian =  findMedianOfMedians(array, noOfRequiredLists);
        //Now we need two lists split using medianOfMedian as pivot. All elements in list listOne will be grater than medianOfMedian and listTwo will have elements lesser than medianOfMedian.
        List<Integer> listWithGreaterNumbers = new ArrayList<>(); // elements greater than medianOfMedian
        List<Integer> listWithSmallerNumbers = new ArrayList<>(); // elements less than medianOfMedian
        for (Integer element : array) {
            if (element < medianOfMedian) {
                listWithSmallerNumbers.add(element);
            } else if (element > medianOfMedian) {
                listWithGreaterNumbers.add(element);
            }
        }
        // Next step.
        if (k <= listWithGreaterNumbers.size()) return findKthLargestUsingMedian((Integer[]) listWithGreaterNumbers.toArray(new Integer[listWithGreaterNumbers.size()]), k);
        else if ((k - 1) == listWithGreaterNumbers.size()) return medianOfMedian;
        else if (k > (listWithGreaterNumbers.size() + 1)) return findKthLargestUsingMedian((Integer[]) listWithSmallerNumbers.toArray(new Integer[listWithSmallerNumbers.size()]), k-listWithGreaterNumbers.size()-1);
        return -1;
    }

    public static int findMedianOfMedians(Integer[] mainList, int noOfRequiredLists) {
        int[] medians = new int[noOfRequiredLists];
        for (int count = 0; count < noOfRequiredLists; count++) {
            int startOfPartialArray = 5 * count;
            int endOfPartialArray = startOfPartialArray + 5;
            Integer[] partialArray = Arrays.copyOfRange((Integer[]) mainList, startOfPartialArray, endOfPartialArray);
            // Step 2: Find median of each of these sublists.
            int medianIndex = partialArray.length/2;
            medians[count] = partialArray[medianIndex];
        }
        // Step 3: Find median of the medians.
        return medians[medians.length / 2];
    }

O(nlogn)เพียงเพื่อประโยชน์ของการเสร็จสิ้นขั้นตอนวิธีอื่นที่ทำให้การใช้คิวลำดับความสำคัญและต้องใช้เวลา

public static int findKthLargestUsingPriorityQueue(Integer[] nums, int k) {
        int p = 0;
        int numElements = nums.length;
        // create priority queue where all the elements of nums will be stored
        PriorityQueue<Integer> pq = new PriorityQueue<Integer>();

        // place all the elements of the array to this priority queue
        for (int n : nums) {
            pq.add(n);
        }

        // extract the kth largest element
        while (numElements - k + 1 > 0) {
            p = pq.poll();
            k++;
        }

        return p;
    }

อัลกอริธึมทั้งสองนี้สามารถทดสอบได้ดังนี้:

public static void main(String[] args) throws IOException {
        Integer[] numbers = new Integer[]{2, 3, 5, 4, 1, 12, 11, 13, 16, 7, 8, 6, 10, 9, 17, 15, 19, 20, 18, 23, 21, 22, 25, 24, 14};
        System.out.println(findKthLargestUsingMedian(numbers, 8));
        System.out.println(findKthLargestUsingPriorityQueue(numbers, 8));
    }

ตามที่คาดหวังเอาท์พุทคือ: 18 18


@rogerdpack ฉันได้ให้ลิงค์ที่ฉันติดตาม
akhil_mittal

2

วิธีการเกี่ยวกับวิธีนี้

รักษา a buffer of length kและ a tmp_max, การรับ tmp_max คือ O (k) และทำ n ครั้งดังนั้นจึงเป็นเช่นนั้นO(kn)

ป้อนคำอธิบายรูปภาพที่นี่

ถูกหรือว่าฉันทำอะไรหายไป?

แม้ว่ามันจะไม่ชนะกรณีเฉลี่ยของการเลือกอย่างรวดเร็วและกรณีที่แย่ที่สุดของวิธีการทางสถิติ แต่มันค่อนข้างง่ายที่จะเข้าใจและนำไปใช้


1
ฉันชอบมันง่ายต่อการเข้าใจ แม้ว่าความซับซ้อนจะเป็น O (nk) ตามที่คุณชี้ให้เห็น
ฮัจจัต

1

วนซ้ำตามรายการ หากค่าปัจจุบันมีค่ามากกว่าค่าที่เก็บไว้ที่ใหญ่ที่สุดให้จัดเก็บเป็นค่ามากที่สุดและชน 1-4 ลงและ 5 ลดลงจากรายการ ถ้าไม่เปรียบเทียบกับหมายเลข 2 และทำสิ่งเดียวกัน ทำซ้ำตรวจสอบกับค่าที่เก็บไว้ทั้งหมด 5 ค่า สิ่งนี้ควรทำใน O (n)


"bump" นั้นคือ O (n) ถ้าคุณใช้อาร์เรย์หรือลงไปที่ O (log n) (ฉันคิดว่า) ถ้าคุณใช้โครงสร้างที่ดีกว่า
Kirk Strauser

ไม่จำเป็นต้องเป็น O (log k) - ถ้ารายการนั้นเป็นลิสต์ที่เชื่อมโยงจากนั้นเพิ่มอิลิเมนต์ใหม่ไปที่ด้านบนและการวางอิลิเมนต์สุดท้ายจะเหมือนกับ O (2)
Alnitak

การชนจะเป็น O (k) สำหรับรายการที่ได้รับการสนับสนุนอาร์เรย์ O (1) สำหรับรายการที่เชื่อมโยงอย่างเหมาะสม ไม่ว่าจะด้วยวิธีใดคำถามแบบนี้โดยทั่วไปถือว่ามีผลกระทบน้อยที่สุดเมื่อเทียบกับ n และไม่แนะนำให้ใช้ปัจจัยใดของ n อีกต่อไป
bobince

มันก็จะเป็น O (1) ถ้าชนใช้แหวนบัฟเฟอร์
Alnitak

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

1

ฉันอยากจะแนะนำคำตอบเดียว

ถ้าเรานำองค์ประกอบ k แรกและจัดเรียงไว้ในรายการที่เชื่อมโยงของค่า k

ตอนนี้สำหรับค่าอื่น ๆ แม้แต่สำหรับกรณีที่เลวร้ายที่สุดถ้าเราทำการเรียงลำดับการแทรกสำหรับค่าที่เหลือ nk แม้ในจำนวนที่แย่ที่สุดของการเปรียบเทียบจะเป็น k * (nk) และสำหรับค่า k ก่อนหน้าจะเรียงเป็น k * (k- 1) มันออกมาเป็น (nk-k) ซึ่งก็คือ o (n)

ไชโย


1
การเรียงลำดับใช้เวลา nlogn ... ขั้นตอนวิธีควรใช้เส้นเวลา
MrDatabase

1

คำอธิบายของอัลกอริธึมมัธยฐาน - ของ - ค่ามัธยฐานเพื่อหาจำนวนเต็มที่ใหญ่ที่สุด k-th จาก n สามารถดูได้ที่นี่: http://cs.indstate.edu/~spitla/presentation.pdf

การใช้งานใน c ++ อยู่ด้านล่าง:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int findMedian(vector<int> vec){
//    Find median of a vector
    int median;
    size_t size = vec.size();
    median = vec[(size/2)];
    return median;
}

int findMedianOfMedians(vector<vector<int> > values){
    vector<int> medians;

    for (int i = 0; i < values.size(); i++) {
        int m = findMedian(values[i]);
        medians.push_back(m);
    }

    return findMedian(medians);
}

void selectionByMedianOfMedians(const vector<int> values, int k){
//    Divide the list into n/5 lists of 5 elements each
    vector<vector<int> > vec2D;

    int count = 0;
    while (count != values.size()) {
        int countRow = 0;
        vector<int> row;

        while ((countRow < 5) && (count < values.size())) {
            row.push_back(values[count]);
            count++;
            countRow++;
        }
        vec2D.push_back(row);
    }

    cout<<endl<<endl<<"Printing 2D vector : "<<endl;
    for (int i = 0; i < vec2D.size(); i++) {
        for (int j = 0; j < vec2D[i].size(); j++) {
            cout<<vec2D[i][j]<<" ";
        }
        cout<<endl;
    }
    cout<<endl;

//    Calculating a new pivot for making splits
    int m = findMedianOfMedians(vec2D);
    cout<<"Median of medians is : "<<m<<endl;

//    Partition the list into unique elements larger than 'm' (call this sublist L1) and
//    those smaller them 'm' (call this sublist L2)
    vector<int> L1, L2;

    for (int i = 0; i < vec2D.size(); i++) {
        for (int j = 0; j < vec2D[i].size(); j++) {
            if (vec2D[i][j] > m) {
                L1.push_back(vec2D[i][j]);
            }else if (vec2D[i][j] < m){
                L2.push_back(vec2D[i][j]);
            }
        }
    }

//    Checking the splits as per the new pivot 'm'
    cout<<endl<<"Printing L1 : "<<endl;
    for (int i = 0; i < L1.size(); i++) {
        cout<<L1[i]<<" ";
    }

    cout<<endl<<endl<<"Printing L2 : "<<endl;
    for (int i = 0; i < L2.size(); i++) {
        cout<<L2[i]<<" ";
    }

//    Recursive calls
    if ((k - 1) == L1.size()) {
        cout<<endl<<endl<<"Answer :"<<m;
    }else if (k <= L1.size()) {
        return selectionByMedianOfMedians(L1, k);
    }else if (k > (L1.size() + 1)){
        return selectionByMedianOfMedians(L2, k-((int)L1.size())-1);
    }

}

int main()
{
    int values[] = {2, 3, 5, 4, 1, 12, 11, 13, 16, 7, 8, 6, 10, 9, 17, 15, 19, 20, 18, 23, 21, 22, 25, 24, 14};

    vector<int> vec(values, values + 25);

    cout<<"The given array is : "<<endl;
    for (int i = 0; i < vec.size(); i++) {
        cout<<vec[i]<<" ";
    }

    selectionByMedianOfMedians(vec, 8);

    return 0;
}

วิธีนี้ไม่ทำงาน คุณต้องจัดเรียงอาร์เรย์ก่อนส่งค่ามัธยฐานสำหรับกรณีองค์ประกอบ 5 รายการ
Agnishom Chattopadhyay

1

นอกจากนี้ยังมีอัลกอริทึมการเลือกของ Wirthซึ่งมีการใช้งานที่ง่ายกว่า QuickSelect อัลกอริทึมการเลือกของ Wirth นั้นช้ากว่า QuickSelect แต่ด้วยการปรับปรุงบางอย่างมันจะกลายเป็นเร็วขึ้น

ในรายละเอียดเพิ่มเติม การใช้การเพิ่มประสิทธิภาพ MODIFIND ของ Vladimir Zabrodsky และการเลือกเดือยแบบมัธยฐานของ 3 และให้ความสนใจกับขั้นตอนสุดท้ายของการแบ่งพาร์ติชันของอัลกอริทึมฉันได้สร้างอัลกอริทึมต่อไปนี้ขึ้นมา (ชื่อ "LefSelect"):

#define F_SWAP(a,b) { float temp=(a);(a)=(b);(b)=temp; }

# Note: The code needs more than 2 elements to work
float lefselect(float a[], const int n, const int k) {
    int l=0, m = n-1, i=l, j=m;
    float x;

    while (l<m) {
        if( a[k] < a[i] ) F_SWAP(a[i],a[k]);
        if( a[j] < a[i] ) F_SWAP(a[i],a[j]);
        if( a[j] < a[k] ) F_SWAP(a[k],a[j]);

        x=a[k];
        while (j>k & i<k) {
            do i++; while (a[i]<x);
            do j--; while (a[j]>x);

            F_SWAP(a[i],a[j]);
        }
        i++; j--;

        if (j<k) {
            while (a[i]<x) i++;
            l=i; j=m;
        }
        if (k<i) {
            while (x<a[j]) j--;
            m=j; i=l;
        }
    }
    return a[k];
}

ในการวัดประสิทธิภาพที่ฉันทำที่นี่ LefSelect เร็วกว่า QuickSelect 20-30%


1

โซลูชั่น Haskell:

kthElem index list = sort list !! index

withShape ~[]     []     = []
withShape ~(x:xs) (y:ys) = x : withShape xs ys

sort []     = []
sort (x:xs) = (sort ls `withShape` ls) ++ [x] ++ (sort rs `withShape` rs)
  where
   ls = filter (<  x)
   rs = filter (>= x)

สิ่งนี้ใช้ค่ามัธยฐานของวิธีแก้ปัญหาค่ามัธยฐานโดยใช้วิธี withShape เพื่อค้นหาขนาดของพาร์ติชันโดยไม่ต้องคำนวณจริง


1

นี่คือการใช้ C ++ ของ QuickSelect แบบสุ่ม ความคิดคือการสุ่มเลือกองค์ประกอบสาระสำคัญ ในการสร้างพาร์ติชันแบบสุ่มเราใช้ฟังก์ชั่นแบบสุ่ม rand () เพื่อสร้างดัชนีระหว่าง l และ r สลับองค์ประกอบที่ดัชนีที่สร้างแบบสุ่มกับองค์ประกอบสุดท้ายและสุดท้ายเรียกกระบวนการพาร์ติชันมาตรฐานซึ่งใช้องค์ประกอบสุดท้ายเป็นเดือย

#include<iostream>
#include<climits>
#include<cstdlib>
using namespace std;

int randomPartition(int arr[], int l, int r);

// This function returns k'th smallest element in arr[l..r] using
// QuickSort based method.  ASSUMPTION: ALL ELEMENTS IN ARR[] ARE DISTINCT
int kthSmallest(int arr[], int l, int r, int k)
{
    // If k is smaller than number of elements in array
    if (k > 0 && k <= r - l + 1)
    {
        // Partition the array around a random element and
        // get position of pivot element in sorted array
        int pos = randomPartition(arr, l, r);

        // If position is same as k
        if (pos-l == k-1)
            return arr[pos];
        if (pos-l > k-1)  // If position is more, recur for left subarray
            return kthSmallest(arr, l, pos-1, k);

        // Else recur for right subarray
        return kthSmallest(arr, pos+1, r, k-pos+l-1);
    }

    // If k is more than number of elements in array
    return INT_MAX;
}

void swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

// Standard partition process of QuickSort().  It considers the last
// element as pivot and moves all smaller element to left of it and
// greater elements to right. This function is used by randomPartition()
int partition(int arr[], int l, int r)
{
    int x = arr[r], i = l;
    for (int j = l; j <= r - 1; j++)
    {
        if (arr[j] <= x) //arr[i] is bigger than arr[j] so swap them
        {
            swap(&arr[i], &arr[j]);
            i++;
        }
    }
    swap(&arr[i], &arr[r]); // swap the pivot
    return i;
}

// Picks a random pivot element between l and r and partitions
// arr[l..r] around the randomly picked element using partition()
int randomPartition(int arr[], int l, int r)
{
    int n = r-l+1;
    int pivot = rand() % n;
    swap(&arr[l + pivot], &arr[r]);
    return partition(arr, l, r);
}

// Driver program to test above methods
int main()
{
    int arr[] = {12, 3, 5, 7, 4, 19, 26};
    int n = sizeof(arr)/sizeof(arr[0]), k = 3;
    cout << "K'th smallest element is " << kthSmallest(arr, 0, n-1, k);
    return 0;
}

ความซับซ้อนของเวลากรณีที่เลวร้ายที่สุดของการแก้ปัญหาข้างต้นยังคงเป็น O (n2) ในกรณีที่เลวร้ายที่สุดฟังก์ชั่นการสุ่มอาจเลือกองค์ประกอบมุมเสมอ ความซับซ้อนของเวลาที่คาดหวังของ QuickSelect แบบสุ่มด้านบนคือΘ (n)


การเข้ารหัสที่ดี ขอบคุณสำหรับการแบ่งปัน +1
Hengameh

1
  1. สร้างคิวลำดับความสำคัญ
  2. แทรกองค์ประกอบทั้งหมดลงในกอง
  3. โทรโพล () k ครั้ง

    public static int getKthLargestElements(int[] arr)
    {
        PriorityQueue<Integer> pq =  new PriorityQueue<>((x , y) -> (y-x));
        //insert all the elements into heap
        for(int ele : arr)
           pq.offer(ele);
        // call poll() k times
        int i=0;
        while(i&lt;k)
         {
           int result = pq.poll();
         } 
       return result;        
    }
    

0

นี่คือการใช้งานใน Javascript

หากคุณปล่อยข้อ จำกัด ที่คุณไม่สามารถแก้ไขอาร์เรย์ได้คุณสามารถป้องกันการใช้หน่วยความจำเพิ่มเติมโดยใช้ดัชนีสองตัวเพื่อระบุ "พาร์ติชันปัจจุบัน" (ในสไตล์ quicksort แบบคลาสสิก - http://www.nczonline.net/blog/2012/ 11/27 / คอมพิวเตอร์วิทยาศาสตร์ใน -javascript-quicksort / )

function kthMax(a, k){
    var size = a.length;

    var pivot = a[ parseInt(Math.random()*size) ]; //Another choice could have been (size / 2) 

    //Create an array with all element lower than the pivot and an array with all element higher than the pivot
    var i, lowerArray = [], upperArray = [];
    for (i = 0; i  < size; i++){
        var current = a[i];

        if (current < pivot) {
            lowerArray.push(current);
        } else if (current > pivot) {
            upperArray.push(current);
        }
    }

    //Which one should I continue with?
    if(k <= upperArray.length) {
        //Upper
        return kthMax(upperArray, k);
    } else {
        var newK = k - (size - lowerArray.length);

        if (newK > 0) {
            ///Lower
            return kthMax(lowerArray, newK);
        } else {
            //None ... it's the current pivot!
            return pivot;
        }   
    }
}  

หากคุณต้องการทดสอบประสิทธิภาพการทำงานคุณสามารถใช้รูปแบบนี้:

    function kthMax (a, k, logging) {
         var comparisonCount = 0; //Number of comparison that the algorithm uses
         var memoryCount = 0;     //Number of integers in memory that the algorithm uses
         var _log = logging;

         if(k < 0 || k >= a.length) {
            if (_log) console.log ("k is out of range"); 
            return false;
         }      

         function _kthmax(a, k){
             var size = a.length;
             var pivot = a[parseInt(Math.random()*size)];
             if(_log) console.log("Inputs:", a,  "size="+size, "k="+k, "pivot="+pivot);

             // This should never happen. Just a nice check in this exercise
             // if you are playing with the code to avoid never ending recursion            
             if(typeof pivot === "undefined") {
                 if (_log) console.log ("Ops..."); 
                 return false;
             }

             var i, lowerArray = [], upperArray = [];
             for (i = 0; i  < size; i++){
                 var current = a[i];
                 if (current < pivot) {
                     comparisonCount += 1;
                     memoryCount++;
                     lowerArray.push(current);
                 } else if (current > pivot) {
                     comparisonCount += 2;
                     memoryCount++;
                     upperArray.push(current);
                 }
             }
             if(_log) console.log("Pivoting:",lowerArray, "*"+pivot+"*", upperArray);

             if(k <= upperArray.length) {
                 comparisonCount += 1;
                 return _kthmax(upperArray, k);
             } else if (k > size - lowerArray.length) {
                 comparisonCount += 2;
                 return _kthmax(lowerArray, k - (size - lowerArray.length));
             } else {
                 comparisonCount += 2;
                 return pivot;
             }
     /* 
      * BTW, this is the logic for kthMin if we want to implement that... ;-)
      * 

             if(k <= lowerArray.length) {
                 return kthMin(lowerArray, k);
             } else if (k > size - upperArray.length) {
                 return kthMin(upperArray, k - (size - upperArray.length));
             } else 
                 return pivot;
     */            
         }

         var result = _kthmax(a, k);
         return {result: result, iterations: comparisonCount, memory: memoryCount};
     }

รหัสที่เหลือเป็นเพียงการสร้างสนามเด็กเล่น:

    function getRandomArray (n){
        var ar = [];
        for (var i = 0, l = n; i < l; i++) {
            ar.push(Math.round(Math.random() * l))
        }

        return ar;
    }

    //Create a random array of 50 numbers
    var ar = getRandomArray (50);   

ตอนนี้ให้คุณทดสอบสักครู่ เพราะ Math.random () มันจะให้ผลลัพธ์ที่แตกต่างกันทุกครั้ง:

    kthMax(ar, 2, true);
    kthMax(ar, 2);
    kthMax(ar, 2);
    kthMax(ar, 2);
    kthMax(ar, 2);
    kthMax(ar, 2);
    kthMax(ar, 34, true);
    kthMax(ar, 34);
    kthMax(ar, 34);
    kthMax(ar, 34);
    kthMax(ar, 34);
    kthMax(ar, 34);

หากคุณทดสอบสองสามครั้งคุณจะเห็นได้ชัดเจนว่าจำนวนการวนซ้ำโดยเฉลี่ยคือ O (n) ~ = ค่าคงที่ * n และค่าของ k จะไม่ส่งผลต่ออัลกอริทึม


0

ฉันคิดอัลกอริทึมนี้ขึ้นมาและดูเหมือนว่าจะเป็น O (n):

สมมุติว่า k = 3 และเราต้องการหาไอเท็มที่ใหญ่เป็นอันดับสามในอาร์เรย์ ฉันจะสร้างตัวแปรสามตัวและเปรียบเทียบแต่ละรายการของอาร์เรย์ด้วยค่าต่ำสุดของตัวแปรทั้งสามนี้ หากรายการอาร์เรย์มากกว่าค่าต่ำสุดของเราเราจะแทนที่ตัวแปรขั้นต่ำด้วยค่ารายการ เราดำเนินการต่อไปจนกระทั่งสิ้นสุดอาเรย์ ค่าต่ำสุดของตัวแปรทั้งสามของเราคือรายการที่ใหญ่เป็นอันดับสามในอาร์เรย์

define variables a=0, b=0, c=0
iterate through the array items
    find minimum a,b,c
    if item > min then replace the min variable with item value
    continue until end of array
the minimum of a,b,c is our answer

และเพื่อค้นหาไอเท็มที่ใหญ่ที่สุดของ Kth เราต้องการตัวแปร K

ตัวอย่าง: (k = 3)

[1,2,4,1,7,3,9,5,6,2,9,8]

Final variable values:

a=7 (answer)
b=8
c=9

ใครช่วยกรุณาตรวจสอบเรื่องนี้และแจ้งให้เราทราบสิ่งที่ฉันหายไป?


0

นี่คือการใช้อัลกอริธึมที่แนะนำ (ฉันยังใส่การใช้งานด้วยเดือยแบบสุ่ม):

public class Median {

    public static void main(String[] s) {

        int[] test = {4,18,20,3,7,13,5,8,2,1,15,17,25,30,16};
        System.out.println(selectK(test,8));

        /*
        int n = 100000000;
        int[] test = new int[n];
        for(int i=0; i<test.length; i++)
            test[i] = (int)(Math.random()*test.length);

        long start = System.currentTimeMillis();
        random_selectK(test, test.length/2);
        long end = System.currentTimeMillis();
        System.out.println(end - start);
        */
    }

    public static int random_selectK(int[] a, int k) {
        if(a.length <= 1)
            return a[0];

        int r = (int)(Math.random() * a.length);
        int p = a[r];

        int small = 0, equal = 0, big = 0;
        for(int i=0; i<a.length; i++) {
            if(a[i] < p) small++;
            else if(a[i] == p) equal++;
            else if(a[i] > p) big++;
        }

        if(k <= small) {
            int[] temp = new int[small];
            for(int i=0, j=0; i<a.length; i++)
                if(a[i] < p)
                    temp[j++] = a[i];
            return random_selectK(temp, k);
        }

        else if (k <= small+equal)
            return p;

        else {
            int[] temp = new int[big];
            for(int i=0, j=0; i<a.length; i++)
                if(a[i] > p)
                    temp[j++] = a[i];
            return random_selectK(temp,k-small-equal);
        }
    }

    public static int selectK(int[] a, int k) {
        if(a.length <= 5) {
            Arrays.sort(a);
            return a[k-1];
        }

        int p = median_of_medians(a);

        int small = 0, equal = 0, big = 0;
        for(int i=0; i<a.length; i++) {
            if(a[i] < p) small++;
            else if(a[i] == p) equal++;
            else if(a[i] > p) big++;
        }

        if(k <= small) {
            int[] temp = new int[small];
            for(int i=0, j=0; i<a.length; i++)
                if(a[i] < p)
                    temp[j++] = a[i];
            return selectK(temp, k);
        }

        else if (k <= small+equal)
            return p;

        else {
            int[] temp = new int[big];
            for(int i=0, j=0; i<a.length; i++)
                if(a[i] > p)
                    temp[j++] = a[i];
            return selectK(temp,k-small-equal);
        }
    }

    private static int median_of_medians(int[] a) {
        int[] b = new int[a.length/5];
        int[] temp = new int[5];
        for(int i=0; i<b.length; i++) {
            for(int j=0; j<5; j++)
                temp[j] = a[5*i + j];
            Arrays.sort(temp);
            b[i] = temp[2];
        }

        return selectK(b, b.length/2 + 1);
    }
}

0

มันคล้ายกับกลยุทธ์ quickSort ที่เราเลือกเดือยโดยพลการและนำองค์ประกอบที่เล็กลงมาทางซ้ายและใหญ่กว่าไปทางขวา

    public static int kthElInUnsortedList(List<int> list, int k)
    {
        if (list.Count == 1)
            return list[0];

        List<int> left = new List<int>();
        List<int> right = new List<int>();

        int pivotIndex = list.Count / 2;
        int pivot = list[pivotIndex]; //arbitrary

        for (int i = 0; i < list.Count && i != pivotIndex; i++)
        {
            int currentEl = list[i];
            if (currentEl < pivot)
                left.Add(currentEl);
            else
                right.Add(currentEl);
        }

        if (k == left.Count + 1)
            return pivot;

        if (left.Count < k)
            return kthElInUnsortedList(right, k - left.Count - 1);
        else
            return kthElInUnsortedList(left, k);
    }


0

คุณสามารถค้นหาองค์ประกอบที่เล็กที่สุด kth ในเวลา O (n) และพื้นที่คงที่ หากเราพิจารณาว่าอาร์เรย์นั้นมีค่าสำหรับจำนวนเต็มเท่านั้น

วิธีการคือทำการค้นหาไบนารีในช่วงของค่า Array หากเรามี min_value และ max_value ทั้งคู่ในช่วงจำนวนเต็มเราสามารถทำการค้นหาแบบไบนารีในช่วงนั้น เราสามารถเขียนฟังก์ชั่นตัวเปรียบเทียบซึ่งจะบอกเราว่าค่าใดก็ตามที่เล็กที่สุดหรือเล็กกว่า kth น้อยกว่าหรือใหญ่กว่า kth น้อยที่สุด ทำการค้นหาแบบไบนารีจนกว่าจะถึงจำนวน kth ที่เล็กที่สุด

นี่คือรหัสสำหรับสิ่งนั้น

โซลูชันระดับ:

def _iskthsmallest(self, A, val, k):
    less_count, equal_count = 0, 0
    for i in range(len(A)):
        if A[i] == val: equal_count += 1
        if A[i] < val: less_count += 1

    if less_count >= k: return 1
    if less_count + equal_count < k: return -1
    return 0

def kthsmallest_binary(self, A, min_val, max_val, k):
    if min_val == max_val:
        return min_val
    mid = (min_val + max_val)/2
    iskthsmallest = self._iskthsmallest(A, mid, k)
    if iskthsmallest == 0: return mid
    if iskthsmallest > 0: return self.kthsmallest_binary(A, min_val, mid, k)
    return self.kthsmallest_binary(A, mid+1, max_val, k)

# @param A : tuple of integers
# @param B : integer
# @return an integer
def kthsmallest(self, A, k):
    if not A: return 0
    if k > len(A): return 0
    min_val, max_val = min(A), max(A)
    return self.kthsmallest_binary(A, min_val, max_val, k)

0

นอกจากนี้ยังมีอัลกอริทึมหนึ่งที่มีประสิทธิภาพสูงกว่าอัลกอริทึม quickselect มันเรียกว่าอัลกอริทึม Floyd-Rivets (FR)อัลกอริทึม

บทความต้นฉบับ: https://doi.org/10.1145/360680.360694

เวอร์ชั่นที่ดาวน์โหลดได้: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.309.7108&rep=rep1&type=pdf

บทความ Wikipedia https://en.wikipedia.org/wiki/Floyd%E2%80%93Rivest_algorithm

ฉันพยายามใช้ Quickselect และอัลกอริทึม FR ใน C ++ นอกจากนี้ฉันเปรียบเทียบพวกเขากับการใช้งานไลบรารี C ++ มาตรฐาน std :: nth_element (ซึ่งโดยทั่วไปแล้วเป็นการผสมผสานระหว่างไฮบริดของ quickselect และ heapselect) ผลที่ได้คือการเลือกอย่างรวดเร็วและ nth_element วิ่งได้โดยเฉลี่ยโดยเปรียบเทียบ แต่อัลกอริทึม FR วิ่งประมาณ เร็วเป็นสองเท่าเมื่อเทียบกับพวกเขา

โค้ดตัวอย่างที่ฉันใช้สำหรับอัลกอริทึม FR:

template <typename T>
T FRselect(std::vector<T>& data, const size_t& n)
{
    if (n == 0)
        return *(std::min_element(data.begin(), data.end()));
    else if (n == data.size() - 1)
        return *(std::max_element(data.begin(), data.end()));
    else
        return _FRselect(data, 0, data.size() - 1, n);
}

template <typename T>
T _FRselect(std::vector<T>& data, const size_t& left, const size_t& right, const size_t& n)
{
    size_t leftIdx = left;
    size_t rightIdx = right;

    while (rightIdx > leftIdx)
    {
        if (rightIdx - leftIdx > 600)
        {
            size_t range = rightIdx - leftIdx + 1;
            long long i = n - (long long)leftIdx + 1;
            long long z = log(range);
            long long s = 0.5 * exp(2 * z / 3);
            long long sd = 0.5 * sqrt(z * s * (range - s) / range) * sgn(i - (long long)range / 2);

            size_t newLeft = fmax(leftIdx, n - i * s / range + sd);
            size_t newRight = fmin(rightIdx, n + (range - i) * s / range + sd);

            _FRselect(data, newLeft, newRight, n);
        }
        T t = data[n];
        size_t i = leftIdx;
        size_t j = rightIdx;
        // arrange pivot and right index
        std::swap(data[leftIdx], data[n]);
        if (data[rightIdx] > t)
            std::swap(data[rightIdx], data[leftIdx]);

        while (i < j)
        {
            std::swap(data[i], data[j]);
            ++i; --j;
            while (data[i] < t) ++i;
            while (data[j] > t) --j;
        }

        if (data[leftIdx] == t)
            std::swap(data[leftIdx], data[j]);
        else
        {
            ++j;
            std::swap(data[j], data[rightIdx]);
        }
        // adjust left and right towards the boundaries of the subset
        // containing the (k - left + 1)th smallest element
        if (j <= n)
            leftIdx = j + 1;
        if (n <= j)
            rightIdx = j - 1;
    }

    return data[leftIdx];
}

template <typename T>
int sgn(T val) {
    return (T(0) < val) - (val < T(0));
}

-1

สิ่งที่ฉันจะทำคือ:

initialize empty doubly linked list l
for each element e in array
    if e larger than head(l)
        make e the new head of l
        if size(l) > k
            remove last element from l

the last element of l should now be the kth largest element

คุณสามารถจัดเก็บพอยน์เตอร์ไปที่องค์ประกอบแรกและสุดท้ายในรายการที่เชื่อมโยง พวกเขาเปลี่ยนเฉพาะเมื่อมีการปรับปรุงรายการ

ปรับปรุง:

initialize empty sorted tree l
for each element e in array
    if e between head(l) and tail(l)
        insert e into l // O(log k)
        if size(l) > k
            remove last element from l

the last element of l should now be the kth largest element

เกิดอะไรขึ้นถ้า e มีขนาดเล็กกว่าหัว (l) มันอาจยังใหญ่กว่าองค์ประกอบที่ใหญ่ที่สุด kth แต่จะไม่ถูกเพิ่มเข้าไปในรายการ คุณจะต้องเรียงลำดับรายการเพื่อให้สามารถใช้งานได้ตามลำดับจากน้อยไปมาก
Elie

คุณพูดถูกฉันจะต้องคิดอีกที :-)
Jasper Bekkers

วิธีแก้ปัญหาคือการตรวจสอบว่า e อยู่ระหว่าง head (l) และ tail (l) แล้วใส่เข้าไปในตำแหน่งที่ถูกต้องถ้าเป็น ทำ O (kn) นี้ คุณสามารถทำให้มันเป็น O (n log k) เมื่อใช้ binary tree ที่คอยติดตามองค์ประกอบขั้นต่ำและสูงสุด
Jasper Bekkers

-1

ก่อนอื่นเราสามารถสร้าง BST จากอาเรย์ที่ไม่ได้เรียงลำดับซึ่งใช้เวลา O (n) และจาก BST เราสามารถหาองค์ประกอบที่เล็กที่สุด kth ใน O (log (n)) ซึ่งนับได้ทั้งหมดตามลำดับของ O (n)

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