การนับการผกผันในอาร์เรย์


108

ฉันออกแบบขั้นตอนวิธีการที่จะทำต่อไปนี้: ให้อาร์เรย์A[1... n]สำหรับทุกค้นหาทุกคู่ผกผันดังกล่าวว่าi < j A[i] > A[j]ฉันใช้การเรียงลำดับการผสานและคัดลอกอาร์เรย์ A ไปยังอาร์เรย์ B แล้วเปรียบเทียบอาร์เรย์ทั้งสอง แต่ฉันมีช่วงเวลาที่ยากลำบากในการดูว่าจะใช้สิ่งนี้เพื่อหาจำนวนการผกผันได้อย่างไร คำแนะนำหรือความช่วยเหลือใด ๆ จะได้รับการชื่นชมอย่างมาก

คำตอบ:


139

นี่คือโซลูชัน O (n log n) ใน java

long merge(int[] arr, int[] left, int[] right) {
    int i = 0, j = 0, count = 0;
    while (i < left.length || j < right.length) {
        if (i == left.length) {
            arr[i+j] = right[j];
            j++;
        } else if (j == right.length) {
            arr[i+j] = left[i];
            i++;
        } else if (left[i] <= right[j]) {
            arr[i+j] = left[i];
            i++;                
        } else {
            arr[i+j] = right[j];
            count += left.length-i;
            j++;
        }
    }
    return count;
}

long invCount(int[] arr) {
    if (arr.length < 2)
        return 0;

    int m = (arr.length + 1) / 2;
    int left[] = Arrays.copyOfRange(arr, 0, m);
    int right[] = Arrays.copyOfRange(arr, m, arr.length);

    return invCount(left) + invCount(right) + merge(arr, left, right);
}

นี่คือการเรียงลำดับการผสานเกือบปกติเวทมนตร์ทั้งหมดถูกซ่อนอยู่ในฟังก์ชันผสาน โปรดทราบว่าในขณะที่อัลกอริทึมการเรียงลำดับลบการผกผัน ในขณะที่การรวมอัลกอริทึมจะนับจำนวนการผกผันที่ถูกลบออกไป

ช่วงเวลาเดียวที่การผกผันจะถูกลบออกคือเมื่ออัลกอริทึมนำองค์ประกอบจากด้านขวาของอาร์เรย์และรวมเข้ากับอาร์เรย์หลัก จำนวนการผกผันที่ลบออกโดยการดำเนินการนี้คือจำนวนองค์ประกอบที่เหลือจากอาร์เรย์ด้านซ้ายที่จะรวมเข้าด้วยกัน :)

หวังว่ามันจะอธิบายได้เพียงพอ


2
ฉันลองเรียกใช้สิ่งนี้และไม่ได้รับคำตอบที่ถูกต้อง คุณควรเรียก invCount (intArray) ภายใน main เพื่อเริ่มต้นหรือไม่? ด้วย intArray เป็นอาร์เรย์ที่ไม่เรียงลำดับของ int? ฉันรันมันด้วยอาร์เรย์ของจำนวนเต็มและได้ -1887062008 เป็นคำตอบของฉัน ผมทำอะไรผิดหรือเปล่า?
จุดใกล้เคียง

4
+1 ดูวิธีแก้ปัญหาที่คล้ายกันในC ++ 11รวมถึงโซลูชันที่ใช้ตัววนซ้ำทั่วไปและตัวอย่างการทดสอบแบบสุ่มโดยใช้ลำดับจาก 5-25 องค์ประกอบ สนุก!.
WhozCraig

3
นี่ไม่ใช่วิธีแก้ปัญหา ฉันลองรันแล้วและให้ผลลัพธ์ที่ไม่ถูกต้อง
mirgee

2
ขออภัยสำหรับคำถาม newbish แต่การเพิ่มleft.length - iตัวนับผกผันคืออะไร? ฉันคิดว่ามันสมเหตุสมผลแล้วที่จะเพิ่ม 1 เนื่องจากคุณตกอยู่ในกรณีตรรกะที่การเปรียบเทียบระหว่างสอง subarrays มีองค์ประกอบอาร์เรย์ด้านซ้ายที่ใหญ่กว่าด้านขวา ใครก็ได้ช่วยอธิบายให้ฟังหน่อยว่าฉันอายุ 5 ขวบไหม?
Alfredo Gallegos

2
@AlfredoGallegos ภาพประกอบสั้น ๆ ของคำตอบของ Marek พิจารณาสองอาร์เรย์: [6, 8] และ [4, 5] เมื่อคุณเห็นว่า 6 มีค่ามากกว่า 4 คุณใช้เวลา 4 arrและวางไว้ใน แต่มันไม่ใช่การผกผันเดียว คุณพบการผกผันสำหรับองค์ประกอบทั้งหมดในอาร์เรย์ด้านซ้ายซึ่งมากกว่า 6 ในกรณีของเรามันมี 8 ด้วย ดังนั้น 2 จะถูกเพิ่มซึ่งจะมีค่าเท่ากับcount left.length - i
ilya

86

ฉันพบมันในเวลา O (n * log n) โดยวิธีการต่อไปนี้

  1. ผสานอาร์เรย์เรียงลำดับ A และสร้างสำเนา (อาร์เรย์ B)
  2. ใช้ A [1] และค้นหาตำแหน่งในอาร์เรย์ที่เรียง B ผ่านการค้นหาแบบไบนารี จำนวนการผกผันขององค์ประกอบนี้จะน้อยกว่าหมายเลขดัชนีของตำแหน่งใน B หนึ่งตัวเนื่องจากตัวเลขที่ต่ำกว่าทุกตัวที่ปรากฏหลังองค์ประกอบแรกของ A จะเป็นการผกผัน

    2a. สะสมจำนวนการผกผันเพื่อตอบโต้ตัวแปร num_inversions

    2b. ลบ A [1] ออกจากอาร์เรย์ A และจากตำแหน่งที่เกี่ยวข้องในอาร์เรย์ B

  3. เริ่มต้นใหม่จากขั้นตอนที่ 2 จนกว่าจะไม่มีองค์ประกอบใน A อีกต่อไป

นี่คือตัวอย่างของอัลกอริทึมนี้ อาร์เรย์เดิม A = (6, 9, 1, 14, 8, 12, 3, 2)

1: ผสานการเรียงลำดับและคัดลอกไปยังอาร์เรย์ B

B = (1, 2, 3, 6, 8, 9, 12, 14)

2: ใช้ A [1] และค้นหาไบนารีเพื่อค้นหาในอาร์เรย์ B

ก [1] = 6

B = (1, 2, 3, 6 , 8, 9, 12, 14)

6 อยู่ในตำแหน่งที่ 4 ของอาร์เรย์ B ดังนั้นจึงมีการผกผัน 3 ครั้ง เรารู้สิ่งนี้เนื่องจาก 6 อยู่ในตำแหน่งแรกในอาร์เรย์ A ดังนั้นองค์ประกอบที่มีค่าต่ำกว่าใด ๆ ที่ปรากฏในภายหลังในอาร์เรย์ A จะมีดัชนีเป็น j> i (เนื่องจาก i ในกรณีนี้คือ 1)

2.b: ลบ A [1] ออกจากอาร์เรย์ A และจากตำแหน่งที่เกี่ยวข้องในอาร์เรย์ B (องค์ประกอบตัวหนาจะถูกลบออก)

A = ( 6, 9, 1, 14, 8, 12, 3, 2) = (9, 1, 14, 8, 12, 3, 2)

B = (1, 2, 3, 6, 8, 9, 12, 14) = (1, 2, 3, 8, 9, 12, 14)

3: รีรันจากขั้นตอนที่ 2 บนอาร์เรย์ A และ B ใหม่

ก [1] = 9

B = (1, 2, 3, 8, 9, 12, 14)

ตอนนี้ 9 อยู่ในตำแหน่งที่ 5 ของอาร์เรย์ B ดังนั้นจึงมีการผกผัน 4 ครั้ง เรารู้สิ่งนี้เนื่องจาก 9 อยู่ในตำแหน่งแรกในอาร์เรย์ A ดังนั้นองค์ประกอบที่มีค่าต่ำกว่าใด ๆ ที่ปรากฏในภายหลังจะมีดัชนี j> i (เนื่องจาก i ในกรณีนี้คือ 1 อีกครั้ง) ลบ A [1] ออกจากอาร์เรย์ A และจากตำแหน่งที่เกี่ยวข้องในอาร์เรย์ B (องค์ประกอบตัวหนาจะถูกลบออก)

A = ( 9 , 1, 14, 8, 12, 3, 2) = (1, 14, 8, 12, 3, 2)

B = (1, 2, 3, 8, 9 , 12, 14) = (1, 2, 3, 8, 12, 14)

การดำเนินการต่อในหลอดเลือดดำนี้จะทำให้เรามีจำนวนการผกผันทั้งหมดสำหรับอาร์เรย์ A เมื่อการวนซ้ำเสร็จสมบูรณ์

ขั้นตอนที่ 1 (ผสานการเรียงลำดับ) จะใช้ O (n * log n) เพื่อดำเนินการ ขั้นตอนที่ 2 จะดำเนินการ n ครั้งและในการดำเนินการแต่ละครั้งจะทำการค้นหาแบบไบนารีที่ใช้ O (log n) เพื่อรันรวม O (n * log n) เวลาทำงานทั้งหมดจะเป็น O (n * log n) + O (n * log n) = O (n * log n)

ขอบคุณสำหรับความช่วยเหลือของคุณ. การเขียนอาร์เรย์ตัวอย่างลงบนกระดาษช่วยให้มองเห็นปัญหาได้จริงๆ


1
เหตุใดจึงใช้การเรียงลำดับผสานไม่ใช่การเรียงลำดับด่วน
Alcott

5
@Alcott Quick sort มีเวลาทำงานที่แย่ที่สุดของ O (n ^ 2) เมื่อเรียงลำดับรายการแล้วและจะเลือกเดือยแรกทุกรอบ กรณีที่เลวร้ายที่สุดของ Merge sort คือ O (n log n)
user482594

29
ขั้นตอนการลบออกจากอาร์เรย์มาตรฐานทำให้อัลกอริทึมของคุณ O (n ^ 2) เนื่องจากการเปลี่ยนค่า (นั่นคือเหตุผลที่การเรียงลำดับการแทรกคือ O (n ^ 2))
Kyle Butt

เริ่มต้นด้วยองค์ประกอบแรกของอาร์เรย์ B และการนับองค์ประกอบก่อนในอาร์เรย์ A ก็จะให้ผลลัพธ์เช่นเดียวกันหากคุณกำจัดองค์ประกอบเหล่านี้ตามที่คุณอธิบายไว้ในคำตอบของคุณ
tutak

@el diablo วิธีลบองค์ประกอบเพื่อหลีกเลี่ยงความซับซ้อน n ^ 2 ??
Jerky

26

ใน Python

# O(n log n)

def count_inversion(lst):
    return merge_count_inversion(lst)[1]

def merge_count_inversion(lst):
    if len(lst) <= 1:
        return lst, 0
    middle = int( len(lst) / 2 )
    left, a = merge_count_inversion(lst[:middle])
    right, b = merge_count_inversion(lst[middle:])
    result, c = merge_count_split_inversion(left, right)
    return result, (a + b + c)

def merge_count_split_inversion(left, right):
    result = []
    count = 0
    i, j = 0, 0
    left_len = len(left)
    while i < left_len and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            count += left_len - i
            j += 1
    result += left[i:]
    result += right[j:]
    return result, count        


#test code
input_array_1 = []  #0
input_array_2 = [1] #0
input_array_3 = [1, 5]  #0
input_array_4 = [4, 1] #1
input_array_5 = [4, 1, 2, 3, 9] #3
input_array_6 = [4, 1, 3, 2, 9, 5]  #5
input_array_7 = [4, 1, 3, 2, 9, 1]  #8

print count_inversion(input_array_1)
print count_inversion(input_array_2)
print count_inversion(input_array_3)
print count_inversion(input_array_4)
print count_inversion(input_array_5)
print count_inversion(input_array_6)
print count_inversion(input_array_7)

13
ฉันงงงันโดยวิธีการจัดการนี้จะได้รับ 13 - ฉันไม่ได้มีฝีมือโดยเฉพาะอย่างยิ่งในหลาม แต่ดูเหมือนว่าสวยมากเช่นเดียวกับรุ่น Java นำเสนอ 2 ปีก่อนยกเว้นที่ว่านี้ไม่ได้ให้คำอธิบายใด ๆ การโพสต์คำตอบในภาษาอื่น ๆ เป็นอันตรายอย่างยิ่ง IMO - อาจมีหลายพันภาษาหากไม่มากกว่านั้น - ฉันหวังว่าจะไม่มีใครโต้แย้งว่าเราควรโพสต์คำตอบหลายพันคำถามสำหรับคำถาม - Stack Exchangeไม่ได้สร้างขึ้นเพื่อสิ่งนั้น .
Bernhard Barker

1
@tennenrishin โอเคอาจจะไม่ถึงพัน แต่เราจะลากเส้นตรงไหน? ขณะนี้มีคำตอบสิบข้อที่ให้แนวทางเดียวกันแล้ว นั่นคือประมาณ43% ของคำตอบ (ไม่รวมคำตอบที่ไม่ใช่คำตอบ) - มีพื้นที่ว่างพอสมควรเนื่องจากมีแนวทางอื่น ๆ อีกครึ่งโหลที่นำเสนอที่นี่ แม้ว่าจะมีเพียง 2 คำตอบสำหรับแนวทางเดียวกัน แต่ก็ยังคงเจือจางคำตอบโดยไม่จำเป็น และฉันได้โต้แย้งที่ดีสำหรับคำตอบนี้โดยเฉพาะว่าไม่มีประโยชน์ในความคิดเห็นก่อนหน้าของฉัน
Bernhard Barker

