【问题标题】:Sorting: how to sort an array that contains 3 kind of numbers排序:如何对包含 3 种数字的数组进行排序
【发布时间】:2012-06-19 11:59:34
【问题描述】:

例如:int A[] = {3,2,1,2,3,2,1,3,1,2,3};

如何高效地排序这个数组?

这是一个工作面试,我只需要一个伪代码。

【问题讨论】:

  • en.wikipedia.org/wiki/Quicksort。如果是面试,那我猜你不能回答 Array.Sort() ;)
  • 明天面试,但是已经面试过的人被问到了这个问题
  • 作弊方式。如果您真的想了解它们,请查找 sorting
  • 为什么不只计算每个有多少,然后从计数中生成一个新数组?
  • 我学会了所有这些排序算法,但是因为这个数组只包含 3 个选项(1,2 和 3)我认为这里有一个技巧

标签: arrays algorithm sorting language-agnostic pseudocode


【解决方案1】:

正如 robert 所说,篮式排序(或 bucketsort)在这种情况下是最好的。

我还会添加下一个算法(它实际上与 busket 排序非常相似):

[伪代码是 java 风格的]

创建一个HashMap<Integer, Interger> map 并循环遍历您的数组:

for (Integer i : array) {
    Integer value = map.get(i);
    if (value == null) {
        map.put(i, 1);
    } else {
        map.put(i, value + 1);
    }
 }

【讨论】:

  • 这是原始问题:你有n个桶,每个桶包含一个硬币,硬币的价值可以是5 0r 10或20。你必须在这个限制下对桶进行排序:1。您只能使用这 2 个函数: SwitchBaskets (Basket1, Basket2) – 切换 2 个篮子 GetCoinValue (Basket1) – 返回所选篮子中的硬币值 2. 您不能定义大小为 n 的数组 3. 尽可能少使用 switch 函数跨度>
  • @thechmodmaster 您必须使用此信息更新您的问题。
  • @ArtemStorozhuk 您的代码中的排序区域在哪里?
【解决方案2】:

我会在这里使用递归方法

fun sortNums(smallestIndex,largestIndex,array,currentIndex){
if(currentIndex >= array.size)
   return
if (array[currentIndex] == 1){
You have found the smallest element, now increase the smallestIndex
//You need to put this element to left side of the array at the smallestIndex position.
//You can simply swap(smallestIndex, currentIndex)
// The catch here is you should not swap it if it's already on the left side
//recursive call
sortNums(smallestIndex,largestIndex,array,currentIndex or currentIndex+1)// Now the task of incrementing current Index in recursive call depends on the element at currentIndex. if it's 3, then you might want to let the fate of currentIndex decided by recursive function else simply increment by 1 and move further
} else if (array[currentInde]==3){
// same logic but you need to add it at end
}
}

可以通过 sortNums(smallestIndex=-1,largestIndex=array.size,array,currentIndex=0) 启动递归函数

您可以在此处找到示例代码 Code Link

