系统提供的堆 VS 手动改写堆

简介: 系统提供的堆 VS 手动改写堆

要求:在一个已经组织好的大根堆或者小根堆中,更改其中的某个值后,仍要求其为大根堆或者小根堆

为啥系统提供的堆实现不了,因为系统提供的堆结构不会记indexMap这张反向表。但是如果系统增加了这张反向表,实现代价又会很高,而且系统也不确定你会不会用到这些功能,所以系统干脆不提供了,大量的情况就是这样。但是不排除某种语言中有这个功能(也就是有resign()这个方法),但是C++跟Java中是没有的,所以这种情况下需要自己手动改写堆结构!

具体请看下面代码和运行结果吧!

package com.harrison.class04;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.PriorityQueue;
public class Code06_HeapGreater {
  public static class MyHeap<T>{
    private ArrayList<T> heap;
    // 为啥系统实现不了,自己手动改写后能实现,关键在于indexMap表
    // 任何一个你给我的样本T,记录着它在堆heap上的什么位置Integer
    private HashMap<T, Integer> indexMap;
    private int heapSize;
    // 告诉我关于样本类型T的比较器comparator
    // 有了这个比较器就知道如何比较大小了
    private Comparator<? super T> comparator;
    public MyHeap(Comparator<? super T> com) {
      heap=new ArrayList<>();
      indexMap=new HashMap<>();
      heapSize=0;
      comparator=com;
    }
    public boolean isEmpty() {
      return heapSize==0;
    }
    public int size() {
      return heapSize;
    }
    public boolean contains(T key) {
      return indexMap.containsKey(key);
    }
    public void push(T value) {
      heap.add(value);
      indexMap.put(value, heapSize);
      heapInsert(heapSize++);
    }
    public void heapInsert(int index) {
      // 比较器告诉你 你自己跟父结点如何比较大小
      while(comparator.compare(heap.get(index), heap.get((index-1)/2))<0){
        swap(index, (index-1)/2);
        index=(index-1)/2;
      }
    }
    public T pop() {
      T ans=heap.get(0);
      int end=heapSize-1;
      swap(0, end);
      // 移除的是堆中的位置!!不是样本!!!
      heap.remove(end);
      indexMap.remove(ans);
      heapify(0, --heapSize);
      return ans;
    }
    public void heapify(int index,int heapSize) {
      int left=index*2+1;
      while(left<heapSize) {
        int largest=(left+1<heapSize && 
            comparator.compare(heap.get(left+1), heap.get(left))<0
            )?left+1:left;
        int allLargest=comparator.compare(heap.get(largest), heap.get(index))<0
            ?largest:index;
        if(allLargest==index) {
          break;
        }
        swap(allLargest, index);
        index=allLargest;
        left=2*index+1;
      }
    }
    // 关键方法:为什么在一个大根堆或者小根堆中更改某个值后,仍然能调整为大根堆或小根堆
    // 用户要求改变堆中的某个值,但是没告诉我们这个值应该是往上移动还是往下移动
      // 通过indexMap表找到样本在堆中的哪个位置,然后调整为大根堆或小根堆
    public void resign(T value) {
      int valueIndex=indexMap.get(value);
      // 两个方法只会中一个
      heapInsert(valueIndex);
      heapify(valueIndex, heapSize);
    }
    // 在堆和indexMap表中交换i位置和j位置的数
    // heap有任何变换,indexMap表都要跟着一起变,保证两个结构强同步
    public void swap(int i, int j) {
      T o1=heap.get(i);
      T o2=heap.get(j);
      heap.set(i, o2);
      heap.set(j, o1);
      indexMap.put(o2, i);
      indexMap.put(o1, j);
    }
  }
  public static class Student{
    public int classNo;
    public int age;
    public int id;
    public Student(int c,int a,int i) {
      classNo=c;
      age=a;
      id=i;
    }
  }
  public static class StudentComparator implements Comparator<Student>{
    @Override
    public int compare(Student o1, Student o2) {
      // TODO Auto-generated method stub
      return o1.age-o2.age;
    }
  }
  public static void main(String[] args) {
    System.out.println("系统提供的堆:");
    Student s1=null;
    Student s2=null;
    Student s3=null;
    Student s4=null;
    Student s5=null;
    Student s6=null;
    s1=new Student(2, 50, 11111);
    s2=new Student(1, 60, 22222);
    s3=new Student(6, 10, 33333);
    s4=new Student(3, 20, 44444);
    s5=new Student(7, 72, 55555);
    s6=new Student(1, 14, 66666);
    PriorityQueue<Student> heap=new PriorityQueue<>(new StudentComparator());
    heap.add(s1);
    heap.add(s2);
    heap.add(s3);
    heap.add(s4);
    heap.add(s5);
    heap.add(s6);
    while(!heap.isEmpty()) {
      Student cur=heap.poll();
      System.out.println(cur.classNo+","+cur.age+","+cur.id);
    }
    System.out.println("======================");
    System.out.println("手动改写的堆:");
    MyHeap<Student> myHeap=new MyHeap<>(new StudentComparator());
    myHeap.push(s1);
    myHeap.push(s2);
    myHeap.push(s3);
    myHeap.push(s4);
    myHeap.push(s5);
    myHeap.push(s6);
    while(!myHeap.isEmpty()) {
      Student cur=myHeap.pop();
      System.out.println(cur.classNo+","+cur.age+","+cur.id);
    }
    System.out.println("======================");
    System.out.println("系统提供的堆(改了堆中某些值):");
    s1=new Student(2, 50, 11111);
    s2=new Student(1, 60, 22222);
    s3=new Student(6, 10, 33333);
    s4=new Student(3, 20, 44444);
    s5=new Student(7, 72, 55555);
    s6=new Student(1, 14, 66666);
    heap=new PriorityQueue<>(new StudentComparator());
    heap.add(s1);
    heap.add(s2);
    heap.add(s3);
    heap.add(s4);
    heap.add(s5);
    heap.add(s6);
    s2.age=6;
    s4.age=12;
    s5.age=10;
    s6.age=84;
    while(!heap.isEmpty()) {
      Student cur=heap.poll();
      System.out.println(cur.classNo+","+cur.age+","+cur.id);
    }
    System.out.println("======================");
    System.out.println("手动改写的堆(改了堆中某些值):");
    s1=new Student(2, 50, 11111);
    s2=new Student(1, 60, 22222);
    s3=new Student(6, 10, 33333);
    s4=new Student(3, 20, 44444);
    s5=new Student(7, 72, 55555);
    s6=new Student(1, 14, 66666);
    myHeap=new MyHeap<>(new StudentComparator());
    myHeap.push(s1);
    myHeap.push(s2);
    myHeap.push(s3);
    myHeap.push(s4);
    myHeap.push(s5);
    myHeap.push(s6);
    s2.age=6;
    myHeap.resign(s2);
    s4.age=12;
    myHeap.resign(s4);
    s5.age=10;
    myHeap.resign(s5);
    s6.age=84;
    myHeap.resign(s6);
    while(!myHeap.isEmpty()) {
      Student cur=myHeap.pop();
      System.out.println(cur.classNo+","+cur.age+","+cur.id);
    }
  }
}