3
@Dukeling เช่นคุณฉันไม่คุ้นเคยกับ Python และคุ้นเคยกับ Java มากกว่า ฉันพบว่าโซลูชันนี้อ่านได้น้อยกว่า Java มาก เป็นไปตามเหตุผลที่ว่าสำหรับบางคนการสนทนาอาจเป็นจริงในระดับเดียวกัน
Museful

3
สำหรับผู้ใช้ส่วนใหญ่ python อยู่ใกล้กับรหัส sudo ฉันพบว่าสิ่งนี้อ่านได้ง่ายกว่า java แม้ว่าจะไม่มีคำอธิบายก็ตาม ฉันไม่เห็นความจำเป็นที่จะต้องสร้างความรำคาญใจให้กับผู้อ่านบางคน
Francisco Vargas

2
โซลูชันนี้เหมาะอย่างยิ่งและสามารถอ่านได้สำหรับผู้ใช้ python ผู้คนต้องการดูว่าคนอื่นนำสิ่งนี้ไปใช้ใน Python อย่างไร
Aerin

24

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

def count_inversions(a):
  res = 0
  counts = [0]*(len(a)+1)
  rank = { v : i+1 for i, v in enumerate(sorted(a)) }
  for x in reversed(a):
    i = rank[x] - 1
    while i:
      res += counts[i]
      i -= i & -i
    i = rank[x]
    while i <= len(a):
      counts[i] += 1
      i += i & -i
  return res

ความซับซ้อนคือ O (n log n) และปัจจัยคงที่ต่ำมาก


น่าจะเป็นแนวทางที่ดีที่สุด :)
Nilutpal Borgohain

@NilutpalBorgohain ขอบคุณ :) ดูเหมือนว่าจะต้องใช้รหัสน้อยที่สุดในบรรดาผู้สมัคร O (n log n) เป็นอย่างน้อย
Niklas B.

1
ขอบคุณสำหรับสิ่งนี้. ความหมายของi -= i & -iเส้นคืออะไร? และในทำนองเดียวกันi += i & -i
เจอราร์ดคอนดอน

1
@GerardCondon นั่นคือโครงสร้างข้อมูล BIT ลิงก์อธิบายสามารถพบได้ในคำตอบ
Niklas B.

TIL เกี่ยวกับต้นไม้ Fenwick ขอบคุณ! ฉันได้โพสต์คำตอบที่timeitเปรียบเทียบคำตอบของ Python ทั้งหมดสำหรับคำถามนี้ดังนั้นจึงมีโค้ดของคุณด้วย คุณอาจสนใจดูผลการจับเวลา
PM 2Ring

14

ฉันมีคำถามคล้าย ๆ นี้สำหรับการบ้านจริงๆ ฉันถูก จำกัด ว่าต้องมีประสิทธิภาพ O (nlogn)

ฉันใช้ความคิดที่คุณเสนอให้ใช้ Mergesort เนื่องจากมีประสิทธิภาพที่ถูกต้องอยู่แล้ว ฉันเพิ่งใส่รหัสบางส่วนลงในฟังก์ชันการรวมที่เป็นพื้นฐาน: เมื่อใดก็ตามที่มีการเพิ่มตัวเลขจากอาร์เรย์ทางด้านขวาไปยังอาร์เรย์เอาต์พุตฉันจะเพิ่มจำนวนการผกผันทั้งหมดจำนวนตัวเลขที่เหลืออยู่ในอาร์เรย์ด้านซ้าย

สิ่งนี้ทำให้ฉันรู้สึกได้มากว่าตอนนี้ฉันคิดเรื่องนี้มามากพอแล้ว การนับจำนวนครั้งที่มีจำนวนมากกว่ามาก่อนตัวเลขใด ๆ

hth.


6
ฉันสนับสนุนคำตอบของคุณความแตกต่างที่สำคัญจากการเรียงลำดับการผสานอยู่ในฟังก์ชันผสานเมื่อองค์ประกอบของอาร์เรย์ด้านขวาที่ 2 ถูกคัดลอกไปยังอาร์เรย์เอาต์พุต => ตัวนับการผกผันที่เพิ่มขึ้นตามจำนวนองค์ประกอบที่เหลืออยู่ในอาร์เรย์ซ้ายที่ 1
Alex Salnikov

11

จุดประสงค์หลักของคำตอบนี้คือเพื่อเปรียบเทียบความเร็วของ Python เวอร์ชันต่างๆที่พบที่นี่ แต่ฉันก็มีส่วนร่วมของฉันเองด้วย (FWIW ฉันเพิ่งค้นพบคำถามนี้ในขณะที่ทำการค้นหาซ้ำ)

ความเร็วในการดำเนินการสัมพัทธ์ของอัลกอริทึมที่ใช้ใน CPython อาจแตกต่างจากที่คาดหวังจากการวิเคราะห์อัลกอริทึมอย่างง่ายและจากประสบการณ์ในภาษาอื่น ๆ นั่นเป็นเพราะ Python มีฟังก์ชั่นและวิธีการที่มีประสิทธิภาพมากมายที่ใช้ใน C ซึ่งสามารถทำงานในรายการและคอลเล็กชันอื่น ๆ ที่ความเร็วใกล้เคียงกับที่จะได้รับในภาษาที่คอมไพล์ครบถ้วนดังนั้นการดำเนินการเหล่านั้นจึงทำงานได้เร็วกว่าอัลกอริทึมที่เทียบเท่ากันที่ใช้ "ด้วยตนเอง" ด้วย Python รหัส.

โค้ดที่ใช้ประโยชน์จากเครื่องมือเหล่านี้มักจะมีประสิทธิภาพเหนือกว่าอัลกอริทึมที่เหนือกว่าในทางทฤษฎีซึ่งพยายามทำทุกอย่างด้วยการดำเนินการของ Python ในแต่ละรายการของคอลเล็กชัน แน่นอนว่าปริมาณข้อมูลที่ประมวลผลจริงก็มีผลกระทบเช่นกัน แต่สำหรับข้อมูลจำนวนปานกลางโค้ดที่ใช้อัลกอริทึม O (n²) ที่ทำงานด้วยความเร็ว C สามารถเอาชนะอัลกอริทึม O (n log n) ที่ทำงานจำนวนมากกับการดำเนินการ Python แต่ละรายการได้อย่างง่ายดาย

คำตอบที่โพสต์จำนวนมากสำหรับคำถามการนับแบบผกผันนี้ใช้อัลกอริทึมที่อ้างอิงจากการผสาน ในทางทฤษฎีนี่เป็นแนวทางที่ดีเว้นแต่ขนาดอาร์เรย์จะเล็กมาก แต่TimSortในตัวของ Python (อัลกอริธึมการเรียงลำดับที่เสถียรแบบไฮบริดซึ่งได้มาจากการผสานการเรียงลำดับและการเรียงลำดับการแทรก) ทำงานด้วยความเร็ว C และการผสานที่เข้ารหัสด้วยมือใน Python ไม่สามารถหวังว่าจะแข่งขันกับความเร็วได้

หนึ่งในวิธีแก้ปัญหาที่น่าสนใจมากขึ้นที่นี่ในคำตอบที่โพสต์โดย Niklas Bใช้การจัดเรียงในตัวเพื่อกำหนดการจัดอันดับของรายการอาร์เรย์และต้นไม้ที่จัดทำดัชนีแบบไบนารี (หรือที่รู้จักกันในชื่อ Fenwick tree) เพื่อเก็บผลรวมสะสมที่จำเป็นในการคำนวณการผกผัน นับ. ในกระบวนการพยายามทำความเข้าใจโครงสร้างข้อมูลนี้และอัลกอริทึมของ Niklas ฉันได้เขียนรูปแบบต่างๆของตัวเอง (โพสต์ด้านล่าง) แต่ฉันยังค้นพบว่าสำหรับรายการขนาดปานกลางการใช้ฟังก์ชันในตัวของ Python นั้นเร็วsumกว่าต้นไม้ Fenwick ที่น่ารัก

def count_inversions(a):
    total = 0
    counts = [0] * len(a)
    rank = {v: i for i, v in enumerate(sorted(a))}
    for u in reversed(a):
        i = rank[u]
        total += sum(counts[:i])
        counts[i] += 1
    return total

ในที่สุดเมื่อขนาดรายการสูงขึ้นประมาณ 500 ด้าน O (n²) ของการโทรsumภายในforลูปนั้นจะกลับมาเป็นส่วนหัวที่น่าเกลียดและประสิทธิภาพก็เริ่มลดลง

การผสานไม่ได้เป็นการจัดเรียง O (nlogn) เพียงอย่างเดียวและอื่น ๆ อีกหลายรายการอาจใช้เพื่อทำการนับแบบผกผัน คำตอบของ prasadvkใช้การเรียงลำดับต้นไม้ไบนารีอย่างไรก็ตามรหัสของเขาดูเหมือนจะอยู่ใน C ++ หรืออนุพันธ์อย่างใดอย่างหนึ่ง ฉันได้เพิ่มเวอร์ชัน Python แล้ว เดิมทีฉันใช้คลาสเพื่อใช้โหนดต้นไม้ แต่พบว่าการเขียนตามลำดับนั้นเร็วกว่าอย่างเห็นได้ชัด ในที่สุดฉันก็ใช้รายการซึ่งเร็วกว่าแม้ว่าจะทำให้โค้ดอ่านได้น้อยลง

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

การเรียงลำดับ O (nlogn) อีกแบบหนึ่งคือการเรียงลำดับเลขที่เคารพ ข้อได้เปรียบที่สำคัญคือไม่ได้เปรียบเทียบคีย์ซึ่งกันและกัน ข้อเสียของมันก็คือการทำงานที่ดีที่สุดในลำดับต่อเนื่องกันของจำนวนเต็มนึกคิดเปลี่ยนแปลงของจำนวนเต็มในrange(b**m)ที่bเป็นปกติ 2. ฉันเพิ่มไม่กี่รุ่นที่มีพื้นฐานจากสมุฎฐานการเรียงลำดับหลังจากที่พยายามที่จะอ่านInversions นับออฟไลน์ฉากช่วงการนับและปัญหาที่เกี่ยวข้องซึ่งเป็น เชื่อมโยงในการคำนวณจำนวนของ“inversions” ในการเปลี่ยนแปลง

ที่จะใช้กี่เรียงลำดับได้อย่างมีประสิทธิภาพในการนับ inversions ในลำดับทั่วไปseqยาว n เราสามารถสร้างการเปลี่ยนแปลงของrange(n)ที่มีหมายเลขเดียวกันของ inversions seqเป็น เราสามารถทำได้ในเวลา (ที่แย่ที่สุด) O (nlogn) ผ่าน TimSort เคล็ดลับคือการเปลี่ยนรูปดัชนีของโดยการเรียงลำดับseq seqง่ายกว่าที่จะอธิบายเรื่องนี้ด้วยตัวอย่างเล็ก ๆ

seq = [15, 14, 11, 12, 10, 13]
b = [t[::-1] for t in enumerate(seq)]
print(b)
b.sort()
print(b)

เอาท์พุท

[(15, 0), (14, 1), (11, 2), (12, 3), (10, 4), (13, 5)]
[(10, 4), (11, 2), (12, 3), (13, 5), (14, 1), (15, 0)]

โดยการเรียงลำดับคู่ (ค่าดัชนี) ของseqเราได้กำหนดดัชนีที่seqมีจำนวนสว็อปเท่ากันที่จำเป็นต้องใส่seqในลำดับเดิมจากลำดับที่เรียง เราสามารถสร้างการเรียงสับเปลี่ยนโดยการจัดเรียงrange(n)ด้วยฟังก์ชันคีย์ที่เหมาะสม:

print(sorted(range(len(seq)), key=lambda k: seq[k]))

เอาท์พุท

[4, 2, 3, 5, 1, 0]

เราสามารถหลีกเลี่ยงlambdaโดยใช้seq's .__getitem__วิธีการ:

sorted(range(len(seq)), key=seq.__getitem__)

นี่เร็วขึ้นเพียงเล็กน้อย แต่เรากำลังมองหาการปรับปรุงความเร็วทั้งหมดที่เราจะได้รับ ;)


โค้ดด้านล่างทำการtimeitทดสอบกับอัลกอริทึม Python ที่มีอยู่ทั้งหมดในหน้านี้รวมถึงเวอร์ชันของฉันเอง: เวอร์ชัน O (n²) กำลังดุร้ายสองสามรูปแบบในอัลกอริทึมของ Niklas B และแน่นอนอีกแบบหนึ่งตามการผสาน (ซึ่งฉันเขียนโดยไม่ได้อ้างถึงคำตอบที่มีอยู่) นอกจากนี้ยังมีรหัสต้นไม้ตามรายการของฉันโดยประมาณได้มาจากรหัสของ prasadvk และฟังก์ชั่นต่าง ๆ ตามการเรียงลำดับเรดิกซ์บางส่วนใช้กลยุทธ์ที่คล้ายกันกับวิธีการผสานและบางส่วนใช้sumหรือต้นไม้เฟนวิก

โปรแกรมนี้จะวัดเวลาดำเนินการของแต่ละฟังก์ชันในชุดของรายการจำนวนเต็มแบบสุ่ม นอกจากนี้ยังสามารถตรวจสอบได้ว่าแต่ละฟังก์ชันให้ผลลัพธ์เหมือนกับฟังก์ชันอื่น ๆ และไม่ได้แก้ไขรายการอินพุต

