如何在快速排序算法中计算比较

How to count comparisons in quicksort algorithm

提问人:imnapr 提问时间:11/13/2023 最后编辑:imnapr 更新时间:11/13/2023 访问量:90

问:

我有一个快速排序的算法,我正在尝试计算其中的比较次数。它使用大小为 10、100、1000 和 10,000 的随机生成的数组,并且具有常量种子,因此每次都会在数组中生成相同的值。(因此,可以预先确定结果并进行比较以检查计数是否正确)。我得到的值是 13、147、1506 和 11014。我期待的是 25、630、10292 和 132882。

/**
 * @file quicksort.hpp
 */

#ifndef QUICKSORT_H
#define QUICKSORT_H

#include <algorithm>

static const int MIN_SIZE  = 10; // Smallest size of an array that quicksort will sort

/**
 * Sorts the items in an array into ascending order.
 * @pre  None.
 * @post  theArray is sorted into ascending order; n is unchanged.
 * @param theArray  The given array.
 * @param first  The first element to consider in theArray.
 * @param last  The last element to consider in theArray.
 * @return the number of comparisons
 */
template<class ItemType>
int insertionSort(ItemType theArray[], int first, int last) {
    int counter = 0;
    for (int unsorted = first + 1; unsorted <= last; unsorted++) {
        ItemType nextItem = theArray[unsorted];
        int loc = unsorted;
        while ((loc > first) && (counter++, theArray[loc - 1] > nextItem)) {
            
            theArray[loc] = theArray[loc - 1];
            loc--;
        }
        theArray[loc] = nextItem;
    }
    return counter;
}

/**
 * Arranges two specified array entries into sorted order by
 * exchanging them, if necessary.
 * @param theArray  The given array.
 * @param i  The index of the first entry to consider in theArray.
 * @param j  The index of the second entry to consider in theArray.
 * */
template<class ItemType>
void order(ItemType theArray[], int i, int j) {
    if (theArray[i] > theArray[j]) {
        std::swap(theArray[i], theArray[j]);
    }
}

/** 
 * Arranges the first, middle, and last entry in an array in sorted order.
 * @pre  theArray[first..last] is an array; first <= last.
 * @post  theArray[first..last] is is arranged so that its
 * first, middle, and last entries are in sorted order.
 * @param theArray  The given array.
 * @param first  The first entry to consider in theArray.
 * @param last  The last entry to consider in theArray.
 * @return  The index of the middle entry.
 */
template<class ItemType>
int sortFirstMiddleLast(ItemType theArray[], int first, int last) {
    int mid = first + (last - first) / 2;
    order(theArray, first, mid); // Make theArray[first] <= theArray[mid]
    order(theArray, mid, last);  // Make theArray[mid] <= theArray[last]
    order(theArray, first, mid); // Make theArray[first] <= theArray[mid]

    return mid;
}

/**
 * Partitions the entries in an array about a pivot entry for quicksort.
 * @pre  theArray[first..last] is an array; first <= last.
 * @post  theArray[first..last] is partitioned such that:
 * S1 = theArray[first..pivotIndex-1] <= pivot * theArray[pivotIndex] == pivot
 * S2 = theArray[pivotIndex+1..last]  >= pivot
 * @param theArray  The given array.
 * @param first  The first entry to consider in theArray.
 * @param last  The last entry to consider in theArray.
 * @return  The index of the pivot.
 */
template<class ItemType>
int partition(ItemType theArray[], int first, int last, int counter) {
    // Choose pivot using median-of-three selection
    int pivotIndex = sortFirstMiddleLast(theArray, first, last);

    // Reposition pivot so it is last in the array
    std::swap(theArray[pivotIndex], theArray[last - 1]);
    pivotIndex = last - 1;
    ItemType pivot = theArray[pivotIndex];

    // Determine the regions S1 and S2
    int indexFromLeft = first + 1;
    int indexFromRight = last - 2;

    bool done = false;
    while (!done) {
        // Locate first entry on left that is >= pivot
        
        while (counter++, theArray[indexFromLeft] < pivot) {
            
            indexFromLeft = indexFromLeft + 1;
        }
        
        // Locate first entry on right that is <= pivot
        while (counter++, theArray[indexFromRight] > pivot) {
            indexFromRight = indexFromRight - 1;
            
        }
    
        if (indexFromLeft < indexFromRight) {
            std::swap(theArray[indexFromLeft], theArray[indexFromRight]);
            indexFromLeft = indexFromLeft + 1;
            indexFromRight = indexFromRight - 1;
            
        }
        else {
            done = true;
        }
    }

    // Place pivot in proper position between S1 and S2, and mark its new location
    std::swap(theArray[pivotIndex], theArray[indexFromLeft]);
    pivotIndex = indexFromLeft;

    counter++;

    return pivotIndex;
}

