【问题标题】:Sort array elements based on their frequency根据频率对数组元素进行排序
【发布时间】:2019-06-10 09:51:05
【问题描述】:

我需要根据频率对元素数组进行排序,例如:

Input array: [1, 6, 6, 6, 6, 4, 3, 5, 5, 5, 2, 2]
Expected output: [1, 3, 4, 2, 2, 5, 5, 5, 6, 6, 6, 6]

我尝试了以下代码:

var set: NSCountedSet = [1, 6, 6, 6, 6, 4, 3, 5, 5, 5, 2, 2]

var dictionary = [Int: Int]()
set.forEach { (item) in
    dictionary[item as! Int] = set.count(for: item)
}
dictionary.keys.sorted()
print(dictionary)

说明:由于1、3、4只出现一次,所以显示在开头,2出现2次,5出现3次,6出现4次。并且 [1, 3, 4] 在其中排序。

预期结果:时间复杂度应为 O(n)

【问题讨论】:

  • 我认为在最坏的情况下你不能达到 O(n)。如果您的要求是对唯一项目进行排序,那已经为您提供 O(n*log n)。
  • 如果频率较小,计数排序是满足o(n)的一种选择

标签: ios arrays swift sorting time-complexity


【解决方案1】:

您可以在O(nlogn) 时间内获得结果,方法是首先创建一个包含每个元素出现次数的Dictionary (O(n)),然后在Array 上调用sorted (Swift uses Introsort,这是O(nlogn)) 并使用之前创建的Dictionary 中的值进行排序。数组的元素必须是 Comparable 才能进行排序,Hashable 才能将它们存储在提供 O(1) 元素查找的 Dictionary 中。

extension Array where Element: Comparable & Hashable {
    func sortByNumberOfOccurences() -> [Element] {
        let occurencesDict = self.reduce(into: [Element:Int](), { currentResult, element in
            currentResult[element, default: 0] += 1
        })
        return self.sorted(by: { current, next in occurencesDict[current]! < occurencesDict[next]!})
    }
}

[1, 6, 6, 6, 6, 4, 3, 5, 5, 5, 2, 2].sortByNumberOfOccurences() // [1, 4, 3, 2, 2, 5, 5, 5, 6, 6, 6, 6]

上述解决方案保留了出现相同次数的元素的顺序。如果您真的想根据它们的比较值对这些元素进行排序(这是您的示例输出所做的),您可以修改 sorted 中的闭包,如下所示:

return self.sorted(by: {occurencesDict[$0]! <= occurencesDict[$1]! && $0 < $1})

甚至更短,comparing tuples for sorting

return self.sorted(by: {(occurencesDict[$0]!,$0) < (occurencesDict[$1]!,$1)})

生成您提供的示例输出,[1, 3, 4, 2, 2, 5, 5, 5, 6, 6, 6, 6]

【讨论】:

  • 我正要发这个let array = [1, 6, 6, 6, 6, 4, 3, 5, 5, 5, 2, 2] let freq = array.reduce(into: [:]) { $0[$1, default: 0] += 1 } let sortedArray = array.sorted() { freq[$0]! &lt; freq[$1]! } sortedArray
  • 它不能在 O(n) 中完成。你需要先对这个数组进行排序,然后使用上面提到的方法。
  • @RakeshaShastri 不,您不需要先对数组进行排序,这是在 sorted 调用中完成的。但是,您是对的,我的算法的总体时间复杂度是 O(nlogn),更新了我的答案以反映这一点
  • sorted(by:) 不是 O(n)。
  • @LeoDabus 我也在考虑为第二个解决方案这样做,但使用&lt; 而不是&lt;=,这不起作用,所以想出了一个更长的解决方案,但更新了如果您不介意,我会根据您的想法回答:)
【解决方案2】:

你可以试试

let dd = [1, 6, 6, 6, 6, 4, 3, 5, 5, 5, 2, 2]
let res = dd.sorted { f, s in
    dd.filter { $0 == f }.count <   dd.filter { $0 == s }.count 
} 
print(res) // [1, 4, 3, 2, 2, 5, 5, 5, 6, 6, 6, 6]

【讨论】:

  • 我想知道这是否是 O(n)。 – 另请注意,排序不是稳定的,不能保证相同频率出现的数字的顺序会被保留。
  • @MartinR 我不认为这样的问题可以在 O(n) 中解决,当然可能还有其他不太广泛的答案
  • 感谢@Sh_Khan 的回答,但预期的输出应该是[1, 3, 4, 2, 2, 5, 5, 5, 6, 6, 6, 6]
【解决方案3】:

没有办法以 O(n) 时间复杂度进行排序。在Wikipedia. 上查看流行算法的最坏情况复杂度

最好的最坏情况时间复杂度是 O(nlogn)。以下是我们如何以 O(nlogn) 时间复杂度解决它:


    let array = [1, 6, 6, 6, 6, 4, 3, 5, 5, 5, 2, 2]

    extension Array where Element: Comparable & Hashable {
        func countableSorted() -> [Element] {
            var counts = [Element: Int]()
            // O(n)
            for element in self {
                counts[element] = (counts[element] ?? 0) + 1
            }

            // I think that standart method uses O(nlogn) time complexity.
            // O(nlogn) + O(n) approximately equal to O(nlogn).
            let sorted = counts.sorted { item1, item2 -> Bool in
                if item2.value > item1.value {
                    return true
                }

                if item2.value == item1.value {
                    return item2.key > item1.key
                }

                return false
            }

            var result = [Element]()
            // O(n)
            for item in sorted {
                let items = Array(repeating: item.key, count: item.value)
                result.append(contentsOf: items)
            }

            // Total time complexity for worst case scenario is O(nlogn)

            return result
        }
    }

    print(array.countableSorted())

    // Output: [1, 3, 4, 2, 2, 5, 5, 5, 6, 6, 6, 6]