การtimeitโทรแต่ละครั้งให้เวกเตอร์ที่มี 3 ผลลัพธ์ซึ่งฉันเรียงลำดับ มูลค่าหลักในการดูที่นี่เป็นหนึ่งในขั้นต่ำค่าอื่น ๆ เพียงให้ข้อบ่งชี้ของวิธีการที่เชื่อถือได้ว่าค่าต่ำสุดคือตามที่กล่าวไว้ในหมายเหตุในเอกสารโมดูลtimeit

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

เอาต์พุตมาจาก 3 รันบนเครื่อง 2GHz 32 บิต single core ของฉันที่ใช้ Python 3.6.0 บน Debian-derivative distro รุ่นเก่า YMMV. ในระหว่างการทดสอบฉันปิดเว็บเบราว์เซอร์และตัดการเชื่อมต่อจากเราเตอร์เพื่อลดผลกระทบของงานอื่น ๆ บน CPU

การรันครั้งแรกจะทดสอบฟังก์ชันทั้งหมดที่มีขนาดรายการตั้งแต่ 5 ถึง 320 โดยมีขนาดลูปตั้งแต่ 4096 ถึง 64 (เนื่องจากขนาดรายการเป็นสองเท่าขนาดของลูปจะลดลงครึ่งหนึ่ง) พูลแบบสุ่มที่ใช้สร้างแต่ละรายการมีขนาดครึ่งหนึ่งของรายการเองดังนั้นเราจึงมีโอกาสที่จะได้รับข้อมูลซ้ำจำนวนมาก อัลกอริทึมการนับแบบผกผันบางอย่างมีความไวต่อการทำซ้ำมากกว่าวิธีอื่น ๆ

การรันครั้งที่สองใช้รายการที่ใหญ่กว่า: 640 ถึง 10240 และขนาดลูปคงที่เท่ากับ 8 เพื่อประหยัดเวลาจะกำจัดฟังก์ชันที่ช้าที่สุดหลายอย่างออกจากการทดสอบ ฉันแรงเดรัจฉาน O (n²) ฟังก์ชั่นนี้เป็นเพียงวิธีที่ช้าเกินไปที่มีขนาดเหล่านี้และเป็นที่กล่าวถึงก่อนหน้านี้รหัสของฉันที่ใช้sumซึ่งจะให้ดีขนาดเล็กถึงปานกลางรายการก็ไม่สามารถให้ทันกับรายการใหญ่

การรันขั้นสุดท้ายครอบคลุมขนาดรายการตั้งแต่ 20480 ถึง 655360 และขนาดลูปคงที่ 4 พร้อมด้วยฟังก์ชันที่เร็วที่สุด 8 ฟังก์ชัน สำหรับรายการขนาดที่ต่ำกว่า 40,000 หรือมากกว่านั้นรหัสของ Tim Babych เป็นผู้ชนะที่ชัดเจน ทิมทำได้ดีมาก! รหัสของ Niklas B เป็นนักแสดงที่เก่งรอบด้านเช่นกันแม้ว่าจะถูกเอาชนะในรายการเล็ก ๆ รหัสที่ใช้การแบ่งส่วนของ "python" ก็ทำได้ค่อนข้างดีเช่นกันแม้ว่าจะดูช้ากว่าเล็กน้อยกับรายการขนาดใหญ่ที่มีรายการซ้ำกันจำนวนมากอาจเป็นเพราะการwhileวนซ้ำเชิงเส้นที่ใช้เพื่อข้ามสิ่งที่ซ้ำกัน

อย่างไรก็ตามสำหรับขนาดรายการที่ใหญ่มากอัลกอริทึมแบบแบ่งส่วนจะไม่สามารถแข่งขันกับอัลกอริทึม O (nlogn) ที่แท้จริงได้

#!/usr/bin/env python3

''' Test speeds of various ways of counting inversions in a list

    The inversion count is a measure of how sorted an array is.
    A pair of items in a are inverted if i < j but a[j] > a[i]

    See /programming/337664/counting-inversions-in-an-array

    This program contains code by the following authors:
    mkso
    Niklas B
    B. M.
    Tim Babych
    python
    Zhe Hu
    prasadvk
    noman pouigt
    PM 2Ring

    Timing and verification code by PM 2Ring
    Collated 2017.12.16
    Updated 2017.12.21
'''

from timeit import Timer
from random import seed, randrange
from bisect import bisect, insort_left

seed('A random seed string')

# Merge sort version by mkso
def count_inversion_mkso(lst):
    return merge_count_inversion(lst)[1]

def merge_count_inversion(lst):
    if len(lst) <= 1:
        return lst, 0
    middle = len(lst) // 2
    left, a = merge_count_inversion(lst[:middle])
    right, b = merge_count_inversion(lst[middle:])
    result, c = merge_count_split_inversion(left, right)
    return result, (a + b + c)

def merge_count_split_inversion(left, right):
    result = []
    count = 0
    i, j = 0, 0
    left_len = len(left)
    while i < left_len and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            count += left_len - i
            j += 1
    result += left[i:]
    result += right[j:]
    return result, count

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Using a Binary Indexed Tree, aka a Fenwick tree, by Niklas B.
def count_inversions_NiklasB(a):
    res = 0
    counts = [0] * (len(a) + 1)
    rank = {v: i for i, v in enumerate(sorted(a), 1)}
    for x in reversed(a):
        i = rank[x] - 1
        while i:
            res += counts[i]
            i -= i & -i
        i = rank[x]
        while i <= len(a):
            counts[i] += 1
            i += i & -i
    return res

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Merge sort version by B.M
# Modified by PM 2Ring to deal with the global counter
bm_count = 0

def merge_count_BM(seq):
    global bm_count
    bm_count = 0
    sort_bm(seq)
    return bm_count

def merge_bm(l1,l2):
    global bm_count
    l = []
    while l1 and l2:
        if l1[-1] <= l2[-1]:
            l.append(l2.pop())
        else:
            l.append(l1.pop())
            bm_count += len(l2)
    l.reverse()
    return l1 + l2 + l

def sort_bm(l):
    t = len(l) // 2
    return merge_bm(sort_bm(l[:t]), sort_bm(l[t:])) if t > 0 else l

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Bisection based method by Tim Babych
def solution_TimBabych(A):
    sorted_left = []
    res = 0
    for i in range(1, len(A)):
        insort_left(sorted_left, A[i-1])
        # i is also the length of sorted_left
        res += (i - bisect(sorted_left, A[i]))
    return res

# Slightly faster, except for very small lists
def solutionE_TimBabych(A):
    res = 0
    sorted_left = []
    for i, u in enumerate(A):
        # i is also the length of sorted_left
        res += (i - bisect(sorted_left, u))
        insort_left(sorted_left, u)
    return res

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Bisection based method by "python"
def solution_python(A):
    B = list(A)
    B.sort()
    inversion_count = 0
    for i in range(len(A)):
        j = binarySearch_python(B, A[i])
        while B[j] == B[j - 1]:
            if j < 1:
                break
            j -= 1
        inversion_count += j
        B.pop(j)
    return inversion_count

def binarySearch_python(alist, item):
    first = 0
    last = len(alist) - 1
    found = False
    while first <= last and not found:
        midpoint = (first + last) // 2
        if alist[midpoint] == item:
            return midpoint
        else:
            if item < alist[midpoint]:
                last = midpoint - 1
            else:
                first = midpoint + 1

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Merge sort version by Zhe Hu
def inv_cnt_ZheHu(a):
    _, count = inv_cnt(a.copy())
    return count

