原创:搜索排序算法之自定义性能优良的PriorityQueue(与Python的heap比较)
前几天写了一篇关于"史上对BM25模型最全面最深刻解读以及lucene排序深入解读"的博客,lucene最后排序用到的思想是"从海量数据中寻找topK"的时间空间最优算法(这是一个博士的学术论文)。在特定的场合,比如solr自带的搜索智能提示公能,当构建完三叉树,前缀匹配查找出所有的节点之后,也要用这种思想进行排序。根据这个思想构造出一个优先级队列,具有容量限制(K),精确的时间复杂度为KlgK+(n-k)lgK,最坏的时间复杂度:(n-k)*lgk +lg(k-1)!。远远优于目前任何的排序算法,在学术论文里已经进行了理论论证。今天根据这个思想,尝试着写了这个数据结构,经过修改,已经接近完美。这个数据结构的适用场景就是从海量数据中寻找出topK的数据来,可以解决lucene排序,解决搜索推荐的冷启动问题(从用户的搜索日志中寻找出topK,推荐出来)。凡是有容量限制的这类问题,都可以使用它。而TreeSet或者TreeMap的排序的时间复杂度是O(lgn),并且底层基于可排序的二叉树(通过红黑树获取平衡),然后中序遍历得到最后结果。在IK分词的消除歧义分词中用到了TreeSet。这两个数据结构常常用于Key-value数据形式并且容量不是很大或者没有容量限制的场景。其他的基础排序算法比如快排,堆排,mergeSort还有近几年新的排序TimSort,常常用于Array的排序。从底层深刻理解这些基础算法很重要!机器学习排序在搜索中的应用,最后仍然要用今天要写的PriorityQueue。JDK中有自带的PriorityQueue,但是没有容量限制,性能比较差。上传本人写的代码:
package com.txq.test;
/**
* 优先级队列,从海量数据中寻找topK的时间空间最优算法,时间复杂度为n * lgK,空间为K,其中K << n.
* @author XueQiang Tong
* @since JDK1.8
* @param <T>
*/
public abstract class PriorityQueue<T> {
protected T heap[];//堆
protected int size;//容量
protected int heapSize;//最大容量 public PriorityQueue(int capacity){
if (capacity >= (1 << 31) - 1) {
throw new IllegalArgumentException("max size must be <= (1 << 31) -1;got:" + capacity);
}
this.heapSize = capacity;
this.size = 0;
Object o[] = new Object[this.heapSize];
heap = (T[]) o;
} public PriorityQueue(){
this(5);
} protected abstract boolean lessThan(T t, T data);
/**
* 向队列中添加元素,如果没有达到容量限制,直接添加并且构建小根堆,如果超出了容量,用大于堆顶的元素替换堆顶,然后调整堆
* @param data
*/
protected synchronized final void insertWithOverFlow(T data){
if(data == null) return;
if(this.size < this.heapSize){
add(data);
} else{
if(this.size > 0 && lessThan(heap[0],data)){
heap[0] = data;
minify(0);
}
}
}
/**
* 按照降序依次取出topK元素,第一次取时,先构建大根堆,以后直接取堆顶元素,然后重新调整大根堆
* @return
*/
protected synchronized final T pop(){
if(this.size == 0) return null;
if(this.size == this.heapSize){
for(int i = this.heapSize / 2 - 1;i >= 0;i--){
maxnify(i);
}
}
T result = heap[0];
heap[0] = heap[this.size - 1];
heap[this.size - 1] = null;
this.size --;
maxnify(0);
return result;
} public final int size(){
return this.size;
} protected final T top(){
return this.heap[0];
}
/**
* 调整大根堆
* @param i
*/
private void maxnify(int i) {
int left = 2 * i + 1;
int right = 2 * i + 2;
int max; if(left < this.size && lessThan(heap[i],heap[left])) max = left;
else max = i;
if(right < this.size && lessThan(heap[max],heap[right])) max = right; if (max == i || max >= this.size) return;
swap(heap,i,max);
maxnify(max);
} private void swap(T[] heap, int i, int j) {
T tmp;
tmp = heap[i];
heap[i] = heap[j];
heap[j] = tmp;
}
/**
* 调整小根堆
* @param i
*/
private void minify(int i) {
int left = 2 * i + 1;
int right = 2 * i + 2;
int min; if(left < this.size && lessThan(heap[left],heap[i])) min = left;
else min = i;
if(right < this.size && lessThan(heap[right],heap[min])) min = right; if (min == i || min >= this.size) return;
swap(heap,i,min);
minify(min);
}
/**
* 添加元素并且构建小根堆
* @param data
*/
private void add(T data) {
this.heap[this.size++] = data;
for(int i = this.size / 2 - 1;i >= 0;i--){
minify(i);
}
} public synchronized final void clear() {
for (int i = 0; i <= this.size; ++i) {
this.heap[i] = null;
}
this.size = 0;
}
}
package com.txq.test; public class MyPriorityQueue extends PriorityQueue<Integer> { @Override
protected boolean lessThan(Integer node, Integer data) {
return node < data;
} public MyPriorityQueue(int capacity){
super(capacity);
}
}
测试类:
package chinese.utility.utils;
import org.junit.Test;
public class PriorityQueueTest {
PriorityQueue<Integer> q;
@Test
public void test() {
q = new MyPriorityQueue(3);
q.insertWithOverFlow(99);
q.insertWithOverFlow(78);
q.insertWithOverFlow(109);
q.insertWithOverFlow(123);
q.insertWithOverFlow(23);
q.insertWithOverFlow(45);
q.insertWithOverFlow(56);
Integer element = (Integer) q.pop();
while(element != null){
System.out.println(element);
element = (Integer) q.pop();
}
}
}
运行结果:
123
109
99
另外一个场景,比如现在有一个矩阵,没行数据都是降序排列的,维度有很多,要求找出其中matrix.length个最大值,用上面的算法就不行了,时间复杂度太高了。因为每行数据都排序好了,可以采取以下策略:
package com.txq.test; import java.util.ArrayList;
import java.util.List; public class PriotiryQueueTest { public static void main(String[] args) {
//从以下矩阵中找出matrix.length个最大值,时间复杂度为k*lgk(k << n)
int matrix[][] = {{100,89,78,73,69,58,40},{98,91,82,80,39,28,16},{88,80,79,76,68,60,59},
{120,110,98,76,68,60,59},{110,100,99,67,68,60,59},{87,79,74,67,68,60,59}};
final int heapSize = 6;
NumInfo heap[] = new NumInfo[heapSize];
int size = 0;
List<Integer> result = new ArrayList<Integer>(heapSize);
NumInfo ni;
//1.初始化
for(int i = 0;i < heapSize;i++){
ni = new NumInfo(matrix[i][0],i,0);
heap[size++] = ni;
}
//2.构建大根堆
for(int j = heap.length / 2 - 1;j >= 0;j--){
maxnify(heap,j,size);
}
//3.取出堆顶元素
result.add(heap[0].data);
heap[0].label ++;
//4.开始迭代
for(int i = 1;i < heapSize;i++){
int index = heap[0].index;
int label = heap[0].label;
System.out.println("index:"+index+" label:"+label);
ni = new NumInfo(matrix[index][label],index,label);
heap[0] = ni;
maxnify(heap,0,size);
result.add(heap[0].data);
heap[0].label ++;
}
for(int i : result){
System.out.println(i);
}
} /**
* 调整大根堆
* @param i
*/
private static void maxnify(NumInfo heap[],int i,int size) {
int left = 2 * i + 1;
int right = 2 * i + 2;
int max; if(left < size && lessThan(heap[i],heap[left])) max = left;
else max = i;
if(right < size && lessThan(heap[max],heap[right])) max = right; if (max == i || max >= size) return;
swap(heap,i,max);
maxnify(heap,max,size);
} private static void swap(NumInfo[] heap, int i, int max) {
NumInfo tmp;
tmp = heap[i];
heap[i] = heap[max];
heap[max] = tmp;
} private static boolean lessThan(NumInfo numInfo, NumInfo numInfo2) {
return numInfo.data < numInfo2.data;
} }
class NumInfo {
public int data;
public int index;
public int label; public NumInfo(int data,int index,int label){
this.data = data;
this.index = index;
this.label = label;
}
}
另外,Python中也有类似于优先级队列的数据结构,JDK中有自带的优先级队列,都没有容量限制。Python更加倾向于函数式编程。Python的实现是靠堆操作函数的模块,叫heapq。今天试着使用一下:
from heapq import *;
from random import shuffle;
data = [x for x in range(10)];
shuffle(data);
heap = [];
for n in data:
heappush(heap,n); print(heap);
print(type(heappop(heap)))
print(nlargest(5,heap))#输出前5个最大值
print(nsmallest(5,heap))#输出前5个最小值
[0, 1, 6, 3, 2, 7, 9, 5, 4, 8]
<class 'int'>
[9, 8, 7, 6, 5]
[1, 2, 3, 4, 5]
可以看出,Python比Java更加灵活!从海量数据中寻找出topK问题的最优解是前面写的优先级队列解决方案,Python仍然可以完成这个功能,现在来模拟这个场景,对Python中 的heap增加容量限制: #从海量数据中找出top4
from heapq import *; data = [2,2,6,7,9,12,34,0,76,-12,45,79,102];#模拟海量数据
s = set();
#首先从海量数据中构造出容量为4的set,然后加载到heap中
for num in data:
s.add(data.pop(0));
if s.__len__() == 4:
break; heap = [];
for n in s:
heappush(heap,n); print(heap); for num in data:
if num > heap[0]:
heapreplace(heap,num);#对剩余的海量数据继续迭代,如果比堆顶元素大的话,替换之并且调整小根堆 print(nlargest(4,heap))#输出前4个最大值,最后输出的时候执行堆排序!
[2, 7, 6, 9]
[102, 79, 76, 45]
原创:搜索排序算法之自定义性能优良的PriorityQueue(与Python的heap比较)的更多相关文章
- 十大经典排序算法最强总结(含Java、Python码实现)
引言 所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作.排序算法,就是如何使得记录按照要求排列的方法.排序算法在很多领域得到相当地重视,尤其是在大量数据的处理方面 ...
- Python实现各种排序算法的代码示例总结
Python实现各种排序算法的代码示例总结 作者:Donald Knuth 字体:[增加 减小] 类型:转载 时间:2015-12-11我要评论 这篇文章主要介绍了Python实现各种排序算法的代码示 ...
- STL中sort排序算法第三个参数_Compare的实现本质
关于C++ STL vector 中的sort排序算法有三种自定义实现,它们本质上都是返回bool类型,提供给sort函数作为第三个参数. 重载运算符 全局的比较函数 函数对象 我认为从实现方式看,重 ...
- 用python实现各种排序算法
最简单的排序有三种:插入排序,选择排序和冒泡排序.它们的平均时间复杂度均为O(n^2),在这里对原理就不加赘述了. 贴出源代码: 插入排序: def insertion_sort(sort_list) ...
- 《算法4》2.1 - 选择排序算法(Selection Sort), Python实现
选择排序算法(Selection Sort)是排序算法的一种初级算法.虽然比较简单,但是基础,理解了有助于后面学习更高深算法,勿以勿小而不为. 排序算法的语言描述: 给定一组物体,根据他们的某种可量化 ...
- 常见排序算法总结:插入排序,希尔排序,冒泡排序,快速排序,简单选择排序以及java实现
今天来总结一下常用的内部排序算法.内部排序算法们需要掌握的知识点大概有:算法的原理,算法的编码实现,算法的时空复杂度的计算和记忆,何时出现最差时间复杂度,以及是否稳定,何时不稳定. 首先来总结下常用内 ...
- Python实现一些常用排序算法
一些常用的排序 #系统内置排序算法#list.sort()#heapq模块 def sys_heap_sort(list): import heapq heap = [] for i in range ...
- 十大基础排序算法[java源码+动静双图解析+性能分析]
一.概述 作为一个合格的程序员,算法是必备技能,特此总结十大基础排序算法.java版源码实现,强烈推荐<算法第四版>非常适合入手,所有算法网上可以找到源码下载. PS:本文讲解算法分三步: ...
- 常用排序算法的python实现和性能分析
常用排序算法的python实现和性能分析 一年一度的换工作高峰又到了,HR大概每天都塞几份简历过来,基本上一天安排两个面试的话,当天就只能加班干活了.趁着面试别人的机会,自己也把一些基础算法和一些面试 ...
随机推荐
- 记一次线上问题排查:C#可选参数的坑
线上报了大量异常,错误信息为:找不到XX方法实现 代码调用关系是: 查看代码历史记录,发现最近上线前对 GetUserDottedLineSuperiors 方法做过修改,增加了一个可选参数. 跟相关 ...
- FreeRTOS软件定时器
API函数 //创建 TimerHandle_t xTimerCreate( const char * const pcTimerName, const TickType_t xTimerPeriod ...
- js 高阶函数之柯里化
博客地址:https://ainyi.com/74 定义 在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且 ...
- 高阶函数概念以及map/filter/reduce
什么样的函数叫高阶函数:map(func, *iterables) --> map object 条件:1.函数接受函数作为参数 2.函数的返回值中包含函数 num_l = [1,2,3,4,5 ...
- 【Docker】docker安装mysql
一.下载镜像并运行容器 docker run -p 3306:3306 --name mymysql -v $PWD/conf:/etc/mysql/conf.d -v $PWD/logs:/logs ...
- 七、Linux_端口、进程
Linux_端口.进程 1.查看所有端口 netstat -nlutp 2.停掉使用端口的进程,根据进程pid kill 1818 kill -9 1818 # 强制杀掉进程 3.根据进程名杀死进程: ...
- Django-模型层(单表操作)
目录 1.ORM简介 2.单表操作 2.1创建表 2.2添加表纪录 2.3查询表纪录 2.4删除表纪录 2.5修改表纪录 1.ORM简介 MVC或者MVC框架中包括一个重要的部分,就是ORM,它实现了 ...
- thymeleaf教程-springboot项目中实现thymeleaf自定义标签
转载: http://www.9191boke.com/466119140.html 91博客网 开始: 在使用thymeleaf的过程中有时候需要公共部分渲染页面,这个时候使用自定义标签实现自 ...
- 如何预防SQL注入?预编译机制
1.预编译机制(一次编译多次执行,防止sql注入) 2.预编译机制
- 如何寻找sql注入漏洞?
1.sql注入是怎么产生的 2.如何寻找sql注入漏洞 在地址栏输入单双引号造成sql执行异常(get) post请求,在标题后输入单引号,造成sql执行异常.