【讨论】:

    【解决方案4】:

    你可以试试下面的代码,效果很好。

    var inputArray = [1, 6, 6, 6, 6, 4, 3, 5, 5, 5, 2, 2]
    inputArray.sort()
    let freq = inputArray.sorted { f, s in
        inputArray.filter { $0 == f}.count < inputArray.filter { $0 == s}.count
    }
    print(freq)
    

    不确定时间复杂度。

    【讨论】:

      【解决方案5】:

      我想在 O(n) 中添加一个解决方案

      排序需要 O(nLogn) 但这个问题也可以在不使用排序的情况下通过 Java 中的 HashMap 来解决,因为它包含根据键排序的对。

      import java.util.*; 
      
      class Simple 
      { 
          public static void main(String[] arg) 
          {  int inputArray[] = {1, 6, 6, 6, 6, 4, 3, 5, 5, 5, 2, 2};
              Map<Integer,Integer> map = new HashMap<Integer,Integer>(); 
              Map<Integer,List<Integer>> map2 = new HashMap<Integer,List<Integer>>();
             for(int i: inputArray)
            {
                        if(map.get(i) == null){
                       map.put(i, 1) ;
                        }
                        else{
                        int a = map.get(i);
                        map.put(i,a+1);
                       }
            }
      
              // using for-each loop for iteration over Map.entrySet() 
              for (Map.Entry<Integer,Integer> entry : map.entrySet()) {
                  if(map2.get(entry.getValue()) == null){
                      map2.put(entry.getValue(), new ArrayList<Integer>()) ;
                  }
                  map2.get(entry.getValue()).add(entry.getKey());
              }
      
              for(Map.Entry<Integer,List<Integer>> entry : map2.entrySet()){
                  for(int j=0; j<entry.getValue().size(); j++){
                      for(int i=0; i<entry.getKey(); i++){
                      System.out.print(entry.getValue().get(j) + " ");
                  }
                  }
      
              }    
          }         
      
      }
      
      1. 在第一个 for 循环中,我正在遍历 map1(HashMap) 中的数组保存对 (value,Occurrence)。这将需要 O(n),因为 HashMap put 操作(插入)需要 O(1)。
      2. 在第二个 for 循环中,我正在迭代 map1 并在 map2(HashMap2) 中插入一对 (出现次数,给定数组中出现该次数的数字列表)
      3. 现在在最后一个 for 循环中,我正在遍历 map2 并逐个打印所有列表,这意味着我正在打印给定数组的每个元素一次,即我正在遍历每个键的列表并打印列表的每个元素 key 次。所以这也需要 O(n)。

      more about HashMap 时间复杂度:O(n)

      上述代码的 Swift 版本

      extension Array where Element: Comparable & Hashable {
      func sortByNumberOfOccurences() -> [Element] {
          let occurencesDict = self.reduce(into: [Element:Int](), { currentResult, element in
              currentResult[element, default: 0] += 1
          })
          let dict = occurencesDict.sorted(by: {$0.0 < $1.0})
          var dictioanary = [Int:Array]()
          for (element,occurence) in dict {
              if dictioanary[occurence] == nil
              {
                  dictioanary[occurence] = Array()
              }
              dictioanary[occurence]?.append(element)
          }
      
      
          var resultArray = Array()
          let finalDict = dictioanary.sorted(by: {$0.0  < $1.0})
          for (frequency,allValuesOccuringWithThisFrequncy) in finalDict {
             for i in allValuesOccuringWithThisFrequncy
             {
              var j = 0
              while(j < frequency)
              {
                  resultArray.append(i)
                  j = j + 1
              }
             }
          }
          print(resultArray)
          return resultArray
      }
      

      }

      Swift O(nLogn) 中的时间复杂度

      【讨论】:

      • 你能在 swift 中尝试同样的方法吗?
      • @Bappaditya 嗨,我尝试快速编写代码。我正在添加代码。但是由于 Swift 中的 Dictionary 并没有保持我们需要在其中应用 sort 方法的顺序,因此复杂度为 O(nLogn)。但是在 java HashMap 中,基于键的排序方式插入对。我希望我们在 Swift 中也有类似类型的集合。悲伤:(我认为我们需要在 Swift 中编写自己的 HashMap :P
      【解决方案6】:
      var inputArray = [1, 6, 6, 6, 6, 4, 3, 5, 5, 5, 2, 2]
      var map:[Int: Int] = [:]
      for element in inputArray {
          let count = map[element]
          if count == nil {
              map[element] = 1
          } else {
              map[element] = count! + 1
          }
      }
      var keysArray = map.keys
      let sortedKeys = keysArray.sorted { (number1, number2) -> Bool in
          if map[number1]! == map[number2]! {
              return number1 < number2
          } else {
              return map[number1]! < map[number2]!
          }
      }
      var finalArray: [Int] = []
      for element in sortedKeys {
          for _ in 1...map[element]! {
              finalArray.append(element)
          }
      }
      print(finalArray)
      

      时间复杂度:O(nlogn)

      【讨论】:

      • @LeoDabus 谢谢。已编辑。此外,还应进行可选检查。但这解决了我们的基本问题。
      【解决方案7】:

      试试这个解决方案。它对我来说就像一个魅力:)

      func numberOfOccurences(in array: [Int], of element: Int) -> Int {
          let object = NSCountedSet(array: array)
          return object.count(for: element)
      }
      
      var inputArray = [1, 6, 6, 6, 6, 4, 3, 5, 5, 5, 2, 2]
      
      var uniqueElements = Array(Set(inputArray))
      
      var otherArray: [Int] = []
      
      var duplicateElements = uniqueElements.filter { (element) -> Bool in
          return (inputArray.contains(element) && numberOfOccurences(in: inputArray, of: element) > 1)
      }
      
      uniqueElements = uniqueElements.filter({ !duplicateElements.contains($0) }).sorted()
      
      for item in duplicateElements {
          let occurences = numberOfOccurences(in: inputArray, of: item)
          for _ in 0...occurences - 1 {
              otherArray.append(item)
          }
      }
      
      otherArray = otherArray.sorted()
      
      duplicateElements.removeAll()
      
      let mergedArray = uniqueElements + otherArray
      
      print(mergedArray)
      
      

      【讨论】:

      • 如果你声明了x...y - 1的循环条件,那么x..&lt;y更有意义。
      • 对于输入 [1, 1, 1, 2, 2, 3],您的代码会生成结果 [3, 1, 1, 1, 2, 2],这在我看来是错误的。
      【解决方案8】:

      我认为这种排序可以在 O(n) 中实现,如下所示:

      let input = [1, 6, 6, 6, 6, 4, 3, 5, 5, 5, 2, 2]
      
      // build the frequency dictionary (easy task)
      let frequencies = input.reduce(into: [:]) { $0[$1] = ($0[$1] ?? 0) + 1 }
      
      // allocate a 2-D array of ints, each item in this array will hold the numbers that
      // appear I times in the input array
      let frequencyTable: [[Int]] = frequencies.reduce(into: Array(repeating: [Int](), count: input.count)) {
          // substracting one as arrays are zero indexed
          // we can't get of of bounds here since the maximum frequency is the 
          // number of elements in the input array
          // Also replicating the numbers by their frequency, to have
          // the same contents as in the input array
          $0[$1.value - 1] += Array(repeating: $1.key, count: $1.value)
      }
      
      // lastly, simply flatten the 2-D array to get the output we need
      let output = frequencyTable.flatMap { $0 }
      
      print(output)
      

      示例结果:

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

      请注意,频率相同的数字的顺序可能会根据字典哈希函数的工作方式而有所不同。

      我们还牺牲空间(分配的二维数组)来换取时间。

      frequencyTable 的内容看起来与此类似(同样,1、4、3 的顺序可能不同):

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

      【讨论】:

      • 又称计数排序