def inv_cnt(a):
    n = len(a)
    if n==1:
        return a, 0
    left = a[0:n//2] # should be smaller
    left, cnt1 = inv_cnt(left)
    right = a[n//2:] # should be larger
    right, cnt2 = inv_cnt(right)

    cnt = 0
    i_left = i_right = i_a = 0
    while i_a < n:
        if (i_right>=len(right)) or (i_left < len(left)
            and left[i_left] <= right[i_right]):
            a[i_a] = left[i_left]
            i_left += 1
        else:
            a[i_a] = right[i_right]
            i_right += 1
            if i_left < len(left):
                cnt += len(left) - i_left
        i_a += 1
    return (a, cnt1 + cnt2 + cnt)

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# Merge sort version by noman pouigt
# From https://stackoverflow.com/q/47830098
def reversePairs_nomanpouigt(nums):
    def merge(left, right):
        if not left or not right:
            return (0, left + right)
        #if everything in left is less than right
        if left[len(left)-1] < right[0]:
            return (0, left + right)
        else:
            left_idx, right_idx, count = 0, 0, 0
            merged_output = []

            # check for condition before we merge it
            while left_idx < len(left) and right_idx < len(right):
                #if left[left_idx] > 2 * right[right_idx]:
                if left[left_idx] > right[right_idx]:
                    count += len(left) - left_idx
                    right_idx += 1
                else:
                    left_idx += 1

            #merging the sorted list
            left_idx, right_idx = 0, 0
            while left_idx < len(left) and right_idx < len(right):
                if left[left_idx] > right[right_idx]:
                    merged_output += [right[right_idx]]
                    right_idx += 1
                else:
                    merged_output += [left[left_idx]]
                    left_idx += 1
            if left_idx == len(left):
                merged_output += right[right_idx:]
            else:
                merged_output += left[left_idx:]
        return (count, merged_output)

    def partition(nums):
        count = 0
        if len(nums) == 1 or not nums:
            return (0, nums)
        pivot = len(nums)//2
        left_count, l = partition(nums[:pivot])
        right_count, r = partition(nums[pivot:])
        temp_count, temp_list = merge(l, r)
        return (temp_count + left_count + right_count, temp_list)
    return partition(nums)[0]

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# PM 2Ring
def merge_PM2R(seq):
    seq, count = merge_sort_count_PM2R(seq)
    return count

def merge_sort_count_PM2R(seq):
    mid = len(seq) // 2
    if mid == 0:
        return seq, 0
    left, left_total = merge_sort_count_PM2R(seq[:mid])
    right, right_total = merge_sort_count_PM2R(seq[mid:])
    total = left_total + right_total
    result = []
    i = j = 0
    left_len, right_len = len(left), len(right)
    while i < left_len and j < right_len:
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
            total += left_len - i
    result.extend(left[i:])
    result.extend(right[j:])
    return result, total

def rank_sum_PM2R(a):
    total = 0
    counts = [0] * len(a)
    rank = {v: i for i, v in enumerate(sorted(a))}
    for u in reversed(a):
        i = rank[u]
        total += sum(counts[:i])
        counts[i] += 1
    return total

# Fenwick tree functions adapted from C code on Wikipedia
def fen_sum(tree, i):
    ''' Return the sum of the first i elements, 0 through i-1 '''
    total = 0
    while i:
        total += tree[i-1]
        i -= i & -i
    return total

def fen_add(tree, delta, i):
    ''' Add delta to element i and thus 
        to fen_sum(tree, j) for all j > i 
    '''
    size = len(tree)
    while i < size:
        tree[i] += delta
        i += (i+1) & -(i+1)

def fenwick_PM2R(a):
    total = 0
    counts = [0] * len(a)
    rank = {v: i for i, v in enumerate(sorted(a))}
    for u in reversed(a):
        i = rank[u]
        total += fen_sum(counts, i)
        fen_add(counts, 1, i)
    return total

def fenwick_inline_PM2R(a):
    total = 0
    size = len(a)
    counts = [0] * size
    rank = {v: i for i, v in enumerate(sorted(a))}
    for u in reversed(a):
        i = rank[u]
        j = i + 1
        while i:
            total += counts[i]
            i -= i & -i
        while j < size:
            counts[j] += 1
            j += j & -j
    return total

def bruteforce_loops_PM2R(a):
    total = 0
    for i in range(1, len(a)):
        u = a[i]
        for j in range(i):
            if a[j] > u:
                total += 1
    return total

def bruteforce_sum_PM2R(a):
    return sum(1 for i in range(1, len(a)) for j in range(i) if a[j] > a[i])

# Using binary tree counting, derived from C++ code (?) by prasadvk
# https://stackoverflow.com/a/16056139
def ltree_count_PM2R(a):
    total, root = 0, None
    for u in a:
        # Store data in a list-based tree structure
        # [data, count, left_child, right_child]
        p = [u, 0, None, None]
        if root is None:
            root = p
            continue
        q = root
        while True:
            if p[0] < q[0]:
                total += 1 + q[1]
                child = 2
            else:
                q[1] += 1
                child = 3
            if q[child]:
                q = q[child]
            else:
                q[child] = p
                break
    return total

# Counting based on radix sort, recursive version
def radix_partition_rec(a, L):
    if len(a) < 2:
        return 0
    if len(a) == 2:
        return a[1] < a[0]
    left, right = [], []
    count = 0
    for u in a:
        if u & L:
            right.append(u)
        else:
            count += len(right)
            left.append(u)
    L >>= 1
    if L:
        count += radix_partition_rec(left, L) + radix_partition_rec(right, L)
    return count

# The following functions determine swaps using a permutation of 
# range(len(a)) that has the same inversion count as `a`. We can create
# this permutation with `sorted(range(len(a)), key=lambda k: a[k])`
# but `sorted(range(len(a)), key=a.__getitem__)` is a little faster.

# Counting based on radix sort, iterative version
def radix_partition_iter(seq, L):
    count = 0
    parts = [seq]
    while L and parts:
        newparts = []
        for a in parts:
            if len(a) < 2:
                continue
            if len(a) == 2:
                count += a[1] < a[0]
                continue
            left, right = [], []
            for u in a:
                if u & L:
                    right.append(u)
                else:
                    count += len(right)
                    left.append(u)
            if left:
                newparts.append(left)
            if right:
                newparts.append(right)
        parts = newparts
        L >>= 1
    return count

def perm_radixR_PM2R(a):
    size = len(a)
    b = sorted(range(size), key=a.__getitem__)
    n = size.bit_length() - 1
    return radix_partition_rec(b, 1 << n)

def perm_radixI_PM2R(a):
    size = len(a)
    b = sorted(range(size), key=a.__getitem__)
    n = size.bit_length() - 1
    return radix_partition_iter(b, 1 << n)

# Plain sum of the counts of the permutation
def perm_sum_PM2R(a):
    total = 0
    size = len(a)
    counts = [0] * size
    for i in reversed(sorted(range(size), key=a.__getitem__)):
        total += sum(counts[:i])
        counts[i] = 1
    return total

# Fenwick sum of the counts of the permutation
def perm_fenwick_PM2R(a):
    total = 0
    size = len(a)
    counts = [0] * size
    for i in reversed(sorted(range(size), key=a.__getitem__)):
        j = i + 1
        while i:
            total += counts[i]
            i -= i & -i
        while j < size:
            counts[j] += 1
            j += j & -j
    return total

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# All the inversion-counting functions
funcs = (
    solution_TimBabych,
    solutionE_TimBabych,
    solution_python,
    count_inversion_mkso,
    count_inversions_NiklasB,
    merge_count_BM,
    inv_cnt_ZheHu,
    reversePairs_nomanpouigt,
    fenwick_PM2R,
    fenwick_inline_PM2R,
    merge_PM2R,
    rank_sum_PM2R,
    bruteforce_loops_PM2R,
    bruteforce_sum_PM2R,
    ltree_count_PM2R,
    perm_radixR_PM2R,
    perm_radixI_PM2R,
    perm_sum_PM2R,
    perm_fenwick_PM2R,
)

def time_test(seq, loops, verify=False):
    orig = seq
    timings = []
    for func in funcs:
        seq = orig.copy()
        value = func(seq) if verify else None
        t = Timer(lambda: func(seq))
        result = sorted(t.repeat(3, loops))
        timings.append((result, func.__name__, value))
        assert seq==orig, 'Sequence altered by {}!'.format(func.__name__)
    first = timings[0][-1]
    timings.sort()
    for result, name, value in timings:
        result = ', '.join([format(u, '.5f') for u in result])
        print('{:24} : {}'.format(name, result))

    if verify:
        # Check that all results are identical
        bad = ['%s: %d' % (name, value)
            for _, name, value in timings if value != first]
        if bad:
            print('ERROR. Value: {}, bad: {}'.format(first, ', '.join(bad)))
        else:
            print('Value: {}'.format(first))
    print()

#Run the tests
size, loops = 5, 1 << 12
verify = True
for _ in range(7):
    hi = size // 2
    print('Size = {}, hi = {}, {} loops'.format(size, hi, loops))
    seq = [randrange(hi) for _ in range(size)]
    time_test(seq, loops, verify)
    loops >>= 1
    size <<= 1

#size, loops = 640, 8
#verify = False
#for _ in range(5):
    #hi = size // 2
    #print('Size = {}, hi = {}, {} loops'.format(size, hi, loops))
    #seq = [randrange(hi) for _ in range(size)]
    #time_test(seq, loops, verify)
    #size <<= 1

#size, loops = 163840, 4
#verify = False
#for _ in range(3):
    #hi = size // 2
    #print('Size = {}, hi = {}, {} loops'.format(size, hi, loops))
    #seq = [randrange(hi) for _ in range(size)]
    #time_test(seq, loops, verify)
    #size <<= 1

โปรดดูที่นี่สำหรับผลลัพธ์


ขอบคุณที่ให้ความบันเทิง :) แสดงให้เห็นอย่างชัดเจนถึงประโยชน์ของการใช้โมดูล C - ซึ่งแบ่งเป็นสองส่วน
Tim Babych

ปัญหาคือผู้ชนะใช้อัลกอริทึมกำลังสอง (ตามทฤษฎี) สำหรับขนาด ~ 100,000 จะถูกทุบตีโดยผู้อื่น ฉันแก้ไขโพสต์ของฉันเพื่อใส่โซลูชันความเร็ว C เสมือนเชิงเส้นของ python
BM

@BM แน่นอน แต่วิธีการแบ่งครึ่งของทิมนั้นค่อนข้างดีจนกว่าคุณจะมีขนาด 45,000 หรือมากกว่านั้น ฉันมีวิธีแก้ไขอีกสองสามวิธีที่จะเพิ่มที่นี่ในวันถัดไป
PM 2Ring

@TimBabych คุณบอกว่าbisectเป็น C หรือเปล่า? ฉันค่อนข้างมั่นใจว่ามันคือ Python
Stefan Pochmann

1
โมดูลแบ่งส่วนของ Python เขียนด้วยภาษา C โปรดดูที่ github.com/python/cpython/blob/master/Modules/_bisectmodule.c github.com/python/cpython/blob/master/Lib/bisect.py#L84
Tim Babych

10

จำนวนการผกผันสามารถพบได้โดยการวิเคราะห์กระบวนการผสานในการเรียงลำดับการผสาน: ผสานกระบวนการ

เมื่อคัดลอกองค์ประกอบจากอาร์เรย์ที่สองไปยังอาร์เรย์ผสาน (9 ในตัวอย่างนี้) องค์ประกอบนั้นจะคงตำแหน่งไว้กับองค์ประกอบอื่น ๆ เมื่อคัดลอกองค์ประกอบจากอาร์เรย์แรกไปยังอาร์เรย์ผสาน (5 ที่นี่) จะมีการกลับด้านกับองค์ประกอบทั้งหมดที่อยู่ในอาร์เรย์ที่สอง (2 การกลับด้านกับ 3 และ 4) ดังนั้นการปรับเปลี่ยนการเรียงลำดับการผสานเพียงเล็กน้อยสามารถแก้ปัญหาใน O (n ln n) ได้
สำหรับตัวอย่างให้ยกเลิกการใส่เครื่องหมาย # สองบรรทัดในโค้ด python ที่ผสานด้านล่างเพื่อให้มีการนับ

def merge(l1,l2):
    l = []
    # global count
    while l1 and l2:
        if l1[-1] <= l2[-1]:
            l.append(l2.pop())
        else:
            l.append(l1.pop())
            # count += len(l2)
    l.reverse()
    return l1 + l2 + l

def sort(l): 
    t = len(l) // 2
    return merge(sort(l[:t]), sort(l[t:])) if t > 0 else l

count=0
print(sort([5,1,2,4,9,3]), count)
# [1, 2, 3, 4, 5, 9] 6

แก้ไข 1

งานเดียวกันสามารถทำได้ด้วยการจัดเรียงด่วนเวอร์ชันเสถียรซึ่งทราบว่าเร็วกว่าเล็กน้อย:

def part(l):
    pivot=l[-1]
    small,big = [],[]
    count = big_count = 0
    for x in l:
        if x <= pivot:
            small.append(x)
            count += big_count
        else:
            big.append(x)
            big_count += 1
    return count,small,big

def quick_count(l):
    if len(l)<2 : return 0
    count,small,big = part(l)
    small.pop()
    return count + quick_count(small) + quick_count(big)

การเลือก Pivot เป็นองค์ประกอบสุดท้ายจะมีการนับการผกผันและเวลาในการดำเนินการดีกว่าการรวมด้านบน 40%

แก้ไข 2

สำหรับประสิทธิภาพใน python เวอร์ชัน numpy & numba:

ก่อนอื่นส่วนที่เป็นตัวเลขซึ่งใช้ argsort O (n ln n):

def count_inversions(a):
    n = a.size
    counts = np.arange(n) & -np.arange(n)  # The BIT
    ags = a.argsort(kind='mergesort')    
    return  BIT(ags,counts,n)

และส่วน numba สำหรับแนวทาง BIT ที่มีประสิทธิภาพ:

@numba.njit
def BIT(ags,counts,n):
    res = 0        
    for x in ags :
        i = x
        while i:
            res += counts[i]
            i -= i & -i
        i = x+1
        while i < n:
            counts[i] -= 1
            i += i & -i
    return  res  

ฉันได้โพสต์คำตอบที่timeitเปรียบเทียบคำตอบของ Python ทั้งหมดสำหรับคำถามนี้ดังนั้นจึงมีโค้ดของคุณด้วย คุณอาจสนใจดูผลการจับเวลา
PM 2Ring

ไม่มีปัญหาด้านประสิทธิภาพในโพสต์นี้ ... ฉันจะพยายามในบางครั้ง Numpy numba อนุญาต;)?
BM

ฉันไม่เคยใช้ Numba แต่ฉันเคยใช้ Numpy มาแล้วและคิดจะเพิ่มเวอร์ชัน Numpy ด้วยตัวเอง แต่ฉันตัดสินใจที่จะ จำกัด การทดสอบเฉพาะโซลูชันที่ใช้เฉพาะไลบรารีมาตรฐานเท่านั้น แต่ฉันเดาว่ามันน่าสนใจที่จะดูว่าโซลูชัน Numpy เปรียบเทียบกันอย่างไร ฉันสงสัยว่ามันจะไม่เร็วกว่านี้ในรายการเล็ก ๆ
PM 2Ring

การเร่งความเร็ว 100 เท่านั้นน่าประทับใจ! แต่ฉันไม่สามารถวิ่งได้เนื่องจากไม่มี Numba และอย่างที่ฉันได้กล่าวไปก่อนหน้านี้มันคงไม่ยุติธรรมที่จะรวมไว้ในtimeitคอลเลกชันของฉัน
น. 2Ring

8

โปรดทราบว่าคำตอบของ Geoffrey Irving นั้นผิด

จำนวนการผกผันในอาร์เรย์เป็นครึ่งหนึ่งขององค์ประกอบระยะทางทั้งหมดที่ต้องย้ายเพื่อจัดเรียงอาร์เรย์ ดังนั้นจึงสามารถคำนวณได้โดยการจัดเรียงอาร์เรย์รักษาการเรียงสับเปลี่ยนผลลัพธ์ p [i] จากนั้นคำนวณผลรวมของ abs (p [i] -i) / 2 สิ่งนี้ใช้เวลา O (n log n) ซึ่งเหมาะสมที่สุด

วิธีการอื่นที่จะได้รับhttp://mathworld.wolfram.com/PermutationInversion.html วิธีนี้เทียบเท่ากับผลรวมของ max (0, p [i] -i) ซึ่งเท่ากับผลรวมของ abs (p [i] -i]) / 2 เนื่องจากองค์ประกอบระยะทางทั้งหมดที่เคลื่อนที่ไปทางซ้ายเท่ากับ องค์ประกอบระยะทางทั้งหมดย้ายไปทางขวา

ใช้ลำดับ {3, 2, 1} เป็นตัวอย่าง การผกผันมีสามแบบ: (3, 2), (3, 1), (2, 1) ดังนั้นจำนวนการผกผันคือ 3 อย่างไรก็ตามตามวิธีการที่ยกมาคำตอบจะเป็น 2


สามารถหาคำตอบที่ถูกต้องแทนได้โดยการนับจำนวนสว็อปที่อยู่ติดกันขั้นต่ำที่ต้องการ ดูการอภิปราย: stackoverflow.com/questions/20990127/…
Isaac Turner


4

นี่คือทางออกที่เป็นไปได้ทางหนึ่งที่มีการเปลี่ยนแปลงของต้นไม้ไบนารี จะเพิ่มฟิลด์ที่เรียกว่า rightSubTreeSize ให้กับโหนดทรีแต่ละโหนด ใส่ตัวเลขลงในต้นไม้ไบนารีตามลำดับที่ปรากฏในอาร์เรย์ ถ้าจำนวนไป lhs ของโหนดจำนวนการผกผันสำหรับองค์ประกอบนั้นจะเป็น (1 + rightSubTreeSize) เนื่องจากองค์ประกอบเหล่านั้นทั้งหมดมีค่ามากกว่าองค์ประกอบปัจจุบันและจะปรากฏก่อนหน้านี้ในอาร์เรย์ หากองค์ประกอบไปที่ rhs ของโหนดให้เพิ่ม rightSubTreeSize ต่อไปนี้เป็นรหัส

Node { 
    int data;
    Node* left, *right;
    int rightSubTreeSize;

    Node(int data) { 
        rightSubTreeSize = 0;
    }   
};

Node* root = null;
int totCnt = 0;
for(i = 0; i < n; ++i) { 
    Node* p = new Node(a[i]);
    if(root == null) { 
        root = p;
        continue;
    } 

    Node* q = root;
    int curCnt = 0;
    while(q) { 
        if(p->data <= q->data) { 
            curCnt += 1 + q->rightSubTreeSize;
            if(q->left) { 
                q = q->left;
            } else { 
                q->left = p;
                break;
            }
        } else { 
            q->rightSubTreeSize++;
            if(q->right) { 
                q = q->right;
            } else { 
                q->right = p;
                break;
            }
        }
    }

    totCnt += curCnt;
  }
  return totCnt;

นี่เป็นแนวทางที่น่าสนใจและดูเหมือนจะค่อนข้างเร็ว อย่างไรก็ตามการเปรียบเทียบนั้นจำเป็นต้องมีการif(p->data < q->data)ทำซ้ำไม่ได้รับการจัดการอย่างถูกต้อง และไม่จำเป็นต้องทดสอบqที่ด้านบนของลูปลูปที่ไม่มีเงื่อนไขก็whileทำงานได้ดี นอกจากนี้คุณยังละเลยที่จะพูดถึงภาษานี้ :) และดูเหมือนว่าฟังก์ชันของคุณจะสูญเสียบรรทัดส่วนหัวไป
PM 2Ring

