📌  相关文章
📜  Python程序计算数组中的反转|设置 1(使用合并排序)

📅  最后修改于: 2022-05-13 01:56:56.498000             🧑  作者: Mango

Python程序计算数组中的反转|设置 1(使用合并排序)

数组的反转计数表示 - 数组距离排序多远(或接近)。如果数组已经排序,则反转计数为 0,但如果数组以相反的顺序排序,则反转计数为最大值。
形式上来说,如果 a[i] > a[j] 并且 i < j 两个元素 a[i] 和 a[j] 形成一个反转
例子:

Input: arr[] = {8, 4, 2, 1}
Output: 6
Explanation: Given array has six inversions:
(8, 4), (4, 2), (8, 2), (8, 1), (4, 1), (2, 1).

Input: arr[] = {3, 1, 2}
Output: 2
Explanation: Given array has two inversions:
(3, 1), (3, 2) 

方法1(简单):

方法:遍历数组,对于每个索引,找到数组右侧的较小元素的数量。这可以使用嵌套循环来完成。将数组中所有索引的计数相加并打印总和。

算法:

  1. 从头到尾遍历数组
  2. 对于每个元素,使用另一个循环找到小于当前数字的元素的计数,直到该索引。
  3. 总结每个索引的反转计数。
  4. 打印反转计数。

执行:

Python3
# Python3 program to count inversions 
# in an array
def getInvCount(arr, n):
    inv_count = 0
    for i in range(n):
        for j in range(i + 1, n):
            if (arr[i] > arr[j]):
                inv_count += 1
  
    return inv_count
  
# Driver Code
arr = [1, 20, 6, 4, 5]
n = len(arr)
print("Number of inversions are",
      getInvCount(arr, n))
# This code is contributed by Smitha Dinesh Semwal


Python3
# Python3 program to count inversions 
# in an array
  
# Function to Use Inversion Count
def mergeSort(arr, n):
    # A temp_arr is created to store
    # sorted array in merge function
    temp_arr = [0]*n
    return _mergeSort(arr, temp_arr, 
                      0, n - 1)
  
# This Function will use MergeSort to 
# count inversions
def _mergeSort(arr, temp_arr, left, right):
  
    # A variable inv_count is used to store
    # inversion counts in each recursive call
  
    inv_count = 0
  
    # We will make a recursive call if and 
    # only if we have more than one elements
  
    if left < right:
  
        # mid is calculated to divide the array 
        # into two subarrays
        # Floor division is must in case of python
  
        mid = (left + right)//2
  
        # It will calculate inversion 
        # counts in the left subarray
        inv_count += _mergeSort(arr, temp_arr, 
                                left, mid)
  
        # It will calculate inversion 
        # counts in right subarray
        inv_count += _mergeSort(arr, temp_arr, 
                                mid + 1, right)
  
        # It will merge two subarrays in 
        # a sorted subarray
        inv_count += merge(arr, temp_arr, 
                           left, mid, right)
    return inv_count
  
# This function will merge two subarrays 
# in a single sorted subarray
def merge(arr, temp_arr, left, mid, right):
  
    # Starting index of left subarray
    i = left     
  
    # Starting index of right subarray
    j = mid + 1 
  
    # Starting index of to be sorted subarray
    k = left     
    inv_count = 0
  
    # Conditions are checked to make sure that 
    # i and j don't exceed their
    # subarray limits.
  
    while i <= mid and j <= right:
  
        # There will be no inversion if 
        # arr[i] <= arr[j]
        if arr[i] <= arr[j]:
            temp_arr[k] = arr[i]
            k += 1
            i += 1
        else:
  
            # Inversion will occur.
            temp_arr[k] = arr[j]
            inv_count += (mid-i + 1)
            k += 1
            j += 1
  
    # Copy the remaining elements of left 
    # subarray into temporary array
    while i <= mid:
        temp_arr[k] = arr[i]
        k += 1
        i += 1
  
    # Copy the remaining elements of right 
    # subarray into temporary array
    while j <= right:
        temp_arr[k] = arr[j]
        k += 1
        j += 1
  
    # Copy the sorted subarray into 
    # Original array
    for loop_var in range(left, right + 1):
        arr[loop_var] = temp_arr[loop_var]
          
    return inv_count
  
# Driver Code
# Given array is
arr = [1, 20, 6, 4, 5]
n = len(arr)
result = mergeSort(arr, n)
print("Number of inversions are", result)
# This code is contributed by ankush_953


输出:

Number of inversions are 5

复杂性分析:

  • 时间复杂度: O(n^2),从头到尾遍历数组需要两个嵌套循环,所以时间复杂度是O(n^2)
  • 空间复杂度 O(1),不需要额外的空间。

