【问题标题】:Heapsort order of magnitude slower than Bubblesort (C++)堆排序比冒泡排序(C++)慢一个数量级
【发布时间】:2016-08-23 17:13:11
【问题描述】:

我已经实现了一个heap 数据结构,并用它来排序。我的理解是O(nlogn) 复杂性。然而,与bubble sort 相比,它慢了一个数量级——是的,我尝试将它运行在更大的阵列上。我在 SO(特别是 thisthis)检查了一些答案,但仍然丢失。谁能指出我在这里做错了什么?

结果是:

HEAP SORT: 12415690ns
QUICK SORT: 71ns
BUBBLE SORT: 541659ns

代码如下:

main.cpp:

#include <chrono>
#include <iostream>
#include <stdexcept>
#include <vector>

// #include "heap.cpp"
// #include "pqueue.cpp"
#include "sort.cpp"

using namespace std;
using namespace std::chrono;

template <class T>
void printVector (vector<T> A) {
    for (std::vector<int>::iterator it = A.begin(); it != A.end(); ++it) {
        std::cout << *it << ' ';
    }
    cout << endl;
}

template <class T>
vector<T> constructVector(int A[], std::size_t len, std::size_t num) {
    vector<T> res (A, A+len);
    for (std::size_t idx = 0; idx < num-1; ++idx) {
        res.push_back(A[idx%len]);
    }
    return res;
}

int main() {

    high_resolution_clock::time_point t1;
    high_resolution_clock::time_point t2;

    int a[] = {4, 1, 3, 2, 16, 9, 10, 14, 8, 7};
    std::size_t len = sizeof(a) / sizeof(int);
    vector<int> HEAP = constructVector<int>(a, len, 32000); // (a, a + sizeof(a) / sizeof(int));
    vector<int> QUICK = constructVector<int>(a, len, 32000); // (a, a + sizeof(a) / sizeof(int));
    vector<int> BUBBLE = constructVector<int>(a, len, 32000);

    // cout << "Original Array: "; printVector(HEAP);
    cout << "HEAP SORT: ";
    t1 = high_resolution_clock::now();
    heapsort(HEAP);
    t2 = high_resolution_clock::now();
    cout << duration_cast<nanoseconds>( t2 - t1 ).count() << "ns\n";
    // cout << "New Array: "; printVector(HEAP);

    // cout << "Original Array: "; printVector(QUICK);
    cout << "QUICK SORT: ";
    t1 = high_resolution_clock::now();
    quicksort(QUICK, 0, QUICK.size());
    t2 = high_resolution_clock::now();
    cout << duration_cast<nanoseconds>( t2 - t1 ).count() << "ns\n";
    // cout << "New Array: "; printVector(HEAP);

    // cout << "Original Array: "; printVector(QUICK);
    cout << "BUBBLE SORT: ";
    t1 = high_resolution_clock::now();
    bublesort(BUBBLE);
    t2 = high_resolution_clock::now();
    cout << duration_cast<nanoseconds>( t2 - t1 ).count() << "ns\n";
    // cout << "New Array: "; printVector(HEAP);

}

sort.cpp:

#ifndef __SORT_CPP_INCLUDED_
#define __SORT_CPP_INCLUDED_

#include <vector>
#include "heap.cpp"

template <class T>
void heapsort(std::vector<T> &A, bool increasing = true) {
    Heap<T> H(A, increasing);
    H.sort();
    A = H.get();
}

template <class T>
std::size_t partition(std::vector<T> &A, std::size_t p, std::size_t r) {
    T x = A[r-1];
    std::size_t i = p - 1;
    for (std::size_t j = p; j < r; ++j) {
        if (A[j] <= x) {
            ++i;
            A[i] ^= A[j];
            A[j] ^= A[i];
            A[i] ^= A[j];
        }
    }
    A[i+1] ^= A[r-1];
    A[r-1] ^= A[i+1];
    A[i+1] ^= A[r-1];

    return i + 1;
}

template <class T>
void quicksort(std::vector<T> &A, std::size_t p, std::size_t r) {
    if (p-1 < r) {
        std::size_t q = partition(A, p, r);
        quicksort(A, p, q);
        quicksort(A, q+1, r);
    }
}

template <class T>
void bublesort(std::vector<T> &A) {
    bool swapped = false; 
    do {
        swapped = false;
        for (std::size_t idx = 1; idx < A.size(); ++idx) {
            if (A[idx-1] > A[idx]) {
                // swap them
                A[idx] = A[idx-1];
                A[idx-1] = A[idx];
                A[idx] = A[idx-1];
                swapped = true;
            }
        }
    } while (swapped);
}
#endif

heap.cpp:

#ifndef __HEAP_CPP_INCLUDED__
#define __HEAP_CPP_INCLUDED__

#include <vector>