ฉันเพิ่งเพิ่มเวอร์ชัน Python ตามอัลกอริทึมต้นไม้ของคุณในคำตอบของฉัน แน่นอนว่ามันไม่เร็วเท่าเวอร์ชันที่คอมไพล์เต็ม แต่ทำได้ค่อนข้างดีเมื่อเทียบกับเวอร์ชัน Python อื่น ๆ
PM 2Ring

3
public static int mergeSort(int[] a, int p, int r)
{
    int countInversion = 0;
    if(p < r)
    {
        int q = (p + r)/2;
        countInversion = mergeSort(a, p, q);
        countInversion += mergeSort(a, q+1, r);
        countInversion += merge(a, p, q, r);
    }
    return countInversion;
}

public static int merge(int[] a, int p, int q, int r)
{
    //p=0, q=1, r=3
    int countingInversion = 0;
    int n1 = q-p+1;
    int n2 = r-q;
    int[] temp1 = new int[n1+1];
    int[] temp2 = new int[n2+1];
    for(int i=0; i<n1; i++) temp1[i] = a[p+i];
    for(int i=0; i<n2; i++) temp2[i] = a[q+1+i];

    temp1[n1] = Integer.MAX_VALUE;
    temp2[n2] = Integer.MAX_VALUE;
    int i = 0, j = 0;

    for(int k=p; k<=r; k++)
    {
        if(temp1[i] <= temp2[j])
        {
            a[k] = temp1[i];
            i++;
        }
        else
        {
            a[k] = temp2[j];
            j++;
            countingInversion=countingInversion+(n1-i); 
        }
    }
    return countingInversion;
}
public static void main(String[] args)
{
    int[] a = {1, 20, 6, 4, 5};
    int countInversion = mergeSort(a, 0, a.length-1);
    System.out.println(countInversion);
}

3
สิ่งนี้แตกต่างจากโซลูชันJavaและPython ที่โพสต์ไว้แล้วหรือไม่? นอกจากนี้คำตอบแบบโค้ดเท่านั้นไม่ใช่ IMO ที่ดีโดยเฉพาะเมื่อพิจารณาจากคำถามนี้ไม่ได้ระบุภาษา
Bernhard Barker

2

เนื่องจากนี่เป็นคำถามเก่าฉันจะให้คำตอบใน C.

#include <stdio.h>

int count = 0;
int inversions(int a[], int len);
void mergesort(int a[], int left, int right);
void merge(int a[], int left, int mid, int right);

int main() {
  int a[] = { 1, 5, 2, 4, 0 };
  printf("%d\n", inversions(a, 5));
}

int inversions(int a[], int len) {
  mergesort(a, 0, len - 1);
  return count;
}

void mergesort(int a[], int left, int right) {
  if (left < right) {
     int mid = (left + right) / 2;
     mergesort(a, left, mid);
     mergesort(a, mid + 1, right);
     merge(a, left, mid, right);
  }
}

void merge(int a[], int left, int mid, int right) {
  int i = left;
  int j = mid + 1;
  int k = 0;
  int b[right - left + 1];
  while (i <= mid && j <= right) {
     if (a[i] <= a[j]) {
       b[k++] = a[i++];
     } else {
       printf("right element: %d\n", a[j]);
       count += (mid - i + 1);
       printf("new count: %d\n", count);
       b[k++] = a[j++];
     }
  }
  while (i <= mid)
    b[k++] = a[i++];
  while (j <= right)
    b[k++] = a[j++];
  for (i = left, k = 0; i <= right; i++, k++) {
    a[i] = b[k];
  }
}

-1 เนื่องจากคำตอบในภาษาอื่น ๆ ทุกคำจะนำไปสู่คำตอบที่มากเกินไปอย่างสิ้นหวังซึ่งทั้งหมดนี้มีข้อมูลซ้ำกันอยู่แล้วในคำตอบอื่น ๆ นอกจากนี้โดยพื้นฐานแล้วนี่เป็นคำตอบแบบใช้รหัสเท่านั้นโดยไม่มีคำอธิบายซึ่งโดยพื้นฐานแล้วจะเหมาะสมกับคำถามเกี่ยวกับภาษานั้นเป็นหลัก
Bernhard Barker

2

นี่คือโซลูชัน c ++

/**
*array sorting needed to verify if first arrays n'th element is greater than sencond arrays
*some element then all elements following n will do the same
*/
#include<stdio.h>
#include<iostream>
using namespace std;
int countInversions(int array[],int size);
int merge(int arr1[],int size1,int arr2[],int size2,int[]);
int main()
{
    int array[] = {2, 4, 1, 3, 5};
    int size = sizeof(array) / sizeof(array[0]);
    int x = countInversions(array,size);
    printf("number of inversions = %d",x);
}

int countInversions(int array[],int size)
{
    if(size > 1 )
    {
    int mid = size / 2;
    int count1 = countInversions(array,mid);
    int count2 = countInversions(array+mid,size-mid);
    int temp[size];
    int count3 = merge(array,mid,array+mid,size-mid,temp);
    for(int x =0;x<size ;x++)
    {
        array[x] = temp[x];
    }
    return count1 + count2 + count3;
    }else{
        return 0;
    }
}

int merge(int arr1[],int size1,int arr2[],int size2,int temp[])
{
    int count  = 0;
    int a = 0;
    int b = 0;
    int c = 0;
    while(a < size1 && b < size2)
    {
        if(arr1[a] < arr2[b])
        {
            temp[c] = arr1[a];
            c++;
            a++;
        }else{
            temp[c] = arr2[b];
            b++;
            c++;
            count = count + size1 -a;
        }
    }

    while(a < size1)
    {
        temp[c] = arr1[a];
        c++;a++;
    }

while(b < size2)
    {
        temp[c] = arr2[b];
        c++;b++;
    }

    return count;
}

2

คำตอบนี้มีผลของtimeitการทดสอบที่ผลิตโดยรหัสในของฉันคำตอบหลัก โปรดดูคำตอบสำหรับรายละเอียด!

count_inversions speed test results

Size = 5, hi = 2, 4096 loops
ltree_count_PM2R         : 0.04871, 0.04872, 0.04876
bruteforce_loops_PM2R    : 0.05696, 0.05700, 0.05776
solution_TimBabych       : 0.05760, 0.05822, 0.05943
solutionE_TimBabych      : 0.06642, 0.06704, 0.06760
bruteforce_sum_PM2R      : 0.07523, 0.07545, 0.07563
perm_sum_PM2R            : 0.09873, 0.09875, 0.09935
rank_sum_PM2R            : 0.10449, 0.10463, 0.10468
solution_python          : 0.13034, 0.13061, 0.13221
fenwick_inline_PM2R      : 0.14323, 0.14610, 0.18802
perm_radixR_PM2R         : 0.15146, 0.15203, 0.15235
merge_count_BM           : 0.16179, 0.16267, 0.16467
perm_radixI_PM2R         : 0.16200, 0.16202, 0.16768
perm_fenwick_PM2R        : 0.16887, 0.16920, 0.17075
merge_PM2R               : 0.18262, 0.18271, 0.18418
count_inversions_NiklasB : 0.19183, 0.19279, 0.20388
count_inversion_mkso     : 0.20060, 0.20141, 0.20398
inv_cnt_ZheHu            : 0.20815, 0.20841, 0.20906
fenwick_PM2R             : 0.22109, 0.22137, 0.22379
reversePairs_nomanpouigt : 0.29620, 0.29689, 0.30293
Value: 5

Size = 10, hi = 5, 2048 loops
solution_TimBabych       : 0.05954, 0.05989, 0.05991
solutionE_TimBabych      : 0.05970, 0.05972, 0.05998
perm_sum_PM2R            : 0.07517, 0.07519, 0.07520
ltree_count_PM2R         : 0.07672, 0.07677, 0.07684
bruteforce_loops_PM2R    : 0.07719, 0.07724, 0.07817
rank_sum_PM2R            : 0.08587, 0.08823, 0.08864
bruteforce_sum_PM2R      : 0.09470, 0.09472, 0.09484
solution_python          : 0.13126, 0.13154, 0.13185
perm_radixR_PM2R         : 0.14239, 0.14320, 0.14474
perm_radixI_PM2R         : 0.14632, 0.14669, 0.14679
fenwick_inline_PM2R      : 0.16796, 0.16831, 0.17030
perm_fenwick_PM2R        : 0.18189, 0.18212, 0.18638
merge_count_BM           : 0.19816, 0.19870, 0.19948
count_inversions_NiklasB : 0.21807, 0.22031, 0.22215
merge_PM2R               : 0.22037, 0.22048, 0.26106
fenwick_PM2R             : 0.24290, 0.24314, 0.24744
count_inversion_mkso     : 0.24895, 0.24899, 0.25205
inv_cnt_ZheHu            : 0.26253, 0.26259, 0.26590
reversePairs_nomanpouigt : 0.35711, 0.35762, 0.35973
Value: 20

Size = 20, hi = 10, 1024 loops
solutionE_TimBabych      : 0.05687, 0.05696, 0.05720
solution_TimBabych       : 0.06126, 0.06151, 0.06168
perm_sum_PM2R            : 0.06875, 0.06906, 0.07054
rank_sum_PM2R            : 0.07988, 0.07995, 0.08002
ltree_count_PM2R         : 0.11232, 0.11239, 0.11257
bruteforce_loops_PM2R    : 0.12553, 0.12584, 0.12592
solution_python          : 0.13472, 0.13540, 0.13694
bruteforce_sum_PM2R      : 0.15820, 0.15849, 0.16021
perm_radixI_PM2R         : 0.17101, 0.17148, 0.17229
perm_radixR_PM2R         : 0.17891, 0.18087, 0.18366
perm_fenwick_PM2R        : 0.20554, 0.20708, 0.21412
fenwick_inline_PM2R      : 0.21161, 0.21163, 0.22047
merge_count_BM           : 0.24125, 0.24261, 0.24565
count_inversions_NiklasB : 0.25712, 0.25754, 0.25778
merge_PM2R               : 0.26477, 0.26566, 0.31297
fenwick_PM2R             : 0.28178, 0.28216, 0.29069
count_inversion_mkso     : 0.30286, 0.30290, 0.30652
inv_cnt_ZheHu            : 0.32024, 0.32041, 0.32447
reversePairs_nomanpouigt : 0.45812, 0.45822, 0.46172
Value: 98

Size = 40, hi = 20, 512 loops
solutionE_TimBabych      : 0.05784, 0.05787, 0.05958
solution_TimBabych       : 0.06452, 0.06475, 0.06479
perm_sum_PM2R            : 0.07254, 0.07261, 0.07263
rank_sum_PM2R            : 0.08537, 0.08540, 0.08572
ltree_count_PM2R         : 0.11744, 0.11749, 0.11792
solution_python          : 0.14262, 0.14285, 0.14465
perm_radixI_PM2R         : 0.18774, 0.18776, 0.18922
perm_radixR_PM2R         : 0.19425, 0.19435, 0.19609
bruteforce_loops_PM2R    : 0.21500, 0.21511, 0.21686
perm_fenwick_PM2R        : 0.23338, 0.23375, 0.23674
fenwick_inline_PM2R      : 0.24947, 0.24958, 0.25189
bruteforce_sum_PM2R      : 0.27627, 0.27646, 0.28041
merge_count_BM           : 0.28059, 0.28128, 0.28294
count_inversions_NiklasB : 0.28557, 0.28759, 0.29022
merge_PM2R               : 0.29886, 0.29928, 0.30317
fenwick_PM2R             : 0.30241, 0.30259, 0.35237
count_inversion_mkso     : 0.34252, 0.34356, 0.34441
inv_cnt_ZheHu            : 0.37468, 0.37569, 0.37847
reversePairs_nomanpouigt : 0.50725, 0.50770, 0.50943
Value: 369

Size = 80, hi = 40, 256 loops
solutionE_TimBabych      : 0.06339, 0.06373, 0.06513
solution_TimBabych       : 0.06984, 0.06994, 0.07009
perm_sum_PM2R            : 0.09171, 0.09172, 0.09186
rank_sum_PM2R            : 0.10468, 0.10474, 0.10500
ltree_count_PM2R         : 0.14416, 0.15187, 0.18541
solution_python          : 0.17415, 0.17423, 0.17451
perm_radixI_PM2R         : 0.20676, 0.20681, 0.20936
perm_radixR_PM2R         : 0.21671, 0.21695, 0.21736
perm_fenwick_PM2R        : 0.26197, 0.26252, 0.26264
fenwick_inline_PM2R      : 0.28111, 0.28249, 0.28382
count_inversions_NiklasB : 0.31746, 0.32448, 0.32451
merge_count_BM           : 0.31964, 0.33842, 0.35276
merge_PM2R               : 0.32890, 0.32941, 0.33322
fenwick_PM2R             : 0.34355, 0.34377, 0.34873
count_inversion_mkso     : 0.37689, 0.37698, 0.38079
inv_cnt_ZheHu            : 0.42923, 0.42941, 0.43249
bruteforce_loops_PM2R    : 0.43544, 0.43601, 0.43902
bruteforce_sum_PM2R      : 0.52106, 0.52160, 0.52531
reversePairs_nomanpouigt : 0.57805, 0.58156, 0.58252
Value: 1467