image.png

相关文章
|
5月前
|
设计模式 前端开发 Java
带你说说 SpringMVC 的处理流程
本文详细解析了Spring MVC的工作原理及其核心组件。从曾经的Servlet王者讲起,分析了其局限性,引出DispatcherServlet作为前端控制器的概念。文章深入介绍了Spring MVC的两级控制器架构:前端控制器DispatcherServlet和次级控制器Handler(通常是Controller)。同时,讲解了请求映射专家HandlerMapping、拦截器HandlerInterceptor、责任链HandlerExecutionChain以及解耦关键ModelAndView的作用。
100 0
|
缓存 安全 算法
SSL和TLS部署实践
在TLS中,所有安全性都以服务器的加密身份开始,这就需要一个强大的私钥来防止攻击者进行模拟攻击。同样重要的是拥有一个有效和强大的证书,它会授予私钥来代表一个特定的主机名。
331 2
|
存储 Java 索引
Java中foreach遍历数组如何拿到想要的值
总结来说,通过foreach循环遍历数组并获取所需的值是一种简单且代码清晰的操作,特别适用于只需访问集合或数组中的每个元素且不需要元素索引或修改集合的场景。在实际编程中,根据场景需求合理选择循环类型,可大大提高代码的可读性与效率。
389 4
|
NoSQL Linux 程序员
Linux objdump命令:深入解析与实战应用
`objdump`是Linux下的反汇编工具,用于将二进制文件转换为汇编代码,便于理解程序底层。它可以反汇编目标文件、可执行文件和库,支持多种参数,如显示符号表(-t)、反汇编代码(-d)、源代码与汇编混合视图(-S)。在实践中,结合-g编译选项和特定段(-j)反汇编,能辅助调试和分析。使用时注意包含调试信息,选择适当参数,并与其他工具(如gdb)配合使用。
|
Linux
【超全】Linux命令思维导图总结 值得收藏
【超全】Linux命令思维导图总结 值得收藏
249 0
python 基于cartopy库绘制台风路径(包含代码详细解释)
python 基于cartopy库绘制台风路径(包含代码详细解释)
python 基于cartopy库绘制台风路径(包含代码详细解释)
凭借左程云(左神)的这份 “程序员代码面试指南”我入职了字节
左程云,本科就读于华中科技大学、硕士毕业于在芝加哥大学。先后在IBM、百度、GrowingIO和亚马逊工作,是一个刷题7年的算法爱好者,也是马士兵教育的算法授课老师。2014年起专职做程序员算法和数据结构培训,代码面试培训,刷题交流等相关工作。
|
存储 Go
善用这些技巧 Go语言map元素删除那么简单
善用这些技巧 Go语言map元素删除那么简单
3353 0
|
SQL 存储 关系型数据库
MySQL中 LBCC 和 MVCC 的理解,常见问题及示例:
MySQL中 LBCC 和 MVCC 的理解,常见问题及示例:
728 1
MySQL中 LBCC 和 MVCC 的理解,常见问题及示例:
|
缓存 Java Android开发
12 张图看懂 CPU 缓存一致性与 MESI 协议,真的一致吗?
什么是缓存一致性问题,CPU Cache 的读取和写入过程是如何执行的,MESI 缓存一致性协议又是什么?今天我们将围绕这些问题展开。
1897 1