JAVA 修炼秘籍第一章:《痛苦的折磨》
JAVA 修炼秘籍第二章:《逐渐魔化》
JAVA 修炼秘籍第三章:《绝地反击》
JAVA 修炼秘籍第四章:《闭关修炼》
JAVA 修炼秘籍第五章:《卧薪尝胆》
JAVA 修炼秘籍第六章:《鏖战》
JAVA 修炼秘籍第七章:《面向对象编程》
JAVA 修炼秘籍第八章:《String类》
JAVA 修炼秘籍第九章:《List / ArrayList / LinkedList 》
JAVA修炼秘籍(番外篇)第一章:《这四道代码题,你真的会吗?》
JAVA修炼秘籍(番外篇)第二章:《图书馆管理系统》
——————————————————————生活以痛吻我,我却报之以歌。
一、介绍
- 堆逻辑上是一棵完全二叉树。
- 堆物理逻辑上保存在数组中。
- 满足任意节点的值都大于其子树中节点的值成为大根堆(大堆)。
- 满足任意节点的值都小于其子树中节点的值成为小根堆(小堆)。
- 堆的作用是找出当前集合中的最值(最大值。最小值,前K个最大或最小值等等)。
- 堆创建对象时,存储空间为11。
- 在建堆时,默认为小堆存储形式。
- 大堆或小堆可以通过构造时传入的比较器来调整(Comparator)。
- 它不允许NULL对象
- 添加到PriorityQueue的对象必须具有可比性。
- 如果存在多个具有相同优先级的对象,则会随机得到其中任意一个。
- PriorityQueue不是线程安全的。
- PriorityQueue所提供的add方法与poll方法为O( log(n) )时间。
二、堆排序
上述图片堆排序的过程如下:
- 先将整个数组调整为大堆存储形式
- 这时我们可以确定的是整棵树的根节点位置的数组是整个数组的最大值
- 再将根节点与尾部交换(这里尾部不是最后一个下标,而是每次找出了最大的值放到了最后我们就不要再修改它)。
- 这时我们就将整个数组的最大值找了出来。
- 其次寻找,第二大,第三大等等。。。以此类推,循环上述步骤。
- 这里我们要知道:
- 已知双亲(parent)的下标,则:左孩子(left)下标=2 parent +1;右孩子(right)下标= 2 parent + 2;
- 已知孩子(不区分左右)(child)下标,则:双亲(parent)下标=(child - 1)/ 2;
代码如下:(已全部注释)
public static void main(String[] args) {
int[] arr={27,15,19,18,28,34,65,49,25,37};//数组
heapSort(arr);//调用函数
System.out.println(Arrays.toString(arr));//输出
}
public static void heapSort(int[] arr){
mySort(arr);//先将整个数组调整为大根堆
int end=arr.length-1;//尾下标
while(end>0){//循环将每个数据都与头交换
swap(arr,0,end);//头尾交换
shiftDown(arr,0,end);//交换后让整个堆再次成为大根堆
end--;//尾部向前移动,因为后面的已经有序了,我们就把需要调整的边界缩小。
}
}
public static void mySort(int[] arr){
for(int i=(arr.length-1)/2;i>=0;i--){//最后的一颗子树根的位置就是i。
shiftDown(arr,i,arr.length);//把每一颗子树调整为大根堆
}
}
public static void shiftDown(int[] arr,int parent,int len){
int child=parent*2+1;//得到每一颗子树的左节点
while(child<len){//向下调整
if(child+1<len&&arr[child]<arr[child+1]){//比较左右子树,找出最大的
child++;
}
if(arr[child]>arr[parent]){//判断左右子树是否有大于当前节点
swap(arr,child,parent);//交换
parent=child;//继续向下调整
child=parent*2+1;//得到要向下调整树的根节点
}else{
break;//如果左右孩子都不大于当前根节点,证明此时当前子树已经为最大堆。
}
}
}
public static void swap(int[] arr,int x,int y){//交换函数
int tmp=arr[x];
arr[x]=arr[y];
arr[y]=tmp;
}
三、添加数据
-
添加数据的add函数实现其实不难,如果已经理解了堆排序,那么添加数据则是小菜一碟
-
假设当我们已经有一个小堆时,那么此时我们整个堆的数据是完全有序的。
-
首先我们将当前数据插入到数组末尾
将当前数据于自己的兄弟节点与父亲节点比较。 -
如果小于则继续交换,直到大于或等于为止。
代码如下:
public static void shiftUp(int[] arr,int index){
while(index>0){
int parent=(index-1)/2;
if(arr[parent]<=arr[index]){
break;
}
int tmp=arr[parent];
arr[parent]=arr[index];
arr[index]=tmp;
index=parent;
}
}
四、TopK问题
原题大意:输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
- 看似简单的题目,其实暗藏玄机,我们用堆来完成。
- 创建一个大小为K的大堆。(创建大堆是因为当前堆中的最大元素在堆订,其他元素都比堆顶小)。
- 开始遍历数组,前K个不用比较直接放入大堆中。
- 放满K个之后,我们拿接下来的每个元素与当前大堆的堆顶元素比较。
- 小于堆顶元素,就将堆顶元素删除,将当前元素放入大堆,继续向下
- 大于堆顶元素,不用操作。
- 最后遍历结束,大堆中的元素就是最小的K个数。
代码如下:
public int[] getLeastNumbers(int[] arr, int k) {
int[] ret=new int[k];//返回的数组
if(k==0){//如果k==0直接返回
return ret;
}
PriorityQueue<Integer> queue=new PriorityQueue<>(new Comparator<Integer>() {
@Override//创建队列,通过Comparator调整为大堆
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
int i=0;
while(i<arr.length){//遍历数组
if(i<k){//大堆没满时,直接放入
queue.add(arr[i]);
}else{
if(arr[i]<=queue.peek()){//当前数据小于等于大堆顶元素。
queue.poll();//删除堆顶元素
queue.add(arr[i]);//放入当前元素
}
}
i++;
}
for(int j=0;j<k;j++){
ret[j]=queue.poll();//把大堆元素全部放到数组中。
}
return ret;
}
五、Java PriorityQueue构造函数
PriorityQueue类提供了六种在java中构造有限队列的方法。
1.PriorityQueue();
- 使用默认初始容量构造空队列。
- 该容量根据其自然顺序对其元素进行排序。
PriorityQueue queue=new PriorityQueue();
2.PriorityQueue(Collection c);
- 构造包含指定集合中元素的空队列。
PriorityQueue<Integer> queue=new PriorityQueue<>();
3.PriorityQueue(int initialCapacity);
- 构造具有只当初始容量的空队列,该容量根据指定的比较器对其元素进行排序。
PriorityQueue<Integer> queue=new PriorityQueue<>(10);
4.PriorityQueue(int initialCapacity, Comparator comparator);
- 构造具有指定初始容量的空队列,该容量根据指定的比较器对其元素进行排序。
PriorityQueue<Integer> queue=new PriorityQueue<>(10, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
5.PriorityQueue(PriorityQueue c);
- 构造包含指定优先队列中元素的队列。
PriorityQueue<Integer> queue=new PriorityQueue<>(queue1);
6.PriorityQueue(SortedSet c);
- 构造包含指定有序集合中元素的空队列。
PriorityQueue<Integer> queue=new PriorityQueue<>(new SortedSet<Integer>() {
@Override
public Comparator<? super Integer> comparator() {
return null;
}
@Override
public SortedSet<Integer> subSet(Integer fromElement, Integer toElement) {
return null;
}
@Override
public SortedSet<Integer> headSet(Integer toElement) {
return null;
}
@Override
public SortedSet<Integer> tailSet(Integer fromElement) {
return null;
}
@Override
public Integer first() {
return null;
}
@Override
public Integer last() {
return null;
}
@Override
public int size() {
return 0;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public boolean contains(Object o) {
return false;
}
@Override
public Iterator<Integer> iterator() {
return null;
}
@Override
public Object[] toArray() {
return new Object[0];
}
@Override
public <T> T[] toArray(T[] a) {
return null;
}
@Override
public boolean add(Integer integer) {
return false;
}
@Override
public boolean remove(Object o) {
return false;
}
@Override
public boolean containsAll(Collection<?> c) {
return false;
}
@Override
public boolean addAll(Collection<? extends Integer> c) {
return false;
}
@Override
public boolean retainAll(Collection<?> c) {
return false;
}
@Override
public boolean removeAll(Collection<?> c) {
return false;
}
@Override
public void clear() {
}
});
六、Java PriorityQueue方法
1.boolean add(object):
作用:将指定的元素插入此优先级队列
queue.add(1);
2.boolean offer(object):
作用:将指定的元素插入此优先级队列。
queue.offer(2);
3.boolean remove(object):
作用:从此队列中删除指定元素的单个实例(如果存在)。
queue.remove(1);
4.Object poll():
作用:检索并删除此队列的头部,如果此队列为空,则返回null。
queue.poll();
5.Object element():
作用:检索但不删除此队列的头部,如果此队列为空,则返回null。
queue.element();
6.Object peek():
作用:检索但不删除此队列的头部,如果此队列为空,则返回null。
int i=queue.peek();
7.void clear():
作用:从此优先级队列中删除所有元素
queue.clear();
8.Comparator comparator():
作用:返回用于对此队列中的元素进行排序的比较器,如果此队列根据其元素的自然顺序排序,则返回null。
queue.comparator();
9.boolean contains(Object o):
作用:如果此队列包含指定的元素,则返回true。
queue.contains(10);
10.Iterator iterator():
作用:返回此队列中元素的迭代器。
queue.iterator();
11.int size():
作用:返回此队列中的元素数。
int size=queue.size();
12.Object [] toArray():
作用:返回包含此队列中所有元素的数组。
Object[] ret=queue.toArray();