/**
 * Sorts an array into ascending order. Uses the quick sort with
 * median-of-three pivot selection for arrays of at least MIN_SIZE
 * entries, and uses the insertion sort for other arrays.
 * @pre  theArray[first..last] is an array.
 * @post  theArray[first..last] is sorted.
 * @param theArray  The given array.
 * @param first  The first element to consider in theArray.
 * @param last  The last element to consider in theArray.
 * @return the number of comparisons
 */
template<class ItemType>
int quicksort(ItemType theArray[], int first, int last, int& counter) {
    
    if (last - first + 1 < MIN_SIZE) {
        counter += insertionSort(theArray, first, last);
    }
    else {
        // Create the partition: S1 | Pivot | S2
        int pivotIndex = partition(theArray, first, last, counter);

        // Sort subarrays S1 and S2
        quicksort(theArray, first, pivotIndex - 1, counter);
        quicksort(theArray, pivotIndex + 1, last, counter);
    }
    return counter; 
}

#endif

这是快速排序的代码。

std::cout << "Quicksort                               ";
    std::cout << "    " << std::left << quicksort(makeRandomArray(10, seed), 0, 10-1, comparisons);
    comparisons = 0;
    std::cout << "    " << quicksort(makeRandomArray(100, seed), 0, 100-1, comparisons);
    comparisons = 0;
    std::cout << "    " << quicksort(makeRandomArray(1000, seed), 0, 1000-1, comparisons);
    comparisons = 0;
    std::cout << "    " << quicksort(makeRandomArray(10000, seed), 0, 10000-1, comparisons) << std::endl;

这是 main.cpp 文件中用于打印快速排序比较的代码。我知道它既不漂亮也不高效,但就目前而言,它有效。我只想让我的快速排序计数准确无误。 编辑:数组生成器的代码:

int* makeRandomArray(int n, int seed) {
srand(seed);
int * a = new int[n];
for (int i = 0; i < n; i++) {
    a[i] = rand() % 1000;
}
return a;

}

C++ 快速排序

评论

2赞 Igor Tandetnik 11/13/2023
你为什么期待这些特别的、非常具体的比较数字?25、630、10292 和 132882 从何而来?
1赞 Igor Tandetnik 11/13/2023
里面有一个比较,你不算数。order
0赞 imnapr 11/13/2023
这些数字来自种子。当你把它放在 10、100、1000、10,000 的数组中时,随机数生成器每次都会生成相同的数字。我将编辑帖子以包含数组生成器的代码。(种子预设为9000)
0赞 Brian61354270 11/13/2023
@imnapr 他们问的是“我期待什么”的数字从何而来,而不是你如何生成数组内容。
0赞 Igor Tandetnik 11/13/2023
但是这个数字是如何得出的呢?为什么你希望算法对你的特定种子和由它生成的特定数组进行 25 次比较,不多也不少?就此而言,您为什么还要关心确切的比较次数?25

答:

2赞 Jerry Coffin 11/13/2023 #1

如果是我,我可能会创建一个重载的小类模板来计算调用它的频率。operator<

template <class T>
class comparison_counter {
    static unsigned comparisons = 0;
    T value;
public:
    comparison_counter(T t) : value(t) {}
    bool operator<(comparison_counter const &other) { 
        ++comparisons;
        return value < other.value;
    }

    comparison_counter &operator=(T t) { value = t; return *this; }

    void reset_counter() { comparisons = 0; }
    // ...
};

unsigned comparison_counter::comparisons;

我没有详细检查您的代码 - 您可能需要支持更多操作来访问基础值,但这是一般的想法。

这样一来,与其对 .完成排序后,将保留已完成的比较次数的计数。std::vector<int>std::vector<comparison_counter<int>>comparison_counter::comparisons

需要注意的几点:

  • 如果您进行多次排序,则需要在两者之间调用。reset_counter
  • 这目前不是线程安全的(尽管我怀疑你是否在乎)。

评论

0赞 tbxfreeware 11/13/2023
+1 很好的答案。我也会这样做。在 OP 的用例中,数组大小最高为 10,000。然而,一般来说,比较的数量(大致)与数组的大小有关。因此,您可以使用 或 作为变量 的类型,而不是 。std::size_tstd::vector<T>::size_typecomparisonsunsigned