【问题标题】:Optimal bubble sorting algorithm for an array of arrays of numbers数字数组数组的最佳冒泡排序算法
【发布时间】:2011-09-27 10:58:42
【问题描述】:

修复正整数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 都是i+1

问题是:需要多少次交换以及什么是最佳算法

注意: 有很多很多更好的排序算法可供使用。但是,对于这个问题,我只对应用上述冒泡排序感兴趣。我只能交换来自相邻数组的条目,并且我只对必要的最小交换次数感兴趣。我非常感谢其他排序算法的所有建议,但这是我试图理解的问题。

示例:

对于k=1,这是众所周知的。交换次数是A 的反转数,被视为一个排列,因此最小交换次数是二项式系数(n choose 2) = n(n-1)/2,这可以通过交换任何乱序对来获得: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=5k=2(通过蛮力证明找到所有解决方案)。对于n=6,最佳解决方案采用22 交换,但该解决方案看起来不如n=5 的解决方案好(按照右5,然后是左1,然后是右5,等等),所以我仍然不知道最佳策略,更不用说公式或更好的交换次数界限。

我已经考虑了几天了,但没有提出任何启发性的想法。如果有人对此问题有任何想法,请分享。我会很高兴知道更多关于k=2 案例的信息。对于一般情况下的任何想法都更好。

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


更新oldest answer below 给出了交换次数的下限(和上限)。 second oldest answer 给出了一个非常接近这个下限的算法(经常达到它)。如果有人可以改进界限,或者更好地证明下面给出的算法是最优的,那就太棒了。

【问题讨论】:

  • 我不明白。根据您的描述,k=1 的结果应该是[ [1], [2], [3], [4], [5] ],您可以在 2 次交换中获得,而不是 10 次。我哪里错了?
  • @svick:我很抱歉。我隐含地假设您只能交换相邻数组中的条目。我现在已经在问题中明确提出了这个假设。感谢您指出我的疏忽。
  • 您只关心交换次数(性能问题)还是比较次数?
  • @Yochai:我根本不在乎比较。我被允许做的唯一操作是相邻数组条目之间的交换,我想最小化这些。
  • 所以你可以在开始交换之前进行任意数量的比较?

标签: algorithm sorting multidimensional-array bubble-sort


【解决方案1】:

这是我想到的一个直观的算法。它为我认为的最佳解决方案提供了建设性的证明。

算法如下:

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

思路如下:

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

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

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

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

否则

将它移到另一边,并将每对中所有最大的元素向右移动。

注意: 移位相当于用每对的最小(或最大)元素“冒泡”这个值。

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 , 手动计算需要一分钟而不计算任何东西

为什么这个解决方案是最佳的:

每次将一个大元素移到另一侧时,都会将这对元素中的所有最小元素移到左侧。

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

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

此外,您为移动极端元素所做的移动次数最少。 (这是因为算法取的是最接近他最后位置的极值,不会丢失任何动作)

小元素也是一样。

此算法为您提供最佳移动,因为它不会做出任何动作 不必要的动作。

希望我没有犯任何错误。

这证明了 badawi 结果如您所愿。

【讨论】:

  • 所以??没有一个反应?至少可以说这听起来是不是真的?
  • 据我所知,这是badawi的算法。您认为这是最佳的“证明”与其说是一种启发,不如说是一种证明。在你正在做的动作类型中,这些是最好的,但目前还不清楚没有更好的动作可以做。从信息论观点来看,您希望像我的绑定参数中那样最大化“通过”的数量,但是您的方法并不能保证全局优化,而是局部优化。所以每一步都是一个最优的移动,但在战略位置进行次优的移动可能会得到更好的结果。
  • @PengOne 好的,我明白为什么它不能证明是最优的。考虑一下,证明这个解决方案是最好的唯一方法是达到下限(不是这种情况),就像在案例 k=1 的演示中一样。
【解决方案2】:

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

基本思想是基于信息增益。让我们假设 A = {[i,j] : 1配置。在每个步骤中,我们有 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] ]

【讨论】:

  • 这很有趣,谢谢!我对 n=9 的下限给出了 51,所以如果你实际上可以得到 53,那非常接近并且可能是最优的。我将不得不考虑一下,但非常感谢!
  • 在 n = 7 的情况下,算法会给出你的下限 31,这是最优的。
【解决方案3】:

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

k=2 的最小交换次数,例如 m,受以下限制:

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

为什么会这样?

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

下限有点棘手,但我是这样得出的。让我们计算 pass 的次数,其中 pass 发生在较大的数字从较小数字的左侧移动到该数字的右侧时。这可能发生在 ab 的 1 次交换中,a 更大并且在 b 左侧的数组中。如果a 在一次交换中移动到带有b 的阵列,然后在以后的交换中继续移动,它也可能需要两次交换。为了正确地跟踪事情,在这种情况下,count pass 分成两半。为方便计数,当两个相同数字分开然后重新组合时,也算一次。

数组在(2n choose 2) 传递后完全排序,所以唯一的问题是一次交换可以发生多少次传递。这是一个简单的例子,其中ac 被交换:

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

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

  • 因为a &gt; c,我们肯定得到1个全通。
  • 如果是a &gt; b,那么我们通过了 1/2,因为 a 一定在某个时候离开了 b
  • 如果a &gt; d,那么我们通过了 1/2,因为a 将在某个时候正好在d 的右边。
  • 如果c &lt; d,那么我们通过了 1/2,因为 d 一定在某个时候离开了 c
  • 如果c &lt; b,那么我们通过了 1/2,因为b 将在某个时刻正好位于c 的右边。

因此,您可以在交换中做的最好的事情是获得 3 次传球(1 次全场和 4 次半场)。

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

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

【讨论】:

  • 5 为 15,6 为 22,这些是你说的最好的数字。 7 岁能得到什么?
猜你喜欢
  • 2013-09-09
  • 1970-01-01
  • 2013-09-28
  • 1970-01-01
  • 2016-02-10
  • 1970-01-01
  • 2014-06-20
相关资源
最近更新 更多