📜  使用合并排序树的修改后的快速排序所涉及的比较

📅  最后修改于: 2021-04-17 09:06:42             🧑  作者: Mango

在QuickSort中,理想的情况是始终选择中位数作为枢轴,因为这样可以缩短时间。在本文中,合并排序树用于在QuickSort中查找不同范围的中位数,并分析比较次数。

例子:

Input : arr = {4, 3, 5, 1, 2}
Output : 11
Explanation
We have to make 11 comparisons when we 
apply quick sort to the array.

如果我们仔细分析快速排序算法,则每次将数组提供给快速排序函数,该数组始终由L到R范围内的数字组成。最初是[1到N],然后是[ 1旋转– 1]和[旋转+ 1到N],依此类推。同样不容易观察到每个可能数组中数字的相对顺序不会改变。现在,为了获取枢轴元素,我们只需要获取数组中的中间数字,即第(r – 1 + 2)/ 2数字。

为了有效地做到这一点,我们可以使用持久段树,Fenwick树或合并排序树。本文重点介绍合并排序树的实现。

在“修改后的快速排序算法”中,我们选择了数组的枢轴元素作为数组的中位数。现在,确定中位数要求我们在对数组进行排序后找到被考虑的中间元素,数组本身就是一个O(n * log(n))运算,其中n是数组的大小。

假设我们有一个介于L到R的范围,那么该范围的中位数计算如下:

Median of A[L; R] = Middle element of sorted(A[L; R])
                  = (R - L + 1)/2th element of 
                    sorted(A[L; R])

让我们考虑一下在快速排序算法中有P个分区,这意味着我们必须通过对从L到R的数组范围进行排序来找到枢轴,其中L和R是每个分区的起点和终点。这是昂贵的。

但是,我们在每个分区中都有一个从L到R的数字排列,因此我们可以找到该范围内最小的ceil((R – L + 1)/ 2)最小数字,就像我们知道何时对这个分区进行排序那么最终该元素最终将成为中间元素,从而成为枢轴。现在,小于支点的元素将转到左子树,大于支点的元素将进入右子树,以维持其顺序。

我们对所有分区P重复此过程,并找到每个分区中涉及的比较。由于在当前分区中,该分区从L到R的所有元素都与枢轴进行了比较,因此在当前分区中我们进行了(R – L +1)个比较。我们还需要通过递归计算来考虑形成的左右子树中的总比较。因此,我们得出结论,

Total Comparisons =  Comparisons in Current Partition +
                     Comparisons in Left partition +
                     Comparisons in right partition

我们在上面讨论了用于有效查找枢纽元素的方法,此处
使用合并排序树的K阶统计信息可用于查找与所讨论的相同的结果。

// CPP program to implement number of comparisons
// in modified quick sort
#include 
using namespace std;
  
const int MAX = 1000;
  
// Constructs a segment tree and stores tree[]
void buildTree(int treeIndex, int l, int r, int arr[],
               vector tree[])
{
  
    /*  l => start of range,
        r => ending of a range
        treeIndex => index in the Segment Tree/Merge 
                     Sort Tree  */
    /* leaf node */
    if (l == r) {
        tree[treeIndex].push_back(arr[l - 1]);
        return;
    }
  
    int mid = (l + r) / 2;
  
    /* building left subtree */
    buildTree(2 * treeIndex, l, mid, arr, tree);
  
    /* building left subtree */
    buildTree(2 * treeIndex + 1, mid + 1, r, arr, tree);
  
    /* merging left and right child in sorted order */
    merge(tree[2 * treeIndex].begin(),
          tree[2 * treeIndex].end(),
          tree[2 * treeIndex + 1].begin(),
          tree[2 * treeIndex + 1].end(),
          back_inserter(tree[treeIndex]));
}
  
// Returns the Kth smallest number in query range
int queryRec(int segmentStart, int segmentEnd,
             int queryStart, int queryEnd, int treeIndex,
             int K, vector tree[])
{
    /*  segmentStart => start of a Segment,
        segmentEnd   => ending of a Segment,
        queryStart   => start of a query range,
        queryEnd     => ending of a query range,
        treeIndex    => index in the Segment 
                        Tree/Merge Sort Tree,
        K  => kth smallest number to find  */
    if (segmentStart == segmentEnd)
        return tree[treeIndex][0];
  
    int mid = (segmentStart + segmentEnd) / 2;
  
    // finds the last index in the segment
    // which is <= queryEnd
    int last_in_query_range = 
             (upper_bound(tree[2 * treeIndex].begin(),
               tree[2 * treeIndex].end(), queryEnd)
                    - tree[2 * treeIndex].begin());
  
    // finds the first index in the segment
    // which is >= queryStart
    int first_in_query_range = 
              (lower_bound(tree[2 * treeIndex].begin(),
              tree[2 * treeIndex].end(), queryStart)
                       - tree[2 * treeIndex].begin());
  
    int M = last_in_query_range - first_in_query_range;
  
    if (M >= K) {
  
        // Kth smallest is in left subtree,
        // so recursively call left subtree for Kth
        // smallest number
        return queryRec(segmentStart, mid, queryStart,
                        queryEnd, 2 * treeIndex, K, tree);
    }
  
    else {
  
        // Kth smallest is in right subtree,
        // so recursively call right subtree for the
        // (K-M)th smallest number
        return queryRec(mid + 1, segmentEnd, queryStart,
                queryEnd, 2 * treeIndex + 1, K - M, tree);
    }
}
  
// A wrapper over query()
int query(int queryStart, int queryEnd, int K, int n, int arr[],
          vector tree[])
{
  
    return queryRec(1, n, queryStart, queryEnd,
                    1, K, tree);
}
  
/* Calculates total Comparisons Involved in Quick Sort
   Has the following parameters:
     
   start => starting index of array
   end   => ending index of array
   n     => size of array
   tree  => Merge Sort Tree */
  
int quickSortComparisons(int start, int end, int n, int arr[],
                         vector tree[])
{
    /* Base Case */
    if (start >= end)
        return 0;
  
    // Compute the middle point of range and the pivot
    int middlePoint = (end - start + 2) / 2;
    int pivot = query(start, end, middlePoint, n, arr, tree);
  
    /* Total Comparisons = (Comparisons in Left part + 
                            Comparisons of right +
                            Comparisons in parent) */
  
    // count comparisons in parent array
    int comparisons_in_parent = (end - start + 1);
  
    // count comparisons involved in left partition
    int comparisons_in_left_part = 
     quickSortComparisons(start, pivot - 1, n, arr, tree);
  
    // count comparisons involved in right partition
    int comparisons_in_right_part = 
      quickSortComparisons(pivot + 1, end, n, arr, tree);
  
    // Return Total Comparisons
    return comparisons_in_left_part + 
           comparisons_in_parent + 
           comparisons_in_right_part;
}
  
// Driver code
int main()
{
    int arr[] = { 4, 3, 5, 1, 2 };
  
    int n = sizeof(arr) / sizeof(arr[0]);
  
    // Construct segment tree in tree[]
    vector tree[MAX];
    buildTree(1, 1, n, arr, tree);
  
    cout << "Number of Comparisons = " 
        << quickSortComparisons(1, n, n, arr, tree);;
  
    return 0;
}

输出:

Number of Comparisons = 11

计算枢轴的每个查询的复杂度为O(log 2 (n))