方法2(增强合并排序):

方法:
假设数组左半边和右半边的反转次数(设为inv1和inv2); Inv1 + Inv2 中没有考虑哪些类型的反转?答案是——在合并步骤中需要计算的反转。因此,要获得需要添加的反转总数是左子数组、右子数组和merge()中的反转数。

inv_count1

如何获得合并()中的反转次数?
在合并过程中,让 i 用于索引左子数组, j 用于索引右子数组。在 merge() 的任何步骤中,如果 a[i] 大于 a[j],则存在 (mid – i) 反转。因为左右子数组是排序的,所以左子数组中的所有剩余元素 (a[i+1], a[i+2] ... a[mid]) 将大于 a[j]

inv_count2

完整的图片:

inv_count3

算法:

  1. 这个想法类似于归并排序,在每个步骤中将数组分成相等或几乎相等的两半,直到达到基本情况。
  2. 创建一个函数merge,当数组的两半合并时计算反转次数,创建两个索引i和j,i是前半部分的索引,j是后半部分的索引。如果 a[i] 大于 a[j],则存在 (mid – i) 反转。因为左右子数组是排序的,所以左子数组中的所有剩余元素 (a[i+1], a[i+2] ... a[mid]) 将大于 a[j]。
  3. 创建一个递归函数,将数组分成两半,并通过将前半部分的反转次数、后半部分的反转次数和合并两者的反转次数相加来找到答案。
  4. 递归的基本情况是给定的一半中只有一个元素。
  5. 打印答案

执行:

Python3

# Python3 program to count inversions 
# in an array
  
# Function to Use Inversion Count
def mergeSort(arr, n):
    # A temp_arr is created to store
    # sorted array in merge function
    temp_arr = [0]*n
    return _mergeSort(arr, temp_arr, 
                      0, n - 1)
  
# This Function will use MergeSort to 
# count inversions
def _mergeSort(arr, temp_arr, left, right):
  
    # A variable inv_count is used to store
    # inversion counts in each recursive call
  
    inv_count = 0
  
    # We will make a recursive call if and 
    # only if we have more than one elements
  
    if left < right:
  
        # mid is calculated to divide the array 
        # into two subarrays
        # Floor division is must in case of python
  
        mid = (left + right)//2
  
        # It will calculate inversion 
        # counts in the left subarray
        inv_count += _mergeSort(arr, temp_arr, 
                                left, mid)
  
        # It will calculate inversion 
        # counts in right subarray
        inv_count += _mergeSort(arr, temp_arr, 
                                mid + 1, right)
  
        # It will merge two subarrays in 
        # a sorted subarray
        inv_count += merge(arr, temp_arr, 
                           left, mid, right)
    return inv_count
  
# This function will merge two subarrays 
# in a single sorted subarray
def merge(arr, temp_arr, left, mid, right):
  
    # Starting index of left subarray
    i = left     
  
    # Starting index of right subarray
    j = mid + 1 
  
    # Starting index of to be sorted subarray
    k = left     
    inv_count = 0
  
    # Conditions are checked to make sure that 
    # i and j don't exceed their
    # subarray limits.
  
    while i <= mid and j <= right:
  
        # There will be no inversion if 
        # arr[i] <= arr[j]
        if arr[i] <= arr[j]:
            temp_arr[k] = arr[i]
            k += 1
            i += 1
        else:
  
            # Inversion will occur.
            temp_arr[k] = arr[j]
            inv_count += (mid-i + 1)
            k += 1
            j += 1
  
    # Copy the remaining elements of left 
    # subarray into temporary array
    while i <= mid:
        temp_arr[k] = arr[i]
        k += 1
        i += 1
  
    # Copy the remaining elements of right 
    # subarray into temporary array
    while j <= right:
        temp_arr[k] = arr[j]
        k += 1
        j += 1
  
    # Copy the sorted subarray into 
    # Original array
    for loop_var in range(left, right + 1):
        arr[loop_var] = temp_arr[loop_var]
          
    return inv_count
  
# Driver Code
# Given array is
arr = [1, 20, 6, 4, 5]
n = len(arr)
result = mergeSort(arr, n)
print("Number of inversions are", result)
# This code is contributed by ankush_953

输出:

Number of inversions are 5

复杂性分析:

  • 时间复杂度: O(n log n),使用的算法是分治法,所以每一层都需要遍历一次全数组,并且有log n层,所以时间复杂度是O(n log n)。
  • 空间复杂度 O(n),临时数组。

请注意,上面的代码修改(或排序)输入数组。如果我们只想计算反转,我们需要创建原始数组的副本并在副本上调用 mergeSort() 以保留原始数组的顺序。