Size = 160, hi = 80, 128 loops
solutionE_TimBabych      : 0.06766, 0.06784, 0.06963
solution_TimBabych       : 0.07433, 0.07489, 0.07516
perm_sum_PM2R            : 0.13143, 0.13175, 0.13179
rank_sum_PM2R            : 0.14428, 0.14440, 0.14922
solution_python          : 0.20072, 0.20076, 0.20084
ltree_count_PM2R         : 0.20314, 0.20583, 0.24776
perm_radixI_PM2R         : 0.23061, 0.23078, 0.23525
perm_radixR_PM2R         : 0.23894, 0.23915, 0.24234
perm_fenwick_PM2R        : 0.30984, 0.31181, 0.31503
fenwick_inline_PM2R      : 0.31933, 0.32680, 0.32722
merge_count_BM           : 0.36003, 0.36387, 0.36409
count_inversions_NiklasB : 0.36796, 0.36814, 0.37106
merge_PM2R               : 0.36847, 0.36848, 0.37127
fenwick_PM2R             : 0.37833, 0.37847, 0.38095
count_inversion_mkso     : 0.42746, 0.42747, 0.43184
inv_cnt_ZheHu            : 0.48969, 0.48974, 0.49293
reversePairs_nomanpouigt : 0.67791, 0.68157, 0.72420
bruteforce_loops_PM2R    : 0.82816, 0.83175, 0.83282
bruteforce_sum_PM2R      : 1.03322, 1.03378, 1.03562
Value: 6194

Size = 320, hi = 160, 64 loops
solutionE_TimBabych      : 0.07467, 0.07470, 0.07483
solution_TimBabych       : 0.08036, 0.08066, 0.08077
perm_sum_PM2R            : 0.21142, 0.21201, 0.25766
solution_python          : 0.22410, 0.22644, 0.22897
rank_sum_PM2R            : 0.22820, 0.22851, 0.22877
ltree_count_PM2R         : 0.24424, 0.24595, 0.24645
perm_radixI_PM2R         : 0.25690, 0.25710, 0.26191
perm_radixR_PM2R         : 0.26501, 0.26504, 0.26729
perm_fenwick_PM2R        : 0.33483, 0.33507, 0.33845
fenwick_inline_PM2R      : 0.34413, 0.34484, 0.35153
merge_count_BM           : 0.39875, 0.39919, 0.40302
fenwick_PM2R             : 0.40434, 0.40439, 0.40845
merge_PM2R               : 0.40814, 0.41531, 0.51417
count_inversions_NiklasB : 0.41681, 0.42009, 0.42128
count_inversion_mkso     : 0.47132, 0.47192, 0.47385
inv_cnt_ZheHu            : 0.54468, 0.54750, 0.54893
reversePairs_nomanpouigt : 0.76164, 0.76389, 0.80357
bruteforce_loops_PM2R    : 1.59125, 1.60430, 1.64131
bruteforce_sum_PM2R      : 2.03734, 2.03834, 2.03975
Value: 24959

Run 2

Size = 640, hi = 320, 8 loops
solutionE_TimBabych      : 0.04135, 0.04374, 0.04575
ltree_count_PM2R         : 0.06738, 0.06758, 0.06874
perm_radixI_PM2R         : 0.06928, 0.06943, 0.07019
fenwick_inline_PM2R      : 0.07850, 0.07856, 0.08059
perm_fenwick_PM2R        : 0.08151, 0.08162, 0.08170
perm_sum_PM2R            : 0.09122, 0.09133, 0.09221
rank_sum_PM2R            : 0.09549, 0.09603, 0.11270
merge_count_BM           : 0.10733, 0.10807, 0.11032
count_inversions_NiklasB : 0.12460, 0.19865, 0.20205
solution_python          : 0.13514, 0.13585, 0.13814

Size = 1280, hi = 640, 8 loops
solutionE_TimBabych      : 0.04714, 0.04742, 0.04752
perm_radixI_PM2R         : 0.15325, 0.15388, 0.15525
solution_python          : 0.15709, 0.15715, 0.16076
fenwick_inline_PM2R      : 0.16048, 0.16160, 0.16403
ltree_count_PM2R         : 0.16213, 0.16238, 0.16428
perm_fenwick_PM2R        : 0.16408, 0.16416, 0.16449
count_inversions_NiklasB : 0.19755, 0.19833, 0.19897
merge_count_BM           : 0.23736, 0.23793, 0.23912
perm_sum_PM2R            : 0.32946, 0.32969, 0.33277
rank_sum_PM2R            : 0.34637, 0.34756, 0.34858

Size = 2560, hi = 1280, 8 loops
solutionE_TimBabych      : 0.10898, 0.11005, 0.11025
perm_radixI_PM2R         : 0.33345, 0.33352, 0.37656
ltree_count_PM2R         : 0.34670, 0.34786, 0.34833
perm_fenwick_PM2R        : 0.34816, 0.34879, 0.35214
fenwick_inline_PM2R      : 0.36196, 0.36455, 0.36741
solution_python          : 0.36498, 0.36637, 0.40887
count_inversions_NiklasB : 0.42274, 0.42745, 0.42995
merge_count_BM           : 0.50799, 0.50898, 0.50917
perm_sum_PM2R            : 1.27773, 1.27897, 1.27951
rank_sum_PM2R            : 1.29728, 1.30389, 1.30448

Size = 5120, hi = 2560, 8 loops
solutionE_TimBabych      : 0.26914, 0.26993, 0.27253
perm_radixI_PM2R         : 0.71416, 0.71634, 0.71753
perm_fenwick_PM2R        : 0.71976, 0.72078, 0.72078
fenwick_inline_PM2R      : 0.72776, 0.72804, 0.73143
ltree_count_PM2R         : 0.81972, 0.82043, 0.82290
solution_python          : 0.83714, 0.83756, 0.83962
count_inversions_NiklasB : 0.87282, 0.87395, 0.92087
merge_count_BM           : 1.09496, 1.09584, 1.10207
rank_sum_PM2R            : 5.02564, 5.06277, 5.06666
perm_sum_PM2R            : 5.09088, 5.12999, 5.13512

Size = 10240, hi = 5120, 8 loops
solutionE_TimBabych      : 0.71556, 0.71718, 0.72201
perm_radixI_PM2R         : 1.54785, 1.55096, 1.55515
perm_fenwick_PM2R        : 1.55103, 1.55353, 1.59298
fenwick_inline_PM2R      : 1.57118, 1.57240, 1.57271
ltree_count_PM2R         : 1.76240, 1.76247, 1.80944
count_inversions_NiklasB : 1.86543, 1.86851, 1.87208
solution_python          : 2.01490, 2.01519, 2.06423
merge_count_BM           : 2.35215, 2.35301, 2.40023
rank_sum_PM2R            : 20.07048, 20.08399, 20.13200
perm_sum_PM2R            : 20.10187, 20.12551, 20.12683

Run 3
Size = 20480, hi = 10240, 4 loops
solutionE_TimBabych      : 1.07636, 1.08243, 1.09569
perm_radixI_PM2R         : 1.59579, 1.60519, 1.61785
perm_fenwick_PM2R        : 1.66885, 1.68549, 1.71109
fenwick_inline_PM2R      : 1.72073, 1.72752, 1.77217
ltree_count_PM2R         : 1.96900, 1.97820, 2.02578
count_inversions_NiklasB : 2.03257, 2.05005, 2.18548
merge_count_BM           : 2.46768, 2.47377, 2.52133
solution_python          : 2.49833, 2.50179, 3.79819

Size = 40960, hi = 20480, 4 loops
solutionE_TimBabych      : 3.51733, 3.52008, 3.56996
perm_radixI_PM2R         : 3.51736, 3.52365, 3.56459
perm_fenwick_PM2R        : 3.76097, 3.80900, 3.87974
fenwick_inline_PM2R      : 3.95099, 3.96300, 3.99748
ltree_count_PM2R         : 4.49866, 4.54652, 5.39716
count_inversions_NiklasB : 4.61851, 4.64303, 4.73026
merge_count_BM           : 5.31945, 5.35378, 5.35951
solution_python          : 6.78756, 6.82911, 6.98217

Size = 81920, hi = 40960, 4 loops
perm_radixI_PM2R         : 7.68723, 7.71986, 7.72135
perm_fenwick_PM2R        : 8.52404, 8.53349, 8.53710
fenwick_inline_PM2R      : 8.97082, 8.97561, 8.98347
ltree_count_PM2R         : 10.01142, 10.01426, 10.03216
count_inversions_NiklasB : 10.60807, 10.62424, 10.70425
merge_count_BM           : 11.42149, 11.42342, 11.47003
solutionE_TimBabych      : 12.83390, 12.83485, 12.89747
solution_python          : 19.66092, 19.67067, 20.72204

Size = 163840, hi = 81920, 4 loops
perm_radixI_PM2R         : 17.14153, 17.16885, 17.22240
perm_fenwick_PM2R        : 19.25944, 19.27844, 20.27568
fenwick_inline_PM2R      : 19.78221, 19.80219, 19.80766
ltree_count_PM2R         : 22.42240, 22.43259, 22.48837
count_inversions_NiklasB : 22.97341, 23.01516, 23.98052
merge_count_BM           : 24.42683, 24.48559, 24.51488
solutionE_TimBabych      : 60.96006, 61.20145, 63.71835
solution_python          : 73.75132, 73.79854, 73.95874

Size = 327680, hi = 163840, 4 loops
perm_radixI_PM2R         : 36.56715, 36.60221, 37.05071
perm_fenwick_PM2R        : 42.21616, 42.21838, 42.26053
fenwick_inline_PM2R      : 43.04987, 43.09075, 43.13287
ltree_count_PM2R         : 49.87400, 50.08509, 50.69292
count_inversions_NiklasB : 50.74591, 50.75012, 50.75551
merge_count_BM           : 52.37284, 52.51491, 53.43003
solutionE_TimBabych      : 373.67198, 377.03341, 377.42360
solution_python          : 411.69178, 411.92691, 412.83856

Size = 655360, hi = 327680, 4 loops
perm_radixI_PM2R         : 78.51927, 78.66327, 79.46325
perm_fenwick_PM2R        : 90.64711, 90.80328, 91.76126
fenwick_inline_PM2R      : 93.32482, 93.39086, 94.28880
count_inversions_NiklasB : 107.74393, 107.80036, 108.71443
ltree_count_PM2R         : 109.11328, 109.23592, 110.18247
merge_count_BM           : 111.05633, 111.07840, 112.05861
solutionE_TimBabych      : 1830.46443, 1836.39960, 1849.53918
solution_python          : 1911.03692, 1912.04484, 1914.69786

1

นี่คือรหัส C สำหรับการนับการผกผัน

#include <stdio.h>
#include <stdlib.h>

int  _mergeSort(int arr[], int temp[], int left, int right);
int merge(int arr[], int temp[], int left, int mid, int right);

/* This function sorts the input array and returns the
   number of inversions in the array */
int mergeSort(int arr[], int array_size)
{
    int *temp = (int *)malloc(sizeof(int)*array_size);
    return _mergeSort(arr, temp, 0, array_size - 1);
}

/* An auxiliary recursive function that sorts the input array and
  returns the number of inversions in the array. */
int _mergeSort(int arr[], int temp[], int left, int right)
{
  int mid, inv_count = 0;
  if (right > left)
  {
    /* Divide the array into two parts and call _mergeSortAndCountInv()
       for each of the parts */
    mid = (right + left)/2;

    /* Inversion count will be sum of inversions in left-part, right-part
      and number of inversions in merging */
    inv_count  = _mergeSort(arr, temp, left, mid);
    inv_count += _mergeSort(arr, temp, mid+1, right);

    /*Merge the two parts*/
    inv_count += merge(arr, temp, left, mid+1, right);
  }
  return inv_count;
}

/* This funt merges two sorted arrays and returns inversion count in
   the arrays.*/
int merge(int arr[], int temp[], int left, int mid, int right)
{
  int i, j, k;
  int inv_count = 0;

  i = left; /* i is index for left subarray*/
  j = mid;  /* i is index for right subarray*/
  k = left; /* i is index for resultant merged subarray*/
  while ((i <= mid - 1) && (j <= right))
  {
    if (arr[i] <= arr[j])
    {
      temp[k++] = arr[i++];
    }
    else
    {
      temp[k++] = arr[j++];

     /*this is tricky -- see above explanation/diagram for merge()*/
      inv_count = inv_count + (mid - i);
    }
  }

  /* Copy the remaining elements of left subarray
   (if there are any) to temp*/
  while (i <= mid - 1)
    temp[k++] = arr[i++];

  /* Copy the remaining elements of right subarray
   (if there are any) to temp*/
  while (j <= right)
    temp[k++] = arr[j++];

  /*Copy back the merged elements to original array*/
  for (i=left; i <= right; i++)
    arr[i] = temp[i];

  return inv_count;
}

/* Driver progra to test above functions */
int main(int argv, char** args)
{
  int arr[] = {1, 20, 6, 4, 5};
  printf(" Number of inversions are %d \n", mergeSort(arr, 5));
  getchar();
  return 0;
}

มีคำอธิบายโดยละเอียดที่นี่: http://www.geeksforgeeks.org/counting-inversions/


1

เวลา O (n log n), O (n) ช่องว่างใน java

