简介
什么是TopN问题?
举个例子:
怎么在几十亿的数字中找到前50个数字(升序或者降序)。
诸如此类问题都被成为topN问题。
那么这种题目或者这种问题到底解决的难点和关键点是什么?
我们来分析,这种题目绝对不能把它当作简单的排序来做,题眼在于超大的数量级,这就是告诉你在内存中不能完全放进这些数字。
我们来具体一点,这几十亿的数子应当是存放着hdfs中,那也就是以稳健的形式落盘存储。并且一次内存装不下。
相关解法:
分治思想:
我们首先找到一个基准然后读取文件,如果大于基准就放到文件1A文件,小于就是1B文件,这样就是大文件变成多个小文件,假如说我们要最大的50个就只需要再去处理1A文件了。
然后重复上面的做法,直到小文件足够小,可以放到内存完全排序计算。
分布式计算(极致的空间换时间):
在解决这种问题的实际公司中肯定有数量比较客观的分布式计算集群,那么有一种我觉得只限于解决这个问题的方法就是数据分成若干个分段,然后交给集群先得到每个分段的最大值,然后集合起来再进行排序。
这上面两种方法其实掌握了也是很好的,相对于其他算法这也是比较优秀的了,但是还是要说
真正的最优解。
堆排序
什么是堆排序:
为什么堆排序是最优的:
- 时间复杂度,堆排序的时间复杂度小于n^2
- 空间复杂度,这里使用的内存是可以自己规定的,同时即使很小也可以有比较好的性能
- 没有占用更多的集群资源,不需要做文件分桶和多集群计算,当建堆完成之后不太需要过多的计算资源。
scala版本
这里写的是一个模拟应用的例子,读取待排序的文件到rdd然后取前8个(模拟小内存)然后建堆,然后进行后面的比较、堆调整和sort部分。最终将前8个写入文件。
package TopN
import java.util
import java.util.Date
import org.apache.spark.{SparkConf, SparkContext}
import scala.util.control.Breaks
/**
* @Author: Braylon
* @Date: 2020/3/1 15:36
* @Description: 大根堆求最小top N
*/
object SparkTopN {
val INPUT_SIZE = 8;
var arr:Array[Int] = new Array[Int](INPUT_SIZE)
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local").setAppName("Top N")
val sc = new SparkContext(conf)
val data = sc.textFile("D:\\idea\\projects\\scalaDemo\\src\\resources\\nums.txt").flatMap(_.split(","))
/*
* 计算运行时间
* */
var start_ = new Date().getTime()
for (i <- 0 until INPUT_SIZE){
arr(i) = Integer.valueOf(data.collect()(i))
}
buildHeap(arr)
val newInput = data.zipWithIndex().filter(_._2 > INPUT_SIZE)
for (x <- newInput){
compareWithHeap(arr, Integer.valueOf(x._1))
}
sort(arr)
var end_ = new Date().getTime()
println(end_ - start_, "ms")
val finalRes = sc.parallelize(arr)
finalRes.saveAsTextFile("D:\\idea\\projects\\scalaDemo\\src\\resources\\res.txt")
sc.stop()
}
def buildHeap(arr:Array[Int]): Unit ={
var pos:Int = (arr.length-1)/2
var i:Int = arr.length
for (i <- 0 to pos){
var pos1:Int = pos - i
adjustHeap(arr, pos1, arr.length)
}
}
def adjustHeap(arr:Array[Int], pos0:Int, scale:Int): Unit ={
var pos = pos0
var tmp:Int = arr(pos)
//左孩子节点
var left:Int = pos*2 + 1
val loop = new Breaks
loop.breakable{
while (left < scale){
if (left + 1 < scale && arr(left)<arr(left+1)){
left = left + 1
}
if (arr(left) > arr(pos)){
arr(pos) = arr(left)
pos = left
left = 2*pos + 1
} else{
loop.break
}
arr(pos) = tmp
}
}
}
def sort(arr:Array[Int]): Unit ={
var tmp:Int = -1
for (index <- arr.indices){
tmp = arr(0)
arr(0) = arr(arr.length - 1 - index)
arr(arr.length - 1- index) = tmp
adjustHeap(arr, 0, arr.length - 1 - index)
}
}
def compareWithHeap(arr:Array[Int], item:Int): Unit ={
if(item >= arr(0)){
}else{
arr(0) = item
adjustHeap(arr, 0, arr.length-1)
}
}
/**
* 生成随机数
* @param count 个数
* @return 随机数数组
*/
def randomTestData(count:Int): Array[Int] ={
val list1:Array[Int] = new Array[Int](count)
var i = 0
while(i < count){
val num = Integer.valueOf(scala.util.Random.nextInt(100000000))
list1(i) = num
i = i+1
}
list1
}
}
大家共勉~~