我还能在哪里改进这个Merge Sort

时间:2015-11-01 02:36:37

标签: c++ mergesort

virtual Indexed<T>& sort(Indexed<T>& data)
{
    //base case

    Vector<T> temp1, temp2, temp3;
    //for (int i = 0; i < data.getSize(); i++)
    //{
    //  cout << data.getElementAt(i) << ", ";
    //}
    mergeSortHelper(data, 0, data.getSize() - 1, temp1, temp2);
    for (int i = 0; i < data.getSize(); i++)
    {
        cout << data.getElementAt(i) << ", ";
    }
    cout << endl;
    return data;
}

void mergeSortHelper(Indexed<T>& data, int begin, int end, Vector<T> &left, Vector<T> &right)
{
    //base case, if there is only 1 item in each side, merge them and return
    if ((begin - end) == 0)
    {
        //left.setElementAt(data.getElementAt(begin), 0);
        //left = storage;
        return;

    }
    else
    {
        //Vector<T> right;
        int midPoint = (end + begin) / 2;
        mergeSortHelper(data, begin, midPoint, left, right);
        mergeSortHelper(data, midPoint + 1, end, left, right);
        //perform merging


        int i = 0, leftCounter = 0, leftLimit = (midPoint - begin) + 1, rightCounter = 0, rightLimit = (end - (midPoint + 1)) + 1;
        for (int i = 0; i < leftLimit; i++)
        {
            if (i < leftLimit)
                left.setElementAt(data.getElementAt(begin + i), i);
            if (i < rightLimit)
                right.setElementAt(data.getElementAt(midPoint + 1 + i), i);
            //cout << left.getElementAt(i) << ", ";
        }
        while (leftCounter < leftLimit || rightCounter < rightLimit)
        {
            if (leftCounter >= leftLimit)
            {
                //done sorting the left side
                data.setElementAt(right.getElementAt(rightCounter), begin + i);
                //temp.setElementAt(right.getElementAt(rightCounter), i);
                rightCounter++;
            }
            else if (rightCounter >= rightLimit)
            {
                //done sorting the right side
                data.setElementAt(left.getElementAt(leftCounter), begin + i);
                //temp.setElementAt(left.getElementAt(leftCounter), i);
                leftCounter++;
            }
            else
            {
                if (left.getElementAt(leftCounter) < right.getElementAt(rightCounter))
                {
                    data.setElementAt(left.getElementAt(leftCounter), begin + i);
                    //temp.setElementAt(left.getElementAt(leftCounter), i);
                    leftCounter++;
                }
                else if (left.getElementAt(leftCounter) > right.getElementAt(rightCounter))
                {
                    data.setElementAt(right.getElementAt(rightCounter), begin + i);
                    //temp.setElementAt(right.getElementAt(rightCounter), i);
                    rightCounter++;
                }
                else
                {
                    data.setElementAt(right.getElementAt(rightCounter), begin + i);
                    //temp.setElementAt(right.getElementAt(rightCounter), i);
                    i++;
                    data.setElementAt(left.getElementAt(leftCounter), begin + i);
                    //temp.setElementAt(left.getElementAt(leftCounter), i);
                    leftCounter++;
                    rightCounter++;

                }

            }
            i++;
        }

    }

    //return temp;

我还可以改进哪些方法来加快排序速度?我花了这么多时间才开始正常工作。但是我记得从类快速排序和合并排序是O(NlogN),但我上面的quickSort代码不是那么有效,它需要2.5倍的时间来排序相同数量的项目。

4 个答案:

答案 0 :(得分:1)

这里的问题至多是不变因素 - 不是渐近的表现。 IOW存在进入过早微优化的强大风险。也就是说,当它不是那么不成熟时,仍然值得学习,所以...

首先,高性能排序是AFAICT始终混合算法。像插入排序那样糟糕的O(n ^ 2)算法实际上非常好,只要n足够小,所以实际的排序算法倾向于在n变小时切换策略。如果你正在排序一百万件物品并且你的战略转换发生在n = 10,那么听起来你可能只是为了获得一个简单的10项目排序,但是在你的百万件物品排序中这种情况发生了大约100,000次因此累积的好处可以很好地还清。

对于硬编码的嵌入式嵌套ifs中的n个非常小的n的情况,使得对于每个置换,应用预定的最佳移动/交换集合。例如,对于n = 3,项目有6种可能的排序 - 6种要检测的情况,6种移动/交换要弄清楚。对于n = 4,现在已有24个案例,所以已经非常痛苦,并且不要忘记太多的代码也会减慢速度。

一旦你确定算法本身是正确的,看看哪些工作可以从内循环中移出。这有双重好处 - 首先,这项工作不那么频繁。其次,内部循环本身变得更简单,因此编译器可以更好地优化它。价格当然是代码的整体复杂性增加,并且要注意 - 编译器应该已经为你做了一些这样的事情,所以很容易不仅浪费时间让编译器为它工作,而且通过获取来引入bug这是错误的,并阻止其他编译器优化,最终导致代码变慢。

回归测试,测量前后的性能,如果有疑问,请检查生成的机器代码等等。

一个显而易见的目标 - 拥有一个循环并且在每次迭代时检查合并的一个/另一个源是否耗尽是否方便,但这也是低效的。因此,当任何一个源耗尽时,检测一次并使用特殊情况循环来完成对其他源的处理。

另外,为了避免对每个循环进行耗尽检查,如果具有最少项目的源数组具有k个项目,则可以安全地进行k次迭代而不进行耗尽检查。所以有一个外部循环,你在那里检查k是什么,以及一个没有用尽检查的k项内循环。

第二点使得内部循环对于大多数合并保持简单,对结束的利益较少(因为每次检查时k变小)。它还增加了额外的复杂性 - 外部循环 - 所以它不是免费的。第一点意味着你有一个简单的内循环,可以在最后完成。

另一种方法可能是让内循环只从一个源获取,直到它耗尽或下一个项太大,然后另一个内循环从另一个源获取。

最后,非常非常难以击败std::sort,除非您对特定平台(基于GPU的排序?)或您的数据(O(n)整数 - 有所了解 - 只排序算法?)。基本上,编写标准库实现的人已经尝试了所有这些以及更多。

答案 1 :(得分:0)

您的代码过于复杂。合并排序有两个部分:分割和合并。每个都应该是一个功能。

您还应该拥有顶级功能(用户将使用的公共界面)。

为了获得最大速度,您应该在顶层功能中分配一次所需的所有额外内存,然后将其传递给下级功能使用。 (使用向量并将其调整为数据长度。)

当(结束 - 开始== 0)时不要触底 - 这是一对浪费的函数调用。当他们的差异 2 时停止。对两者进行排序并开始重新启动树。

您的合并算法应该都在一个函数中,您可以稍微清理它。基本合并算法是:

  • 虽然A和B都有元素:
    • 如果A的第一个元素小于B的第一个元素:
      • 删除A并将其附加到结果
      • 否则删除B并将其附加到结果
  • 追加结果
  • 中仍有元素(A或B)的序列

希望这有帮助。

答案 2 :(得分:0)

首先,请注意Hoare有充分理由将其命名为“quicksort” - 它明显快于当时已知的其他类型(包括Merge-sort)。换句话说,不要指望合并排序能够跟上快速排序(作为一般规则),几乎不管它的实施情况如何。

那就是说,你可以通常比你在这里实现更好的合并排序。特别是,合并排序可以“自下而上”而不是“自上而下”实现。现在,你的函数基本上将数组分成两半,然后调用自己对数组的每一半进行排序。

所以,你做了很多递归调用,基本上每个都做的是计算需要排序的数组部分中间的索引。这为相对简单的计算增加了相当多的开销。

最后,无论如何,结果几乎已成定局。你总是继续进行递归调用,直到分区只有一个或两个元素(或左右)。

就你需要的额外空间来说,一个与输入相同大小的额外数组就足够了(即使这不是绝对必要的,但是就地合并的技术增加了复杂性,所以它们更难以写得正确,无论如何都要慢一点。)

既然你知道你要拆分数组,直到你只得到两个项目,你可以从那里开始。只需取出第一对元素,如果它们出现故障就更换它们。继续使用数组的其余部分。

然后你处于合并阶段:将第一对与第二对合并,第三对与第四对合并,依此类推。继续进行数组的其余部分。重复该过程,直到您对整个数组进行排序。

您通常不希望在分区中只使用两个项目。您通常希望首先在(例如)10个项目上使用插入排序,然后进入合并阶段(尽管精确的大小并不重要)。

答案 3 :(得分:0)

这些例子相当快,在我的系统上,Intel 2600K 3.4ghz,对400万个伪随机32位无符号整数进行排序需要不到0.4秒。自下而上使用迭代生成用于合并运行的索引,而自上而下使用递归生成索引,开销很小,因为大部分时间用于合并数据,而不是生成索引。

自下而上:

template <typename T>
void BottomUpMergeSort(T a[], T b[], size_t n);
template <typename T>
void BottomUpCopy(T a[], T b[], size_t ll, size_t rr);
template <typename T>
void BottomUpMerge(T a[], T b[], size_t ll, size_t rr, size_t ee);

template <typename T>
void MergeSort(T a[], size_t n)             // entry function
{
    if(n < 2)                               // if size < 2 return
        return;
    T *b = new T[n];
    BottomUpMergeSort(a, b, n);
    delete[] b;
}

size_t GetPassCount(size_t n)               // return # passes
{
    size_t i = 0;
    for(size_t s = 1; s < n; s <<= 1)
        i += 1;
    return(i);
}

template <typename T>
void BottomUpMergeSort(T a[], T b[], size_t n)
{
size_t s = 1;                               // run size 
    if(GetPassCount(n) & 1){                // if odd number of passes
        for(s = 1; s < n; s += 2)           // swap in place for 1st pass
            if(a[s] < a[s-1])
                std::swap(a[s], a[s-1]);
        s = 2;
    }
    while(s < n){                           // while not done
        size_t ee = 0;                      // reset end index
        while(ee < n){                      // merge pairs of runs
            size_t ll = ee;                 // ll = start of left  run
            size_t rr = ll+s;               // rr = start of right run
            if(rr >= n){                    // if only left run
                rr = n;
                BottomUpCopy(a, b, ll, rr); //   copy left run
                break;                      //   end of pass
            }
            ee = rr+s;                      // ee = end of right run
            if(ee > n)
                ee = n;
            BottomUpMerge(a, b, ll, rr, ee);
        }
        std::swap(a, b);                    // swap a and b
        s <<= 1;                            // double the run size
    }
}

template <typename T>
void BottomUpCopy(T a[], T b[], size_t ll, size_t rr)
{
    while(ll < rr){                         // copy left run
        b[ll] = a[ll];
        ll++;
    }
}

template <typename T>
void BottomUpMerge(T a[], T b[], size_t ll, size_t rr, size_t ee)
{
    size_t o = ll;                          // b[]       index
    size_t l = ll;                          // a[] left  index
    size_t r = rr;                          // a[] right index
    while(1){                               // merge data
        if(a[l] <= a[r]){                   // if a[l] <= a[r]
            b[o++] = a[l++];                //   copy a[l]
            if(l < rr)                      //   if not end of left run
                continue;                   //     continue (back to while)
            while(r < ee)                   //   else copy rest of right run
                b[o++] = a[r++];
            break;                          //     and return
        } else {                            // else a[l] > a[r]
            b[o++] = a[r++];                //   copy a[r]
            if(r < ee)                      //   if not end of right run
                continue;                   //     continue (back to while)
            while(l < rr)                   //   else copy rest of left run
                b[o++] = a[l++];
            break;                          //     and return
        }
    }
}

自上而下。共同递归调用(... AtoA,... AtoB)避免了不必要的数据复制。 TopDownMerge()与BottomUpMerge()相同。

template <typename T>
void TopDownSplitMergeAtoA(T a[], T b[], size_t ll, size_t ee);
template <typename T>
void TopDownSplitMergeAtoB(T a[], T b[], size_t ll, size_t ee);
template <typename T>
void TopDownMerge(T a[], T b[], size_t ll, size_t rr, size_t ee);

template <typename T>
void MergeSort(T a[], size_t n)             // entry function
{
    if(n < 2)                               // if size < 2 return
        return;
    T *b = new T[n];
    TopDownSplitMergeAtoA(a, b, 0, n);
    delete[] b;
}

template <typename T>
void TopDownSplitMergeAtoA(T a[], T b[], size_t ll, size_t ee)
{
    if((ee - ll) == 1)                  // if size == 1 return
        return;
    size_t rr = (ll + ee)>>1;           // midpoint, start of right half
    TopDownSplitMergeAtoB(a, b, ll, rr);
    TopDownSplitMergeAtoB(a, b, rr, ee);
    TopDownMerge(b, a, ll, rr, ee);     // merge b to a
}

template <typename T>
void TopDownSplitMergeAtoB(T a[], T b[], size_t ll, size_t ee)
{
    if((ee - ll) == 1){                 // if size == 1 copy a to b
        b[ll] = a[ll];
        return;
    }
    size_t rr = (ll + ee)>>1;           // midpoint, start of right half
    TopDownSplitMergeAtoA(a, b, ll, rr);
    TopDownSplitMergeAtoA(a, b, rr, ee);
    TopDownMerge(a, b, ll, rr, ee);     // merge a to b
}

template <typename T>
void TopDownMerge(T a[], T b[], size_t ll, size_t rr, size_t ee)
{
    size_t o = ll;                          // b[]       index
    size_t l = ll;                          // a[] left  index
    size_t r = rr;                          // a[] right index
    while(1){                               // merge data
        if(a[l] <= a[r]){                   // if a[l] <= a[r]
            b[o++] = a[l++];                //   copy a[l]
            if(l < rr)                      //   if not end of left run
                continue;                   //     continue (back to while)
            while(r < ee)                   //   else copy rest of right run
                b[o++] = a[r++];
            break;                          //     and return
        } else {                            // else a[l] > a[r]
            b[o++] = a[r++];                //   copy a[r]
            if(r < ee)                      //   if not end of right run
                continue;                   //     continue (back to while)
            while(l < rr)                   //   else copy rest of left run
                b[o++] = a[l++];
            break;                          //     and return
        }
    }
}