用于排序不同类型对象的高效算法

时间:2016-05-26 05:48:01

标签: algorithm

鉴于我们有不同类型的视频集合(比如类型A,B和C,......),我们正在寻找一种有效的算法来将这些对象整理成一个播放列表,以便我们获得最大的分散。也就是说,我们希望确保来自A的两个视频不会背靠背放置,如果可以避免的话。 播放列表将重复播放(它会在播放结束时重新播放。因此也应考虑此方面)。

什么是一个有效的算法可以执行上述良好的分散?

示例输入:

  • 类型A的五个对象(A1,A2,A3,A4,A5)
  • 3个B类物体(B1,B2, B3)

输出 - 不是最佳

A1,B1,A2,B2,A3,B3,A4,A5

这不是最佳的,因为在播放A4之后,A5播放然后播放列表循环播放并播放A1。现在我们连续播放了A型的3个视频。

最佳输出

A1,B1,A2,A3,B2,A4,B4,A5

这是最佳选择,因为我们只有2个相同类型的视频背靠背播放。

请注意,该算法适用于不同类型和视频。

6 个答案:

答案 0 :(得分:3)

这类似于我几年前遇到的一个问题:混合液体以避免分层。我们的想法是,如果您将液体A,B和C混合到一个容器中,您不希望将它们一个接一个地倒入容器中。相反,你想以相对比例添加一些A,一些B,一些C等。

与在列表中均匀分配项目的问题相同。

假设您有30个A类型,20个B类型和10个C类型,总共60个视频。然后,每个其他视频必须是A.每三个视频都是一个B,每六个视频就是一个C.

所以A是0,2,4,6,8等。 B&#39,6 / 12,12等。 C&C在0,6,12,18等等。

显然,您必须解决冲突。

我这样做的方法是构建一个包含视频类型及其频率的最小堆,以及从频率/ 2开始的当前位置。所以堆包含:{A,2,1},{B,3,1},{C,6,3}

要生成列表,请从堆中删除最低项并将其添加到列表中。然后,将其频率添加到当前位置,并将其放回堆中。因此,在第一次完成后,您输出了A,而您的堆现在包含:{B,3,1},{A,2,2},{C,6,3}

输出B,然后将其添加回来,为您提供{A,2,2},{C,6,3},{B,3,4}

您当然也希望保留每个项目的计数,每次输出该项目时都会减少,如果计数变为0,则不会将其添加回堆中。

一年前我在我的博客中写了很长篇篇章。请参阅Evenly distributing items in a list

就效率而言,算法具有复杂度O(n log k),其中n是视频总数,k是视频类型的数量。

答案 1 :(得分:3)

这里的算法适用于任意数量的类型,而不仅仅是2:

  • 调用类型(例如A,B,C,...)T。
  • 调用T N(T)类型的项目数。

伪码中的算法:

var size = 0;
for_each (T)
  size += N(T);

var output = array(size); // Initialised to null, to mean gap (no item)
var gapsRemaining = size;

for_each (T)
{
  var itemsRemaining = N(T);
  var i = 0;
  var limit = itemsRemaining / gapsRemaining;
  while (itemsRemaining > 0)
  {
    if (itemsRemaining / (gapsRemaining - i) >= limit)
    { output[{i}th_gap] = next_item_of_type(T)
      gapsRemaining--;
      itemsRemaining--;
    }
    else
      i++;
  }
}

{i} th_gap从零开始,就像数组索引一样。

如果你能在恒定时间内完成{i} th_gap(可以通过使用另一个计数器来完成),那么算法是线性时间,即O(大小) O(大小* numTypes)。

对于您的示例,它提供输出a b a b a a b a

修改

重新思考:如果你只维护每种类型的计数,它就不会那么复杂。

