数组数组的最优气泡排序算法

时间:2011-07-02 23:25:00

标签: algorithm sorting multidimensional-array bubble-sort

修复正整数nk

A为长度n的数组,其中A[i]为长度为k的数组,其中每个条目都为n-i。例如,对于n=5k=1,这只是

[ [5] , [4] , [3] , [2] , [1] ]

以及n=5k=2,这是

[ [5,5] , [4,4] , [3,3] , [2,2] , [1,1] ]

目标是通过交换相邻数组中的数字(例如,用A[i][j1]交换A[i+1][j2])对此数组数组进行冒泡排序,直到A[i]的每个条目都为i+1为止每i

问题是:需要多少次互换?什么是最佳算法

注意: 有许多更好的排序算法可供使用。但是,对于这个问题,我只对应用如上所述的冒泡排序感兴趣。我只能交换相邻数组中的条目,我只对所需的最小数量的交换感兴趣。我很欣赏其他排序算法的所有建议,但这是我想要解决的问题。

实施例

对于k=1,这是众所周知的。交换次数是被视为排列的A的反转次数,因此最小交换次数是二项式系数(n choose 2) = n(n-1)/2,这可以通过交换任何乱序对来实现:{{ 1}}。对于第一个示例,这是一个最佳的冒泡排序:

A[i] > A[j]

对于[ [5] , [4] , [3] , [2] , [1] ] [ [4] , [5] , [3] , [2] , [1] ] [ [4] , [5] , [2] , [3] , [1] ] [ [4] , [2] , [5] , [3] , [1] ] [ [4] , [2] , [5] , [1] , [3] ] [ [4] , [2] , [1] , [5] , [3] ] [ [4] , [1] , [2] , [5] , [3] ] [ [1] , [4] , [2] , [5] , [3] ] [ [1] , [4] , [2] , [3] , [5] ] [ [1] , [2] , [4] , [3] , [5] ] [ [1] , [2] , [3] , [4] , [5] ] ,使用相同的策略会产生所需的k=2交换范围。对于上面的示例,这意味着2 (n choose 2)交换。但是有一种只使用20交换的解决方案:

15