template <class T>
class Heap {
public:
    Heap(bool maxHeap = true) : heap_size(0), max_heap(maxHeap) {}
    Heap(const std::vector<T> &a, bool maxHeap = true) : A(a),  max_heap(maxHeap) {
        if (maxHeap) this->build_max_heap(); else this->build_min_heap(); }
    ~Heap() {}

protected:
    std::vector<T> A;
    std::size_t heap_size;
    bool max_heap;

public:
    std::size_t parent(std::size_t idx) { return (idx - 1) >> 1; }
    std::size_t left(std::size_t idx) { return (idx << 1) + 1; }
    std::size_t right (std::size_t idx) { return (idx + 1) << 1; }

public:
    std::vector<T> get() { return A; }
    std::size_t size() { return heap_size; }

    void sort();

    void build_max_heap();
    void build_min_heap();

    void max_heapify(std::size_t idx);
    void min_heapify(std::size_t idx);
};

template <class T>
void Heap<T>::sort() {
    if (this->heap_size <= 0) return; // Already sorted or empty
    if (this->heap_size != this->A.size()){ // Not sorted and not heapified
        max_heap ? build_max_heap() : build_min_heap();
    }
    for (std::size_t idx = this->A.size()-1; idx > 0; --idx) {
        A[0] ^= A[idx];
        A[idx] ^= A[0];
        A[0] ^= A[idx];
        --this->heap_size;
        max_heap ? max_heapify(0) : min_heapify(0);
    }
}

template<class T>
void Heap<T>::build_max_heap() {
    this->heap_size = this->A.size();
    for (std::size_t idx = (this->A.size() - 1) >> 1; idx > 0; --idx)
        this->max_heapify(idx);
    this->max_heapify(0);
}

template<class T>
void Heap<T>::build_min_heap() {
    this->heap_size = this->A.size();
    for (std::size_t idx = (this->A.size()-1) >> 1; idx > 0; --idx)
        this->min_heapify(idx);
    this->min_heapify(0);
}


template <class T>
void Heap<T>::max_heapify(std::size_t idx) {
    std::size_t l = this->left(idx);
    std::size_t r = this->right(idx);
    std::size_t largest;

    if (l < this->heap_size && A[l] > A[idx]) largest = l;
    else largest = idx;

    if (r < this->heap_size && A[r] > A[largest]) largest = r;

    if (largest != idx) {
        this->A[idx] ^= this->A[largest];
        this->A[largest] ^= this->A[idx];
        this->A[idx] ^= this->A[largest];
        this->max_heapify(largest);
    }
}

template <class T>
void Heap<T>::min_heapify(std::size_t idx) {
    std::size_t l = this->left(idx);
    std::size_t r = this->right(idx);
    std::size_t smallest;
    // std::cout << "DEBUG: " << idx << std::endl;
    if (l < this->heap_size && A[l] < A[idx]) smallest = l;
    else smallest = idx;

    if (r < this->heap_size && A[r] < A[smallest]) smallest = r;

    if (smallest != idx) {
        this->A[idx] ^= this->A[smallest];
        this->A[smallest] ^= this->A[idx];
        this->A[idx] ^= this->A[smallest];
        this->min_heapify(smallest);
    }
}
#endif

【问题讨论】:

  • 我没有看你的代码,只是想评论一下,大 O 表示法忽略了任何常数项,因此即使你尝试使用大数组,O(nlogn) 算法可能总是比O(n^2) 用于任何实际数组大小,只要常数项足够大。
  • 我知道O 中的常量会丢失,但我认为HEAP sort 的常量相对较小。如果我错了,请纠正我。
  • 我不是那个排序专家;)老实说,代码太长了,我无法详细研究它。我建议您使用分析器来找出热点的位置。
  • 你检查过编译器是如何优化这段代码的吗?如果由于堆栈帧而将递归函数保留为函数调用,则它们可能会非常昂贵。我认为主要的错误是在有这么多好的库的情况下尝试自己动手,除非是学习排序算法。
  • 离题建议,这将在以后为您节省大量解释:当您命名包含模板的文件并打算将它们包含在其他文件中时,不要使用 .cpp 扩展名。使用 .h 或 .hpp 或 .impl 或其他。 .cpp 会搞砸人们如何使用它们,IDE 将如何使用它们,并且通常会导致错误的 juju。

标签: c++ sorting quicksort bubble-sort heapsort


【解决方案1】:
  1. 您的冒泡排序不会交换,而只会复制。这将使它更快一些。不过,不确定仅凭这一点就可以解释速度如此之快。
  2. 您的Heap&lt;T&gt; 复制了数组。这可以解释缓慢。我猜你忘记了&amp;
  3. 您应该已经注意到 71ns 对 32k 数组进行排序是不真实的。您的快速排序永远不会对任何内容进行排序。您可以使用 std::sort 进行可靠的快速排序。
  4. 在编译时已知的太多,而且数字远非随机。在这种测试中切换到具有随机数的数组。

【讨论】:

    猜你喜欢
    • 2017-03-18
    • 2023-03-20
    • 2015-09-06
    • 2014-03-26
    • 2018-11-13
    • 2015-08-25
    • 2012-05-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多