【讨论】:

    【解决方案3】:
    def DNF(input,length):
        high = length - 1
        p = 0
        i = 0
        while i <= high:
                if input[i] == 0:
                        input[i],input[p]=input[p],input[i]
                        p = p+1
                        i = i+1
                elif input[i] == 2:
                        input[i],input[high]=input[high],input[i]
                        high = high-1
                else:
                        i = i+1
    input = [0,1,2,2,1,0]
    print "input: ", input
    DNF(input,len(input))
    print "output: ", input
    

    【讨论】:

      【解决方案4】:
      //Bubble sort for unsorted array - algorithm
      public void  bubleSort(int arr[], int n) { //n is the length of an array
          int temp;
          for(int i = 0; i <= n-2; i++){
              for(int j = 0; j <= (n-2-i); j++){
                  if(arr[j] > arr[j +1]){
                      temp = arr[j];
                      arr[j] = arr[j +1];
                      arr[j + 1] = temp;
      
                  }
              }
      
          }
      

      【讨论】:

        【解决方案5】:

        这可以很容易地使用-->

        荷兰国旗算法http://www.csse.monash.edu.au/~lloyd/tildeAlgDS/Sort/Flag/

        不使用 1,2,3 而是将其作为 0,1,2

        【讨论】:

          【解决方案6】:

          让我们打破数组中只有两个数字的问题。 [1,2,1,2,2,2,1,1]

          如果: 我们从左右两个指针开始,直到它们相遇。 如果左元素更大,则将左元素与右元素交换。 (升序)

          我们可以再做一次,为三个数字(k-1 次)。在第一个通道中,我们将 1 移动到它们的最终位置,在第 2 通道中,我们移动了 2。

          def start = 0, end = array.size() - 1;
          
          // Pass 1, move lowest order element (1) to their final position 
          while (start < end) {
              // first element from left which is not 1
              for ( ; Array[start] ==  1 && start < end ; start++);
              // first element from right which IS 1
              for ( ; Array[end] != 1 && start < end ; end--);
          
              if (start < end) swap(start, end);
          }     
          
          // In second pass we can do 10,15
          
          // We can extend this using recurion, for sorting domain = k, we need k-1 recurions 
          

          【讨论】:

            【解决方案7】:

            这是基于@ElYusubov 的常规解决方案,但不是将 Bucket(5) 推到开始,将 Bucket(15) 推到结束。使用筛选,使 5 向开头移动,15 向结尾移动。

            每当我们将存储桶从 end 交换到当前位置时,我们都会减少 end,不要增加当前计数器,因为我们需要再次检查元素。

            array = [15,5,10,5,10,10,15,5,15,10,5]
            
                def swapBucket(int a, int b) {
            
                    if (a == b) return; 
                    array[a] = array[a] + array[b]
                    array[b] = array[a] - array[b]
                    array[a] = array[a] - array[b]
            
                }
            
            def getBucketValue(int a) {
                return array[a];
            }
            
            def start = 0, end = array.size() -1, counter = 0;
            // we can probably do away with this start,end but it helps when already sorted.
            
            // start - first bucket from left which is not 5
            while (start < end) {
            
                if (getBucketValue(start) != 5) break;
                start++;
            
            }     
            
            // end - first bucket from right whichis not 15
            while (end > start) {
            
                if (getBucketValue(end) != 15) break;
                end--;
            
            }
            
            // already sorted when end = 1 { 1...size-1 are Buck(15) } or start = end-1      
            
            for (counter = start; counter < end;) {
            
                def value = getBucketValue(counter)
            
                if (value == 5) { swapBucket(start, counter); start++; counter++;}
                else if (value == 15) { swapBucket(end, counter); end--; } // do not inc counter
                else { counter++; }
            
            }
            
            for (key in array) { print " ${key} " }
            

            【讨论】:

              【解决方案8】:

              只是为了好玩,下面是您将如何实现“将值推送到远端”的方法,正如 ElYusubub 建议的那样:

              sort(array) {
                a = 0
                b = array.length
                # a is the first item which isn't a 1 
                while array[a] == 1
                  a++
                # b is the last item which isn't a 3
                while array[b] == 3
                  b--
              
                # go over all the items from the first non-1 to the last non-3
                for (i = a; i <= b; i++)
                  # the while loop is because the swap could result in a 3 or a 1
                  while array[i] != 2
                    if array[i] == 1
                      swap(i, a)
                      while array[a] == 1
                        a++
                    else # array[i] == 3
                      swap(i, b)
                      while array[b] == 3
                        b--
              

              这实际上可能是一个最佳解决方案。我不确定。

              【讨论】:

                【解决方案9】:

                如何对其进行排序的有希望的方法似乎是 counting sort。值得一看 Richard Buckland 的 this lecture,尤其是 15:20 的部分。

                类似于计数排序,但更好的是创建一个表示域的数组,将其所有元素初始化为 0,然后遍历您的数组并计算这些值。一旦知道了域值的这些计数,就可以相应地重写数组的值。这种算法的复杂度是 O(n)。

                这是具有我描述的行为的 C++ 代码。它的复杂度实际上是 O(2n):

                int A[] = {3,2,1,2,3,2,1,3,1,2,3};
                int domain[4] = {0};
                
                // count occurrences of domain values - O(n):  
                int size = sizeof(A) / sizeof(int);
                for (int i = 0; i < size; ++i)
                    domain[A[i]]++;
                
                // rewrite values of the array A accordingly - O(n):    
                for (int k = 0, i = 1; i < 4; ++i)
                    for (int j = 0; j < domain[i]; ++j)
                        A[k++] = i;
                

                请注意,如果域值之间存在很大差异,则将域存储为数组是低效的。在这种情况下,最好使用 map (感谢abhinav 指出)。这是使用 std::map 存储域值的 C++ 代码 - 出现次数对:

                int A[] = {2000,10000,7,10000,10000,2000,10000,7,7,10000};
                std::map<int, int> domain;
                
                // count occurrences of domain values:  
                int size = sizeof(A) / sizeof(int);
                for (int i = 0; i < size; ++i)
                {
                    std::map<int, int>::iterator keyItr = domain.lower_bound(A[i]);
                    if (keyItr != domain.end() && !domain.key_comp()(A[i], keyItr->first))
                        keyItr->second++; // next occurrence 
                    else
                        domain.insert(keyItr, std::pair<int,int>(A[i],1)); // first occurrence
                }
                
                // rewrite values of the array A accordingly:    
                int k = 0;
                for (auto i = domain.begin(); i != domain.end(); ++i)
                    for (int j = 0; j < i->second; ++j)
                        A[k++] = i->first;
                

                (如果有办法在上面的代码中更有效地使用std::map,请告诉我)

                【讨论】:

                • 我认为这是我心中的答案,但无法很好地解释:) 复杂性绝对应该是 O(n)。换句话说,应该只对初始数组的所有元素进行一次迭代。
                • 计数排序是最好的,但如果我们有高动态范围,你的方法就不能很好地扩展。我的意思是如果我有一个数组 A[] = {1, 10, 1000, 1, 200}。在这种情况下,您需要大小至少为 max(A) 的域,这意味着为仅包含 5 个元素的数组分配 1000*elemSize (仅考虑正元素)。相同算法的更好方法是地图(我不是说 hash 地图;只是基于树的地图),您可以简单地通过 count++=0;asize = sizeof(A)/sizeof( A[0]); while(count++
                • @abhinav:是的,如果该域包含那种值,使用 map 会更好。但是,即使您将数组替换为地图,该方法仍然几乎相同(类比)。
                • 有人可以评论如何在 cmets 中进行格式化吗?我可以在帖子或新回复中执行此操作,但不能在评论中执行,如上所示。
                【解决方案10】:

                我想我理解了这个问题 - 您只能使用 O(1) 空间,并且只能通过交换单元格来更改数组。 (因此您可以对数组使用 2 个操作 - 交换和获取)

                我的解决方案:

                使用 2 个索引指针 - 一个用于最后 1 的位置,一个用于最后 2 的位置。

                在阶段 i,您假设数组已经从 1 排序到 i-1, 比你检查第 i 个单元格: 如果 A[i] == 3 你什么都不做。 如果 A[i] == 2 您将其与最后 2 个索引之后的单元格交换。 如果 A[i] == 1 您将其与最后 2 个索引之后的单元格交换,然后交换单元格 在最后 2 个索引之后(包含 1)与最后 1 个索引之后的单元格。

                这是主要思想,您需要注意一些小细节。 总体 O(n) 复杂度。

                【讨论】:

                  【解决方案11】:

                  这是计算机科学中的标准问题:Dutch national flag problem 请参阅链接。

                  【讨论】:

                    【解决方案12】:

                    问题描述:你有n个桶,每个桶包含一个硬币,硬币的价值可以是5或10或20。你必须在这个限制下对桶进行排序:1。你只能使用这2个功能: SwitchBaskets (Basket1, Basket2) – 切换 2 个篮子 GetCoinValue (Basket1) – 返回所选篮子中的硬币值 2. 不能定义大小为 n 的数组 3. 尽量少使用 switch 函数。

                    我的简单伪代码解决方案,可以用任何语言实现,复杂度为 O(n)。

                    我会从篮子里捡硬币 1)如果是 5 - 将其推为第一个, 2)如果是20-推到最后, 3)如果 10 - 将其留在原处。 4) 并查看排队的下一个桶。

                    编辑: 如果您不能将元素推到第一个或最后一个位置,那么Merge sort 将是盗版实现的理想选择。以下是它的工作原理:

                    合并排序利用了将已排序列表合并到新排序列表的便利性。它首先比较每两个元素(即 1 与 2,然后 3 与 4...),如果第一个应该在第二个之后,则交换它们。然后它将结果列表中的每一个 2 合并为 4 列表,然后合并这些 4 列表,依此类推;直到最后两个列表合并到最终的排序列表中。在这里描述的算法中,这是第一个可以很好地扩展到非常大的列表的算法,因为它的最坏情况运行时间是 O(n log n)。合并排序最近在实际实现中越来越受欢迎,被用于编程语言中的标准排序例程

                    【讨论】:

                    • 你不能推到最后或第一个 - 你只能在两个桶之间切换。
                    • ElYusubov 非常感谢您的帮助,非常感谢!
                    【解决方案13】:

                    此代码适用于 c#:

                    但是,您必须考虑以非语言/框架特定的方式实现它的算法。正如所建议的Bucket set 可能是最有效的选择。如果您提供有关问题的详细信息,我会尝试寻找最佳解决方案。 祝你好运...

                    这是 C# .NET 中的代码示例

                    int[] intArray = new int[9] {3,2,1,2,3,2,1,3,1 };
                    Array.Sort(intArray);
                    // write array
                    foreach (int i in intArray) Console.Write("{0}, ", i.ToString());  
                    

                    【讨论】:

                    • 我会更具体:你有n个桶,每个桶包含一个硬币,硬币的价值可以是5 0r 10或20。你必须在这个限制下对桶进行排序:1。您只能使用这 2 个函数: SwitchBaskets (Basket1, Basket2) – 切换 2 个篮子 GetCoinValue (Basket1) – 返回所选篮子中的硬币值 2. 您不能定义大小为 n 的数组 3. 尽可能少使用 switch 函数。
                    • 在这里,我会怎么做。我将选择硬币 1)如果是 5 - 将其推为第一个,2)如果是 20- 将其推为最后一个,3)如果是 10 - 将其留在原处。 4) 并查看下一个存储桶。
                    【解决方案14】:

                    计算每个数字,然后根据它们的计数创建新数组...时间复杂度 O(n)

                     int counts[3] = {0,0,0};
                     for(int a in A)
                      counts[a-1]++;
                     for(int i = 0; i < counts[0]; i++)
                      A[i] = 1;
                     for(int i = counts[0]; i < counts[0] + counts[1]; i++)
                      A[i] = 2;
                     for(int i = counts[0] + counts[1]; i < counts[0] + counts[1] + counts[2]; i++)
                      A[i] = 3;
                    

                    【讨论】:

                    • 我无法定义另一个数组。我可以切换单元格(需要切换的越少越好
                    • 所以使用三个变量代替数组计数
                    • 实际上,这是 O(n+k),其中 n = 输入的大小,k = 可能值的数量。由于原始海报给出的示例中的 k
                    【解决方案15】:

                    例如,您是否尝试过查看 wiki? - http://en.wikipedia.org/wiki/Sorting_algorithm

                    【讨论】:

                    • 我学习了所有这些排序算法,但是因为这个数组只包含 3 个选项(1,2 和 3)我认为这里有一个技巧
                    • 不,每个排序算法都会处理。但是,如果您知道,只有 3 个选项(1,2,3),您可以线性遍历数组并计算数字 1。如果找到数字 1,则将其放在数组的开头,如果找到数字 3你把它放在数组的末尾,数字 2 应该放在位置 - 数字的数量 1(你记得它)+ 1。
                    【解决方案16】:

                    我认为这个问题打算让您使用bucket sort。在有少量值的情况下,桶排序可以比更常用的快速排序或合并排序快得多。

                    【讨论】:

                      猜你喜欢
                      • 2017-09-13
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2016-04-03
                      • 2020-02-18
                      相关资源
                      最近更新 更多