使用JS代码(http://js.do/code/96801

var numItems = [5,3]; // for AAAAABBB
var numItems = [6,3,5]; // for AAAAAABBBCCCCC
var totalNumItems = 0;
for (i=0; i<numItems.length; i++)
    totalNumItems += numItems[i];
var limits = [];
for (i=0; i<numItems.length; i++)
    limits[i] = numItems[i] / totalNumItems;
var numGaps = totalNumItems;
var output = [];
for (i=0; i<totalNumItems; i++)
{   var bestValue = 0;
    var bestType;
    for (j=0; j<numItems.length; j++)
    {   var value = numItems[j] / numGaps - limits[j];
        if (value >= bestValue)
        {   bestValue = value;
            bestType = j;
    }   }
    output[i] = bestType;
    numItems[bestType]--;
    numGaps--;
}
for (i=0; i<totalNumItems; i++)
    document.writeln(output[i]);
document.writeln("<br>");

但正如@Jim所说,它是O(n * k),其中n是totalNumItems,k是numItems.length。所以他的O(n log k)解决方案具有更好的复杂性。

修改2

调整以更好地打破关系,因此更喜欢更频繁的项目。 [10,1,1]的先前代码输出为caaabaaaaaaa,现为abaaaaacaaaa

http://js.do/code/96848

var numItems = [10,1,1];
var totalNumItems = 0;
for (i=0; i<numItems.length; i++)
    totalNumItems += numItems[i];
var limits = [];
for (i=0; i<numItems.length; i++)
    limits[i] = numItems[i] / totalNumItems;
var numGaps = totalNumItems;
var output = [];
for (i=0; i<totalNumItems; i++)
{   var bestValue = 0;
    var bestNumItems = 0;
    var bestType;
    for (j=0; j<numItems.length; j++)
    {   var value = numItems[j] / numGaps - limits[j];
        if (value >= bestValue && numItems[j] > bestNumItems)
        {   bestValue = value;
            bestNumItems = numItems[j];
            bestType = j;
    }   }
    output[i] = bestType;
    numItems[bestType]--;
    numGaps--;
}
for (i=0; i<totalNumItems; i++)
    document.writeln(output[i]);
document.writeln("<br>");

答案 2 :(得分:1)

假设你有A1,A2,......,An和B1,B2,...,Bm。

如果n> m,那么将至少播放2个A项(如果播放列表是循环的[不断重复全部])。

您应该首先将A项放在一个循环上。然后在每两个连续的A项之间放置一个B项。这将分隔下一个到下一个A项。然后放下剩余的B项,如果有剩余的话。

如果你想确保第一个和最后一个项目都不是A,那么在开头放置一个A项目,如果有足够的B项目,则在最后放置一个B项目。

作为一种计算算法,为每个A项指定数字(双重类型以允许它们为有理数)并将这些数字从最小到最大排序。然后为每个B项分配连续A项的平均值。例如:

A1 = 3 A2 = 5 A3 = 10

ArrayA(0)= 3

ArrayA(1)= 5

ArrayA(2)= 10

然后假设你有4个B项。

 On Error Resume Next

 For n=0 to 3

 ArrayB(n)=(ArrayA(n)+ArrayA(n+1))/2

 Loop

这个循环将尝试调用ArrayA(3)并给出错误,我们将跳过错误恢复接下来。然后,您可以将随机数分配给未分配的B项。

最后,组合两个数组,对它们进行排序。你会得到排序的数字。按这些数字,以最佳排序的方式回拨物品。

答案 3 :(得分:1)

这个问题似乎并不容易,因为组合的数量很大。如果我是对的,对于三种类型的NaNbNc个视频,有(Na+Nb+Nc-1)!/Na!Nb!Nc!种可能性。 (分子中的-1来自于彼此的循环排列的序列被认为是相同的事实。)

对组合结构没有清楚的了解,我会尝试如下:

  • 定义一个评价播放列表分散程度的优点数字。这可能是同一组视频之间(循环)距离的总和。

例如

A1, B1, A2, B2, A3, B3, A4, A5

给出

2+2+2+2+2+4+1+1 = 16

A1, B1, A2, A3, B2, A4, B4, A5

给出

2+3+1+2+2+2+3+1 = 16

(这可能是一个效率不高的指标,短距离应该更加严厉。)

  • 通过在可用类型中进行选择直到它们耗尽来尝试随机序列,并对序列进行评级。经过多次随机试验,保持最佳分数。 [我会在没有替换的情况下模拟绘图,以便以平衡的方式消耗不同的类型。]

对于小N,可以进行详尽的试验。

<强>更新

很明显,我建议的简单指标给出了一个恒定的值!

答案 4 :(得分:0)

划分最大的视频数组,并在分割点插入其他元素。

视频类型A :( An = 5)[A1,A2,A3,A4,A5]

视频类型B:(Bn = 3)[B1,B2,B3]

 1. Choose the Video type having maximum number of instances, in this
    case A.
 2. Divide: (An=5)[A1, A2, A3, A4, A5] / 2 = 2, (An=2)[A1, A2](An=3)[A3, A4, A5]
 3. Now insert one instance of B at the point of division as per step 1, 
    i.e (An=2)[A1, A2](Bn=1)[B1](An=3)(A3, A4, A5)
 4. Now repeat step 2, 3 with (An=2)[A1, A2]  and   (An=3)[A3, A4, A5] and so forth like we do in binary search.
    Final arrangement: (An=1)[A1](Bn=1)[B2](An=1)[A2](Bn=1)[B1](An=2)[A3, A4](Bn=1)[B3](An=1)[A5]

答案 5 :(得分:0)

以下是Java程序,用于排列数组,以使两个相邻的数字都不相同。

        public int[] rearrangeArray(int[] arr) {
            int n = arr.length;

            // Store frequencies of all elements
            // of the array
            int[] count = new int[1000]; 
            int[] visited = new int[1000]; 

            for (int i = 0; i < n; i++)
                count[arr[i]]++;

            // Insert all characters with their frequencies
            // into a priority_queue
            PriorityQueue<RandomKey> pq = new PriorityQueue<>(11, new KeyComparator());

            // Adding high freq elements in descending order
            for (int i = 0; i < n; i++) {
                int val = arr[i];

                if (count[val] > 0 && visited[val] != 1)
                    pq.add(new RandomKey(count[val], val));
                visited[val] = 1;
            }

            // 'result[]' that will store resultant value
            int[] result = new int[n];

            // work as the previous visited element
            // initial previous element will be ( '-1' and
            // it's frequency will also be '-1' )
            RandomKey prev = new RandomKey(-1, -1);

            // Traverse queue
            int l = 0;
            while (pq.size() != 0) {

                // pop top element from queue and add it
                // to result
                RandomKey k = pq.peek();
                pq.poll();
                result[l] = k.num;

                // If frequency of previous element is less
                // than zero that means it is useless, we
                // need not to push it
                if (prev.freq > 0)
                    pq.add(prev);

                // make current element as the previous
                // decrease frequency by 'one'
                (k.freq)--;
                prev = k;
                l++;
            }

            return result;
        }



     public class RandomKey {

             int freq;
             int num;

            RandomKey(int freq, int num) {
                this.freq = freq;
                this.num = num;
            }
        }


class KeyComparator implements Comparator<RandomKey> {

    // Overriding compare()method of Comparator
    public int compare(RandomKey k1, RandomKey k2) {
        if (k1.freq < k2.freq)
            return 1;
        else if (k1.freq > k2.freq)
            return -1;
        return 0;
    }
}