此解决方案适用于[ [5,5] , [4,4] , [3,3] , [2,2] , [1,1] ] [ [5,4] , [5,4] , [3,3] , [2,2] , [1,1] ] [ [5,4] , [3,4] , [5,3] , [2,2] , [1,1] ] [ [5,4] , [3,4] , [2,3] , [5,2] , [1,1] ] [ [5,4] , [3,4] , [2,3] , [1,2] , [5,1] ] [ [5,4] , [3,4] , [2,1] , [3,2] , [5,1] ] [ [5,4] , [3,1] , [2,4] , [3,2] , [5,1] ] [ [1,4] , [3,5] , [2,4] , [3,2] , [5,1] ] [ [1,4] , [3,2] , [5,4] , [3,2] , [5,1] ] [ [1,4] , [3,2] , [2,4] , [3,5] , [5,1] ] [ [1,4] , [3,2] , [2,4] , [3,1] , [5,5] ] [ [1,4] , [3,2] , [2,1] , [3,4] , [5,5] ] [ [1,4] , [1,2] , [2,3] , [3,4] , [5,5] ] [ [1,1] , [4,2] , [2,3] , [3,4] , [5,5] ] [ [1,1] , [2,2] , [4,3] , [3,4] , [5,5] ] [ [1,1] , [2,2] , [3,3] , [4,4] , [5,5] ] n=5(通过蛮力证明找到所有解决方案)。对于k=2,最佳解决方案需要n=6次交换,但解决方案看起来不如22那样好(按照右边5,然后是1左边,然后是5对,等等,所以我仍然不知道一个最优策略,更不用说一个公式或更好的交换数量。

我一直在考虑这几天,并没有提出任何有启发性的事情。如果有人对此问题有任何想法,请分享。了解n=5案件的更多信息我会很激动。对于一般案例的任何想法更好。

编辑:如果我不能按照你的喜好激励这个问题,我很抱歉,但这是一个尝试:排序排序所需的泡沫排序数量是组合数学和数论中非常重要的统计量,称为排列的反转数。您可以使用更好的算法对乱序排列进行排序,但这可以为您提供代数意义。如果这没有帮助,也许这个相关的SO帖子可能是:What is a bubble sort good for?


更新 oldest answer below给出了掉期数量的下限(和上限)。 second oldest answer给出了一个非常接近这个下界的算法(经常达到它)。如果有人能够改善界限,或者甚至更好地证明下面给出的算法是最佳的,那将是太棒了。

3 个答案:

答案 0 :(得分:10)

这不是最佳答案,但我想分享我的尝试,因为有人可能会改进它。我没有考虑找到一个公式来计算最小交换次数,而是考虑最优算法。该算法基于k = 2。

基本思想是基于信息增益。让我们假设A = {[i,j]:1< = i< = n,1< = j< = n}表示配置。在每个步骤中,我们有4 *(n-1)次交换可以从一种配置移动到另一种配置。例如,如果n = 2(即A = [{2,2},{1,1}]),那么我们有4个可能的交换A [0] [0]< - > A [1] [0],A [0] [0]< - > A [1] [1],A [0] [1]< - > A [1] [0]和A [0] [1]< - > A [1] [1]。因此,我们的目标是在需要从一个配置移动到另一个配置时选择具有高信息增益的交换。

棘手的部分将是“如何计算信息增益”。在我的解决方案(下面)中,信息增益基于值与其正确位置的距离。让我告诉你我的代码(用C ++编写)来理解我想说的话:

const int n = 5;
const int k = 2;

int gain(int item, int from, int to)
{
    if (to > from)
        return item - to;
    else
        return to - item ;
}

void swap(int &x, int &y)
{
    int temp = x;
    x = y;
    y = temp;
}

void print_config (int A[][k])
{
    cout << "[";
    for (int i=0; i<n; i++) {
        cout << " [";
        for (int j=0; j<k; j++) {
            cout << A[i][j] << ", ";
        }
        cout << "\b\b], ";
    }
    cout << "\b\b ]" << endl;
}

void compute (int A[][k], int G[][4])
{
    for (int i=0; i<n-1; i++)
    {
        G[i][0] = gain(A[i][0], i+1, i+2) + gain(A[i+1][0], i+2, i+1);
        G[i][1] = gain(A[i][0], i+1, i+2) + gain(A[i+1][1], i+2, i+1);
        G[i][2] = gain(A[i][1], i+1, i+2) + gain(A[i+1][0], i+2, i+1);
        G[i][3] = gain(A[i][1], i+1, i+2) + gain(A[i+1][1], i+2, i+1);
    }
}

int main()
{
    int A[n][k];
    int G[n-1][k*k];

    // construct initial configuration
    for (int i=0; i<n; i++)
        for (int j=0; j<k; j++)
            A[i][j] = n-i;

    print_config(A);

    int num_swaps = 0;
    int r, c;
    int max_gain;

    do {
        compute (A, G);

        // which swap has high info gain
        max_gain = -1;
        for (int i=0; i<n-1; i++)
            for (int j=0; j<k*k; j++)
                if (G[i][j] > max_gain) {
                   r = i;
                   c = j;
                   max_gain = G[i][j];
                }

        // Did we gain more information. If not terminate
        if (max_gain < 0) break;

        switch (c)
        {
            case 0: swap(A[r][0], A[r+1][0]); break;
            case 1: swap(A[r][0], A[r+1][1]); break;
            case 2: swap(A[r][1], A[r+1][0]); break;
            case 3: swap(A[r][1], A[r+1][1]); break;
        }

        print_config(A);
        num_swaps++;

    } while (1);
    cout << "Number of swaps is " << num_swaps << endl;
}

我为上述代码运行了上述代码n = 1,2,...和7.以下是答案(交换次数):0,2,5,10,15,23(非常接近),以及31.我认为当n是偶数时,函数gain()不能正常工作。你可以通过验证当n = 7时的交换次数来确认。方程的下界是31,所以这是当n = 7时的最优交换次数。

当n = 5时,我在这里打印输出(因为你正在寻找一个模式):

[ [5, 5],  [4, 4],  [3, 3],  [2, 2],  [1, 1] ]
[ [4, 5],  [5, 4],  [3, 3],  [2, 2],  [1, 1] ]
[ [4, 5],  [3, 4],  [5, 3],  [2, 2],  [1, 1] ]
[ [4, 5],  [3, 4],  [2, 3],  [5, 2],  [1, 1] ]
[ [4, 5],  [3, 4],  [2, 3],  [1, 2],  [5, 1] ]
[ [4, 3],  [5, 4],  [2, 3],  [1, 2],  [5, 1] ]
[ [4, 3],  [2, 4],  [5, 3],  [1, 2],  [5, 1] ]
[ [4, 3],  [2, 4],  [1, 3],  [5, 2],  [5, 1] ]
[ [4, 3],  [2, 4],  [1, 3],  [1, 2],  [5, 5] ]
[ [4, 3],  [2, 1],  [4, 3],  [1, 2],  [5, 5] ]
[ [1, 3],  [2, 4],  [4, 3],  [1, 2],  [5, 5] ]
[ [1, 3],  [2, 4],  [1, 3],  [4, 2],  [5, 5] ]
[ [1, 3],  [2, 1],  [4, 3],  [4, 2],  [5, 5] ]
[ [1, 1],  [2, 3],  [4, 3],  [4, 2],  [5, 5] ]
[ [1, 1],  [2, 3],  [2, 3],  [4, 4],  [5, 5] ]
[ [1, 1],  [2, 2],  [3, 3],  [4, 4],  [5, 5] ]

答案 1 :(得分:4)

我知道回答一个人自己的问题是相当俗气的,但我只是想出了这个问题,它更接近答案而不是问题的一部分。但是,这是一个完整的答案,不会被接受,所以如果有人能改进,请发表想法。

m的最小互换次数(例如k=2)受限于:

2 * (n choose 2) >= m >= (2n choose 2) / 3

为什么这样做?

上限是对数组的第一个元素进行冒泡排序,然后对数组的第二个元素进行冒泡排序。那部分并不那么棘手。

下限有点棘手,但这就是我的意思。让我们计算一下传递的数量,当一个较大的数字从较小数字的左边移动到该数字的右边时,会发生传递。这可能发生在ab的1次交换中,a更大,并且位于b左侧的数组中。如果a在一次交换中移动到具有b的数组,则可以进行2次交换,然后在稍后的交换中继续。为了正确跟踪事物,在这种情况下,计数通过一半。为了使计数更容易,当两个相同的数字分开然后重新组合时,它也算作一个通过。

数组在(2n choose 2)通过后完全排序,因此唯一的问题是一次交换可以发生多少次传递。以下是交换ac的简单示例:

... [a,b] , [c,d] ... 
... [c,b] , [a,d] ... 

现在让我们计算可能发生的最大次数:

  • a > c起,我们肯定会获得1次完整通行证。
  • 如果a > b,则我们会获得1/2通过,因为a必须在b处留下。{/ li>
  • 如果a > d,那么我们会获得1/2次通过,因为a在某个时候是d的权利。
  • 如果c < d,则我们会获得1/2通过,因为d必须在c处留下。{/ li>
  • 如果c < b,那么我们会获得1/2次通过,因为b在某个时候是c的权利。

因此,你可以做的最好的交换就是获得3次传球(1次全场和4次传球)。

为什么这不是一个完整的答案?

我不知道下限是否总能达到!我不认为是这样,尽管尝试了几次失败,但我无法编写实现它的算法。

答案 2 :(得分:2)

这是我想到的直观算法。它给出了我认为最佳解决方案的建设性证据。

以下是算法:

我尝试了n = 4 5 6 7 9,结果与badawi的结果相同:

这个想法如下:

1:选择了一个不在最终位置的极值(1或n开始)

2:找到最接近其最终位置的极值(在下面的示例中标有箭头)

第3: 如果它是最大的元素之一,

然后将它移到另一边并将该对中的所有最小元素向左移动

,否则

将其移至另一侧,并将每对中的所有最大元素向右移动。

注意:移位与使用每对中的smalles(resp最大)元素“冒泡”此值相等。

4:返回第2步,但是如果您选择其中一个较小,请选择其中一个,反之亦然。

这很直观,似乎有效:

示例n = 5:

11 22 33 44 55 
^
|
12 23 34 45 51 (4 moves) // shifted all larger numbers to the left
          ^
          |
52 13 24 43 51 (3 moves) // shifted all smaller numbers to the right
   ^
   |
52 34 24 35 11 (3 moves) // shifted all larger numbers to the left
          ^
          |
55 24 34 32 11 (3 moves) // smaller to the right
   ^
   |
55 44  33 22 11 (2 moves) // larger to left

共15次动作。

第二个例子n = 7:

11 22 33 44 55 66 77 // 6 moves
 ^
12 23 34 45 56 67 71 //5 moves
                ^
72 13 24 35 46 56 71 //5 moves
   ^
72 34 25 36 46 57 11 // 4 moves
                ^
77 24 35 26 36 45 11 //4 moves
   ^
77 45 36 26 35 42 11 //1 move
       ^       
77 65 34 26 35 42 11 //2 moves
         ^
77 65 34 56 34 22 11 //2 moves
          ^
77 66 54 53 34 22 11 //1 move
          ^
77 66 54 45 33 22 11 //1 move
          ^
77 66 55 44 33 22 11
总共:31

如果我不清楚,请不要犹豫问我。

手工操作很容易。您可以使用6或7自己尝试或编写算法。

我尝试了6,它给了23。 ,7分为31分,9分为53分,手动计算需要1分钟而不计算任何东西

为什么此解决方案最佳:

每次将一个大元素移动到另一侧时,将所有最小的元素移动到左侧。

所以移动所有大元素不会让你失去任何移动所有最小的元素。

你总是在“正确的方向”移动元素

此外,你移动极限元素,你做出最小的移动次数。 (这是因为算法采用最接近其最后位置的极值,没有移动丢失)

小元素的推理是相同的。

  

此算法为您提供最佳移动,因为它不会产生任何移动   不必要的举动。

希望我没有犯任何错误。

它证明了badawi结果是你所期望的最佳结果。