การผสานกับการปรับแต่งเพื่อรักษาจำนวนการผกผันที่ดำเนินการระหว่างขั้นตอนการผสาน (สำหรับการผสานรวมที่อธิบายไว้อย่างดีโปรดดูที่http://www.vogella.com/tutorials/JavaAlgorithmsMergesort/article.html )

เนื่องจากสามารถสร้าง mergesort ได้จึงอาจปรับปรุงความซับซ้อนของพื้นที่เป็น O (1)

เมื่อใช้การเรียงลำดับนี้การผกผันจะเกิดขึ้นเฉพาะในขั้นตอนการผสานและต่อเมื่อเราต้องใส่องค์ประกอบของส่วนที่สองก่อนองค์ประกอบจากครึ่งแรกเช่น

  • 0 5 10 15

ผสานกับ

  • 1 6 22

เรามี 3 + 2 + 0 = 5 ผกผัน:

  • 1 กับ {5, 10, 15}
  • 6 กับ {10, 15}
  • 22 กับ {}

หลังจากที่เราทำการผกผัน 5 ครั้งแล้วรายการที่รวมใหม่ของเราคือ 0, 1, 5, 6, 10, 15, 22

มีงานสาธิตเกี่ยวกับ Codility ที่เรียกว่า ArrayInversionCount ซึ่งคุณสามารถทดสอบโซลูชันของคุณได้

    public class FindInversions {

    public static int solution(int[] input) {
        if (input == null)
            return 0;
        int[] helper = new int[input.length];
        return mergeSort(0, input.length - 1, input, helper);
    }

    public static int mergeSort(int low, int high, int[] input, int[] helper) {
        int inversionCount = 0;
        if (low < high) {
            int medium = low + (high - low) / 2;
            inversionCount += mergeSort(low, medium, input, helper);
            inversionCount += mergeSort(medium + 1, high, input, helper);
            inversionCount += merge(low, medium, high, input, helper);
        }
        return inversionCount;
    }

    public static int merge(int low, int medium, int high, int[] input, int[] helper) {
        int inversionCount = 0;

        for (int i = low; i <= high; i++)
            helper[i] = input[i];

        int i = low;
        int j = medium + 1;
        int k = low;

        while (i <= medium && j <= high) {
            if (helper[i] <= helper[j]) {
                input[k] = helper[i];
                i++;
            } else {
                input[k] = helper[j];
                // the number of elements in the first half which the j element needs to jump over.
                // there is an inversion between each of those elements and j.
                inversionCount += (medium + 1 - i);
                j++;
            }
            k++;
        }

        // finish writing back in the input the elements from the first part
        while (i <= medium) {
            input[k] = helper[i];
            i++;
            k++;
        }
        return inversionCount;
    }

}

1

นี่คือการใช้งาน O (n * log (n)) perl:

sub sort_and_count {
    my ($arr, $n) = @_;
    return ($arr, 0) unless $n > 1;

    my $mid = $n % 2 == 1 ? ($n-1)/2 : $n/2;
    my @left = @$arr[0..$mid-1];
    my @right = @$arr[$mid..$n-1];

    my ($sleft, $x) = sort_and_count( \@left, $mid );
    my ($sright, $y) = sort_and_count( \@right, $n-$mid);
    my ($merged, $z) = merge_and_countsplitinv( $sleft, $sright, $n );

    return ($merged, $x+$y+$z);
}

sub merge_and_countsplitinv {
    my ($left, $right, $n) = @_;

    my ($l_c, $r_c) = ($#$left+1, $#$right+1);
    my ($i, $j) = (0, 0);
    my @merged;
    my $inv = 0;

    for my $k (0..$n-1) {
        if ($i<$l_c && $j<$r_c) {
            if ( $left->[$i] < $right->[$j]) {
                push @merged, $left->[$i];
                $i+=1;
            } else {
                push @merged, $right->[$j];
                $j+=1;
                $inv += $l_c - $i;
            }
        } else {
            if ($i>=$l_c) {
                push @merged, @$right[ $j..$#$right ];
            } else {
                push @merged, @$left[ $i..$#$left ];
            }
            last;
        }
    }

    return (\@merged, $inv);
}

1

คำตอบของฉันใน Python:

1- จัดเรียง Array ก่อนและทำสำเนา ในโปรแกรมของฉัน B แทนอาร์เรย์ที่เรียงลำดับ 2- วนซ้ำอาร์เรย์เดิม (ไม่เรียงลำดับ) และค้นหาดัชนีขององค์ประกอบนั้นในรายการที่เรียงลำดับ จดดัชนีขององค์ประกอบไว้ด้วย 3- ตรวจสอบให้แน่ใจว่าองค์ประกอบไม่มีรายการที่ซ้ำกันหากมีคุณต้องเปลี่ยนค่าดัชนีของคุณด้วย -1 เงื่อนไข while ในโปรแกรมของฉันกำลังทำอย่างนั้น 4- ให้นับการผกผันที่จะเป็นค่าดัชนีของคุณและลบองค์ประกอบเมื่อคุณคำนวณการผกผันแล้ว

def binarySearch(alist, item):
    first = 0
    last = len(alist) - 1
    found = False

    while first <= last and not found:
        midpoint = (first + last)//2
        if alist[midpoint] == item:
            return midpoint
        else:
            if item < alist[midpoint]:
                last = midpoint - 1
            else:
                first = midpoint + 1

def solution(A):

    B = list(A)
    B.sort()
    inversion_count = 0
    for i in range(len(A)):
        j = binarySearch(B, A[i])
        while B[j] == B[j - 1]:
            if j < 1:
                break
            j -= 1

        inversion_count += j
        B.pop(j)

    if inversion_count > 1000000000:
        return -1
    else:
        return inversion_count

print solution([4, 10, 11, 1, 3, 9, 10])

ฉันได้โพสต์คำตอบที่timeitเปรียบเทียบคำตอบของ Python ทั้งหมดสำหรับคำถามนี้ดังนั้นจึงมีโค้ดของคุณด้วย คุณอาจสนใจดูผลการจับเวลา
PM 2Ring

1

ฉันมีวิธีแก้ปัญหาที่แตกต่างออกไป แต่ฉันกลัวว่าจะใช้ได้กับองค์ประกอบอาร์เรย์ที่แตกต่างกันเท่านั้น

//Code
#include <bits/stdc++.h>
using namespace std;

int main()
{
    int i,n;
    cin >> n;
    int arr[n],inv[n];
    for(i=0;i<n;i++){
        cin >> arr[i];
    }
    vector<int> v;
    v.push_back(arr[n-1]);
    inv[n-1]=0;
    for(i=n-2;i>=0;i--){
        auto it = lower_bound(v.begin(),v.end(),arr[i]); 
        //calculating least element in vector v which is greater than arr[i]
        inv[i]=it-v.begin();
        //calculating distance from starting of vector
        v.insert(it,arr[i]);
        //inserting that element into vector v
    }
    for(i=0;i<n;i++){
        cout << inv[i] << " ";
    }
    cout << endl;
    return 0;
}

เพื่ออธิบายรหัสของฉันเรายังคงเพิ่มองค์ประกอบจากจุดสิ้นสุดของ Array สำหรับองค์ประกอบอาร์เรย์ขาเข้าใด ๆ เราจะพบดัชนีขององค์ประกอบแรกในเวกเตอร์ v ซึ่งมากกว่าองค์ประกอบขาเข้าของเราและกำหนดค่านั้นให้กับการกลับจำนวนดัชนีขององค์ประกอบที่เข้ามา หลังจากนั้นเราแทรกองค์ประกอบนั้นลงในเวกเตอร์ v ในตำแหน่งที่ถูกต้องเพื่อให้เวกเตอร์ v อยู่ในลำดับที่เรียง

//INPUT     
4
2 1 4 3

//OUTPUT    
1 0 1 0

//To calculate total inversion count just add up all the elements in output array

1

อีกวิธีหนึ่งของ Python สั้น ๆ ใช้ประโยชน์จากโมดูลบิเซคในตัวซึ่งมีฟังก์ชันในการแทรกองค์ประกอบลงในตำแหน่งในอาร์เรย์ที่เรียงลำดับและเพื่อค้นหาดัชนีขององค์ประกอบในอาร์เรย์ที่เรียงลำดับ

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

import bisect
def solution(A):
    sorted_left = []
    res = 0
    for i in xrange(1, len(A)):
        bisect.insort_left(sorted_left, A[i-1])
        # i is also the length of sorted_left
        res += (i - bisect.bisect(sorted_left, A[i]))
    return res

1
ฉันได้โพสต์คำตอบที่timeitเปรียบเทียบคำตอบของ Python ทั้งหมดสำหรับคำถามนี้ดังนั้นจึงมีโค้ดของคุณด้วย คุณอาจสนใจดูผลการจับเวลา : D
PM 2Ring

0

คำตอบง่ายๆ O (n ^ 2) คือการใช้สำหรับลูปที่ซ้อนกันและเพิ่มตัวนับสำหรับการผกผันทุกครั้ง

int counter = 0;

for(int i = 0; i < n - 1; i++)
{
    for(int j = i+1; j < n; j++)
    {
        if( A[i] > A[j] )
        {
            counter++;
        }
    }
}

return counter;

ตอนนี้ฉันคิดว่าคุณต้องการโซลูชันที่มีประสิทธิภาพมากขึ้นฉันจะคิดถึงมัน


3
สำหรับคำถามการบ้านทางที่ดีควรให้คำแนะนำที่เป็นประโยชน์มากกว่าการแก้ปัญหาจริง สอนผู้ชายให้ตกปลา
Doctor Jones

3
นั่นเป็นทางออกที่ชัดเจนที่นักเรียนคนอื่น ๆ ทุกคนจะได้รับก่อนฉันคิดว่าครูของพวกเขาต้องการการนำไปใช้ที่ดีขึ้นซึ่งจะทำให้พวกเขาได้คะแนนมากขึ้น
mbillard

ไม่จำเป็นต้องขึ้นอยู่กับระดับของหลักสูตรการเขียนโปรแกรม มันไม่ตรงไปตรงมาสำหรับผู้เริ่มต้น
Doctor Jones

พวกเขามักต้องการโซลูชัน n * log (n)
aderesh

0

วิธีแก้ปัญหาหนึ่งที่เป็นไปได้ใน C ++ ที่ตรงตามข้อกำหนดความซับซ้อนของเวลา O (N * log (N)) จะเป็นดังนี้

#include <algorithm>

vector<int> merge(vector<int>left, vector<int>right, int &counter)
{

    vector<int> result;

    vector<int>::iterator it_l=left.begin();
    vector<int>::iterator it_r=right.begin();

    int index_left=0;

    while(it_l!=left.end() || it_r!=right.end())
    {

        // the following is true if we are finished with the left vector 
        // OR if the value in the right vector is the smaller one.

        if(it_l==left.end() || (it_r!=right.end() && *it_r<*it_l) )
        {
            result.push_back(*it_r);
            it_r++;

            // increase inversion counter
            counter+=left.size()-index_left;
        }
        else
        {
            result.push_back(*it_l);
            it_l++;
            index_left++;

        }
    }

    return result;
}

vector<int> merge_sort_and_count(vector<int> A, int &counter)
{

    int N=A.size();
    if(N==1)return A;

    vector<int> left(A.begin(),A.begin()+N/2);
    vector<int> right(A.begin()+N/2,A.end());

    left=merge_sort_and_count(left,counter);
    right=merge_sort_and_count(right,counter);


    return merge(left, right, counter);

}

มันแตกต่างจากการเรียงลำดับการผสานปกติโดยตัวนับเท่านั้น


ดูเหมือนว่าจะค่อนข้างเหมือนกับโซลูชันJavaและPython ที่โพสต์ก่อนหน้านี้ซึ่งดูเหมือนจะใช้อัลกอริทึมเดียวกันดังนั้นฉันจึงไม่รู้สึกว่ามันเพิ่มมูลค่ามากไปกว่านั้น
Bernhard Barker

0

นี่คือโซลูชัน O (n log n) ของฉันใน Ruby:

def solution(t)
    sorted, inversion_count = sort_inversion_count(t)
    return inversion_count
end

def sort_inversion_count(t)
    midpoint = t.length / 2
    left_half = t[0...midpoint]
    right_half = t[midpoint..t.length]

    if midpoint == 0
        return t, 0
    end

    sorted_left_half, left_half_inversion_count = sort_inversion_count(left_half)
    sorted_right_half, right_half_inversion_count = sort_inversion_count(right_half)

    sorted = []
    inversion_count = 0
    while sorted_left_half.length > 0 or sorted_right_half.length > 0
        if sorted_left_half.empty?
            sorted.push sorted_right_half.shift
        elsif sorted_right_half.empty?
            sorted.push sorted_left_half.shift
        else
            if sorted_left_half[0] > sorted_right_half[0]
                inversion_count += sorted_left_half.length
                sorted.push sorted_right_half.shift
            else
                sorted.push sorted_left_half.shift
            end
        end
    end

    return sorted, inversion_count + left_half_inversion_count + right_half_inversion_count
end

และบางกรณีการทดสอบ:

require "minitest/autorun"

class TestCodility < Minitest::Test
    def test_given_example
        a = [-1, 6, 3, 4, 7, 4]
        assert_equal solution(a), 4
    end

    def test_empty
        a = []
        assert_equal solution(a), 0
    end

    def test_singleton
        a = [0]
        assert_equal solution(a), 0
    end

    def test_none
        a = [1,2,3,4,5,6,7]
        assert_equal solution(a), 0
    end

    def test_all
        a = [5,4,3,2,1]
        assert_equal solution(a), 10
    end

    def test_clones
        a = [4,4,4,4,4,4]
        assert_equal solution(a), 0
    end
end

0

วิธีที่ดีที่สุดคือการแก้ปัญหาผ่านการเรียงลำดับการผสานซึ่งจะรวมตัวเองเราสามารถตรวจสอบจำนวนการผกผันที่ต้องการโดยการเปรียบเทียบอาร์เรย์ซ้ายและขวา เมื่อใดก็ตามที่องค์ประกอบในอาร์เรย์ด้านซ้ายมีค่ามากกว่าองค์ประกอบที่อาร์เรย์ด้านขวาจะมีการกลับกัน

รวมวิธีการเรียงลำดับ: -

นี่คือรหัส รหัสเหมือนกับการเรียงลำดับการผสานยกเว้นข้อมูลโค้ดภายใต้mergeToParentวิธีการที่ฉันกำลังนับการผกผันภายใต้เงื่อนไขอื่นของ(left[leftunPicked] < right[rightunPicked])

public class TestInversionThruMergeSort {
    
    static int count =0;

    public static void main(String[] args) {
        int[] arr = {6, 9, 1, 14, 8, 12, 3, 2};
        

        partition(arr);

        for (int i = 0; i < arr.length; i++) {

            System.out.println(arr[i]);
        }
        
        System.out.println("inversions are "+count);

    }

    public static void partition(int[] arr) {

        if (arr.length > 1) {

            int mid = (arr.length) / 2;
            int[] left = null;

            if (mid > 0) {
                left = new int[mid];

                for (int i = 0; i < mid; i++) {
                    left[i] = arr[i];
                }
            }

            int[] right = new int[arr.length - left.length];

            if ((arr.length - left.length) > 0) {
                int j = 0;
                for (int i = mid; i < arr.length; i++) {
                    right[j] = arr[i];
                    ++j;
                }
            }

            partition(left);
            partition(right);
            mergeToParent(left, right, arr);
        }

    }

    public static void mergeToParent(int[] left, int[] right, int[] parent) {

        int leftunPicked = 0;
        int rightunPicked = 0;
        int parentIndex = -1;

        while (rightunPicked < right.length && leftunPicked < left.length) {

            if (left[leftunPicked] < right[rightunPicked]) {
                parent[++parentIndex] = left[leftunPicked];
                ++leftunPicked;

            } else {
                count = count + left.length-leftunPicked;
                if ((rightunPicked < right.length)) {
                    parent[++parentIndex] = right[rightunPicked];
                    ++rightunPicked;
                }
            }

        }

        while (leftunPicked < left.length) {
            parent[++parentIndex] = left[leftunPicked];
            ++leftunPicked;
        }

        while (rightunPicked < right.length) {
            parent[++parentIndex] = right[rightunPicked];
            ++rightunPicked;
        }

    }

}

อีกวิธีหนึ่งที่เราสามารถเปรียบเทียบอาร์เรย์อินพุตกับอาร์เรย์ที่เรียงลำดับ: - การใช้คำตอบ Diablo นี้ แม้ว่าวิธีนี้ไม่ควรเป็นแนวทางที่ต้องการเนื่องจากการลบองค์ประกอบ n ออกจากอาร์เรย์หรือรายการคือบันทึก (n ^ 2)

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;


public class TestInversion {

    public static void main(String[] args) {
        
        Integer [] arr1 = {6, 9, 1, 14, 8, 12, 3, 2};
        
        List<Integer> arr = new ArrayList(Arrays.asList(arr1));
        List<Integer> sortArr = new ArrayList<Integer>();
        
        for(int i=0;i<arr.size();i++){
            sortArr.add(arr.get(i));
         
        }
        
            
        Collections.sort(sortArr);
        
        int inversion = 0;
        
        Iterator<Integer> iter = arr.iterator();
        
        while(iter.hasNext()){
            
            Integer el = (Integer)iter.next();
            int index = sortArr.indexOf(el);
            
            if(index+1 > 1){
                inversion = inversion + ((index+1)-1);
            }
            
            //iter.remove();
            sortArr.remove(el);
            
        }
        
        System.out.println("Inversions are "+inversion);
        
        
        

    }


}

0

จำนวนการผกผันสูงสุดที่เป็นไปได้สำหรับรายการขนาดnสามารถสรุปได้โดยนิพจน์:

maxPossibleInversions = (n * (n-1) ) / 2

ดังนั้นสำหรับอาร์เรย์ของขนาด6การผกผันสูงสุดที่เป็นไปได้จะเท่ากับ15เป็นไปได้จะเท่ากับ

เพื่อให้เกิดความซับซ้อนของ n lognเราสามารถใช้อัลกอริธึมการผกผันในการเรียงลำดับการผสาน

ขั้นตอนทั่วไปมีดังนี้

  • แบ่งอาร์เรย์ออกเป็นสอง
  • เรียกรูทีน mergeSort หากองค์ประกอบใน subarray ด้านซ้ายมีค่ามากกว่าองค์ประกอบในอาร์เรย์ย่อยด้านขวา makeinversionCount += leftSubArray.length

แค่นั้นแหละ!

นี่เป็นตัวอย่างง่ายๆที่ฉันสร้างโดยใช้ Javascript:

var arr = [6,5,4,3,2,1]; // Sample input array

var inversionCount = 0;

function mergeSort(arr) {
    if(arr.length == 1)
        return arr;

    if(arr.length > 1) {
        let breakpoint = Math.ceil((arr.length/2));
        // Left list starts with 0, breakpoint-1
        let leftList = arr.slice(0,breakpoint);
        // Right list starts with breakpoint, length-1
        let rightList = arr.slice(breakpoint,arr.length);

        // Make a recursive call
        leftList = mergeSort(leftList);
        rightList = mergeSort(rightList);

        var a = merge(leftList,rightList);
        return a;
    }
}

function merge(leftList,rightList) {
    let result = [];
    while(leftList.length && rightList.length) {
        /**
         * The shift() method removes the first element from an array
         * and returns that element. This method changes the length
         * of the array.
         */
        if(leftList[0] <= rightList[0]) {
            result.push(leftList.shift());
        }else{
            inversionCount += leftList.length;
            result.push(rightList.shift());
        }
    }

    while(leftList.length)
        result.push(leftList.shift());

    while(rightList.length)
        result.push(rightList.shift());

    console.log(result);
    return result;
}

mergeSort(arr);
console.log('Number of inversions: ' + inversionCount);

0

การดำเนินการนับการผกผันในอาร์เรย์ด้วยการผสานการเรียงลำดับใน Swift:

โปรดทราบว่าจำนวนการแลกเปลี่ยนจะเพิ่มขึ้นโดย

nSwaps += mid + 1 - iL 

(ซึ่งเป็นความยาวสัมพัทธ์ของด้านซ้ายของอาร์เรย์ลบดัชนีขององค์ประกอบปัจจุบันทางด้านซ้าย)

... เพราะนั่นคือจำนวนองค์ประกอบที่องค์ประกอบทางด้านขวาของอาร์เรย์ต้องข้ามไป (# ของการผกผัน) เพื่อจัดเรียง

func merge(arr: inout [Int], arr2: inout [Int], low: Int, mid: Int, high: Int) -> Int {
    var nSwaps = 0;

    var i = low;
    var iL = low;
    var iR = mid + 1;

    while iL <= mid && iR <= high {
        if arr2[iL] <= arr2[iR] {
            arr[i] = arr2[iL]
            iL += 1
            i += 1
        } else {
            arr[i] = arr2[iR]
            nSwaps += mid + 1 - iL
            iR += 1
            i += 1
        }
    }

    while iL <= mid {
        arr[i] = arr2[iL]
        iL += 1
        i += 1
    }

    while iR <= high {
        arr[i] = arr2[iR]
        iR += 1
        i += 1
    }

    return nSwaps
}

func mergeSort(arr: inout [Int]) -> Int {
    var arr2 = arr
    let nSwaps = mergeSort(arr: &arr, arr2: &arr2, low: 0, high: arr.count-1)
    return nSwaps
}

func mergeSort(arr: inout [Int], arr2: inout [Int], low: Int, high: Int) -> Int {

    if low >= high {
        return 0
    }

    let mid = low + ((high - low) / 2)

    var nSwaps = 0;
    nSwaps += mergeSort(arr: &arr2, arr2: &arr, low: low, high: mid)
    nSwaps += mergeSort(arr: &arr2, arr2: &arr, low: mid+1, high: high)
    nSwaps += merge(arr: &arr, arr2: &arr2, low: low, mid: mid, high: high)

    return nSwaps
}

var arrayToSort: [Int] = [2, 1, 3, 1, 2]
let nSwaps = mergeSort(arr: &arrayToSort)

print(arrayToSort) // [1, 1, 2, 2, 3]
print(nSwaps) // 4

0

คำตอบส่วนใหญ่จะขึ้นอยู่กับMergeSortแต่ไม่ใช่วิธีเดียวที่จะแก้ปัญหานี้ได้O(nlogn)

ฉันจะพูดถึงแนวทางบางประการ

  1. ใช้ Balanced Binary Search Tree

    • เพิ่มต้นไม้ของคุณเพื่อเก็บความถี่สำหรับองค์ประกอบที่ซ้ำกัน
    • แนวคิดคือการนับโหนดที่มากขึ้นเรื่อย ๆ เมื่อต้นไม้ถูกข้ามจากรากไปยังใบไม้เพื่อแทรก

อะไรทำนองนี้.

Node *insert(Node* root, int data, int& count){
    if(!root) return new Node(data);
    if(root->data == data){
        root->freq++;
        count += getSize(root->right);
    }
    else if(root->data > data){
        count += getSize(root->right) + root->freq;
        root->left = insert(root->left, data, count);
    }
    else root->right = insert(root->right, data, count);
    return balance(root);
}

int getCount(int *a, int n){
    int c = 0;
    Node *root = NULL;
    for(auto i=0; i<n; i++) root = insert(root, a[i], c);
    return c;
}
  1. ใช้ Binary Indexed Tree
    • สร้างผลรวม BIT
    • วนซ้ำจากจุดสิ้นสุดและเริ่มค้นหาจำนวนองค์ประกอบที่มากขึ้น
int getInversions(int[] a) {
    int n = a.length, inversions = 0;
    int[] bit = new int[n+1];
    compress(a);
    BIT b = new BIT();
    for (int i=n-1; i>=0; i--) {
         inversions += b.getSum(bit, a[i] - 1);
         b.update(bit, n, a[i], 1);
     }
     return inversions;
}
  1. ใช้ Segment Tree
    • สร้างทรีเซกเมนต์การสรุป
    • วนซ้ำจากจุดสิ้นสุดของอาร์เรย์และแบบสอบถามระหว่าง[0, a[i]-1]และอัปเดตa[i] with 1
int getInversions(int *a, int n) {
    int N = n + 1, c = 0;
    compress(a, n);
    int tree[N<<1] = {0};
    for (int i=n-1; i>=0; i--) {
        c+= query(tree, N, 0, a[i] - 1);
        update(tree, N, a[i], 1);
    }
    return c;
}

นอกจากนี้เมื่อใช้BITหรือSegment-Treeความคิดที่ดีคือการทำCoordinate compression

void compress(int *a, int n) {
    int temp[n];
    for (int i=0; i<n; i++) temp[i] = a[i];
    sort(temp, temp+n);
    for (int i=0; i<n; i++) a[i] = lower_bound(temp, temp+n, a[i]) - temp + 1;
}

0

C ++ Θ (n lg n) วิธีการแก้ปัญหาด้วยการพิมพ์คู่ซึ่งเป็นจำนวนการผกผัน

int merge(vector<int>&nums , int low , int mid , int high){
    int size1 = mid - low +1;
    int size2= high - mid;
    vector<int>left;
    vector<int>right;
    for(int i = 0  ; i < size1 ; ++i){
        left.push_back(nums[low+i]);
    }
    for(int i = 0 ; i <size2 ; ++i){
        right.push_back(nums[mid+i+1]);
    }
    left.push_back(INT_MAX);
    right.push_back(INT_MAX);
    int i = 0 ;
    int j = 0;
    int start  = low;
    int inversion = 0 ;
    while(i < size1 && j < size2){
        if(left[i]<right[j]){
            nums[start] = left[i];
            start++;
            i++;
        }else{
            for(int l = i ; l < size1; ++l){
                cout<<"("<<left[l]<<","<<right[j]<<")"<<endl;
            }
            inversion += size1 - i;
            nums[start] = right[j];
            start++;
            j++;
        }
    }
    if(i == size1){
        for(int c = j ; c< size2 ; ++c){
            nums[start] = right[c];
            start++;
        }
    }
    if(j == size2){
        for(int c =  i ; c< size1 ; ++c){
            nums[start] = left[c];
            start++;
        }
    }
    return inversion;
}
int inversion_count(vector<int>& nums , int low , int high){
    if(high>low){
        int mid = low + (high-low)/2;
        int left = inversion_count(nums,low,mid);
        int right = inversion_count(nums,mid+1,high);
        int inversion = merge(nums,low,mid,high) + left + right;
        return inversion;
    }
    return 0 ;
}

-1

ใช้ mergesort ในการผสานตัวนับขั้นตอนที่เพิ่มขึ้นหากจำนวนที่คัดลอกไปยังเอาต์พุตมาจากอาร์เรย์ด้านขวา


การเพิ่มตัวนับ (น่าจะทีละตัว) สำหรับแต่ละองค์ประกอบจะทำให้คุณมีการผกผันน้อยเกินไป
Bernhard Barker

-1

ฉันเพิ่งต้องทำสิ่งนี้ใน R:

inversionNumber <- function(x){
    mergeSort <- function(x){
        if(length(x) == 1){
            inv <- 0
        } else {
            n <- length(x)
            n1 <- ceiling(n/2)
            n2 <- n-n1
            y1 <- mergeSort(x[1:n1])
            y2 <- mergeSort(x[n1+1:n2])
            inv <- y1$inversions + y2$inversions
            x1 <- y1$sortedVector
            x2 <- y2$sortedVector
            i1 <- 1
            i2 <- 1
            while(i1+i2 <= n1+n2+1){
                if(i2 > n2 || i1 <= n1 && x1[i1] <= x2[i2]){
                    x[i1+i2-1] <- x1[i1]
                    i1 <- i1 + 1
                } else {
                    inv <- inv + n1 + 1 - i1
                    x[i1+i2-1] <- x2[i2]
                    i2 <- i2 + 1
                }
            }
        }
        return (list(inversions=inv,sortedVector=x))
    }
    r <- mergeSort(x)
    return (r$inversions)
}

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