QuickSort 最佳情况比平均情况最差

Posted

技术标签:

【中文标题】QuickSort 最佳情况比平均情况最差【英文标题】:QuickSort best case is worst than average case 【发布时间】:2015-03-22 12:10:53 【问题描述】:

我对快速排序有一个恼人的问题。因此,我必须研究快速排序在最佳、平均和最坏情况下的性能(在操作中)。

操作包括:比较+归因。

目前我在这种情况下测试快速排序,例如(100 到 10.000 个元素的数组)。当我测试它并得到以下结果时出现问题(例如 100 个元素数组):

最佳情况: 大约。 4853 次操作

平均情况: 大约。 1468 次操作

最坏情况:大约。 9024 次操作

理论上说,QuickSort 在最佳和平均情况下的效率都是O(n*log n)。正如你所看到的,我得到了一个完全不同的结果,它违反了理论。

(我使用Profiler作为自定义库生成随机数组。FillRandomArray方法的最后一个参数是order(0-无序,1-升序,2-降序)。

这是我使用的代码:

#include <iostream>
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include "Profiler.h"

#define MIN_SIZE 100
#define MAX_SIZE 10000


struct sortingAlg
        std::string type;
        int atributions;
        int comparisons;
;

int partition(int *givenArray, int p, int r, sortingAlg& sortingAlgoritm)

        int x = givenArray[r];
        int i = p - 1;
        for (int j = p; j <= r - 1; ++j)
        
                sortingAlgoritm.comparisons += 1;
                if (givenArray[j] <= x)
                
                        sortingAlgoritm.atributions += 2;
                        i += 1;
                        int aux = givenArray[i];
                        givenArray[i] = givenArray[j];
                        givenArray[j] = aux;
                
        

        sortingAlgoritm.atributions += 2;
        givenArray[r] = givenArray[i + 1];
        givenArray[i + 1] = x;
        return i + 1;


void quicksort(int *givenArray, int beginning, int length, sortingAlg& sortingAlgoritm)

        if (beginning < length)
        
                int q = partition(givenArray, beginning, length, sortingAlgoritm);
                quicksort(givenArray, beginning, q-1,  sortingAlgoritm);
                quicksort(givenArray, q + 1, length, sortingAlgoritm);
        


int main()

        Profiler profiler("heapProfiler");

        sortingAlg sortingAlgs[2];
        sortingAlgs[0].type = "HS";
        sortingAlgs[0].atributions = 0;
        sortingAlgs[0].comparisons = 0;

        sortingAlgs[1].type = "QS";
        sortingAlgs[1].atributions = 0;
        sortingAlgs[1].comparisons = 0;


        for (int i = MIN_SIZE; i <= MAX_SIZE; i += 100)
        
                std::cout << "Sorting array for " << i << " elements.." << std::endl;


                sortingAlgs[1].atributions = 0;
                sortingAlgs[1].comparisons = 0;

                int *avg =  new int[i];
                FillRandomArray(avg, i, 0, 1000, false, 0);
                quicksort(avg, 1, i, sortingAlgs[1]);

                profiler.countOperation("AVG_QuickSort_ALL", i, sortingAlgs[1].atributions + sortingAlgs[1].comparisons);
                profiler.createGroup("AVG_QuickSort", "AVG_QuickSort_ALL");

                sortingAlgs[1].atributions = 0;
                sortingAlgs[1].comparisons = 0;

                int *best =  new int[i];
                FillRandomArray(best, i, 0, 1000, false, 1);
                quicksort(best, 1, i, sortingAlgs[1]);

                profiler.countOperation("BEST_QuickSort_ALL", i, sortingAlgs[1].atributions + sortingAlgs[1].comparisons);
                profiler.createGroup("BEST_QuickSort", "BEST_QuickSort_ALL");

                sortingAlgs[1].atributions = 0;
                sortingAlgs[1].comparisons = 0;


                int *worst = new int[i];
                FillRandomArray(worst, i, 0, 1000, false, 2);
                quicksort(worst, 1, i, sortingAlgs[1]);

                profiler.countOperation("WORST_QuickSort_ALL", i, sortingAlgs[1].atributions + sortingAlgs[1].comparisons);
                profiler.createGroup("WORST_QuickSort", "WORST_QuickSort_ALL");
        
        std::cout << "Building complete...! Creating profiler groups... Opnening reports!" << std::endl;
        profiler.showReport();


        return 0;

有什么想法吗?谢谢。

【问题讨论】:

【参考方案1】:

简短的回答是,它看起来就像您没有正确选择支点以成为(甚至接近)最佳情况。事实上,考虑到您似乎选择了支点,我很惊讶按顺序排序数据并没有比您显示的更糟糕。

为了使有序数据成为最佳情况,您希望选择枢轴作为当前分区部分中间的元素。在这种情况下,您不必移动任何元素来进行分区。

顺便说一句:IMO,您的代码不必要地难以阅读。例如,pr 作为参数名称几乎没有意义。更好的名称将极大地帮助您破译您的代码。同样,除非您有非常具体的理由不这样做,否则我也会考虑替换您的:

                    int aux = givenArray[i];
                    givenArray[i] = givenArray[j];
                    givenArray[j] = aux;

类似:

using std::swap;
// ...


                   swap(givenArray[i], givenArray[j]);

这不仅更具可读性,而且对于处理 int 以外的其他类型元素的代码可能更有效,因为最有效的交换可能不是复制整个元素。

就个人而言,如果我想像你一样分析比较和分配的计数,我会采取不同的做法:我会定义一个类型来跟踪该类型的比较和分配:

template <class T>
class counted 
    static size_t comparisons;
    static size_t assignments;
    T val;
public:
    counted(T val) : val(val) 
    bool operator<(counted c) 
        ++comparisons;
        return val < c.val;
    

    counted &operator=(counted &other)  
        ++assignments;
        val = other.val;
        return *this;
    
    static void reset()  
        assignments = 0;
        comparisons = 0;
    
    std::pair<size_t, size_t> counts()  
        return std::make_pair(assignments, comparisons); 
    
;

然后排序代码只会进行排序,而要分析排序代码,您只需传递处理分析的这种类型的数组(或者最好是向量)。排序完成后,您可以从该类型中检索计数,重置计数,然后进行下一个测试。这样,您几乎可以分析任何排序代码,而无需重写排序代码来进行分析(例如,如果您想将您的快速排序与 std::sort 进行比较以获取各种输入顺序,您可以很容易地做到这一点)。

【讨论】:

关于枢轴,我将其更改为数组的中间元素(例如myArray[length/2])。 (10.000 个元素)的结果是:最佳(1,822,000)和平均(380,000)。这样好吗?我的意思是应该有一点区别。 @SJD:您必须选择枢轴作为当前正在分区的部分的中间(也许您是,但这还不是很清楚)。【参考方案2】:

我觉得你选择pivot的时候有问题。

对于“最佳情况”方案,您应该选择“最佳支点”,但您没有这样做。如果您总是选择枢轴作为中间的数字,它会起作用。

【讨论】:

以上是关于QuickSort 最佳情况比平均情况最差的主要内容,如果未能解决你的问题,请参考以下文章

渐近时间复杂度与最佳、平均和最坏情况输入的组合

冒泡排序

算法中的快速排序 quicksort

算法最坏,平均和最佳情况(Worst, Average and Best Cases)-------geeksforgeeks 翻译

随手编程---快速排序(QuickSort)-Java实现

随手编程---快速排序(QuickSort)-Java实现