Comparison of Java priority queue (heap) and objects

Posted by evlive on Thu, 13 Jan 2022 10:37:25 +0100

1, Sequential storage of binary tree

1.1 storage mode

Use the array to save the binary tree structure, that is, the binary tree is put into the array by sequence traversal.
Generally, it is only suitable for representing complete binary trees, because incomplete binary trees will waste space. The main use of this approach is the representation of the heap.

1.2 subscript relationship

  • If the subscript of the parent is known, then:
    Left subscript = 2 * parent + 1;
    Right subscript = 2 * parent + 2;
  • If the child subscript is known, then:
    Parent subscript = (child - 1) / 2;

2, Heap

2.1 related concepts of reactor

  1. Heap logic is a complete binary tree;
  2. Heap is physically stored in an array;
  3. If the value of any node is greater than the value of the node in its subtree, it is called a large heap, or a large root heap, or a maximum heap;
  4. On the contrary, it is a small heap, or a small root heap, or a minimum heap;
  5. The basic function of heap is to quickly find the most important value in the set.

2.2 operation - downward adjustment

Premise: the left and right subtrees must be a heap before they can be adjusted.

Create a large root heap

Heap building is a bottom-up heap building method.
Take the large root heap as an example. First, create a large root heap:

public class TestHeap {
    public int[] elem;
    public int usedSize;
    public TestHeap(){
        this.elem = new int[10];
    }

    /**
     * Create large root heap
     * @param array
     */
    public  void  creatHeap(int[] array){

        for (int i = 0; i < array.length; i++) {
            this.elem[i] = array[i];
            usedSize++;
        }

        //parent represents the root node of each subtree, (array.length - 1) the subscript of the last node, (array.length - 1 - 1) / 2 the root node of the last node
        for(int parent= (array.length - 1 - 1) / 2; parent >= 0; parent--)
            //The end position of each adjustment of the second parameter is uncertain
            adjustDown(parent,this.usedSize);
    }

Test code:

public static void main(String[] args) {
        int[] arr = {1,5,3,8,7,6};
        TestHeap testHeap = new TestHeap();
        testHeap.creatHeap(arr);
        //Break point debugging here
        System.out.println("dvsfb");
    }

Output results:

// Before reactor construction
int[] array = { 1,5,3,8,7,6 };
// After reactor construction
int[] array = { 8,7,6,5,1,3 };

The time complexity of heap building in heap sorting is O(n)

Detailed explanation of time complexity
The bottom-up heap building method is Floyd heap building algorithm. Because the time complexity of the top-down heap building method in the opposite direction is O(n · logn).

Suppose that the target heap is a full heap, that is, the number of nodes in layer k is 2 ᵏ. If the size of the input array is n and the height of the heap is h, then n = 2 is satisfied between N and H ʰ ⁺ ¹ - 1, which can be reduced to h = log З (n+1) - 1. (the number of layers k and height h start from 0, that is, only the heap height of the root node is 0 and the empty heap height is - 1). In the process of heap building, each node needs a down filtering operation, and the number of exchanges is equal to the depth from the node to the leaf node. Then the number of exchanges of all nodes in each layer is the number of nodes multiplied by the depth from the leaf node to the node (for example, the number of exchanges in the first layer is 2 ⁰ · h, and the number of exchanges in the second layer is 2) ¹ · (h-1), and so on). Sum the exchange times Sn from the top of the heap to the last layer:
Sn = 2⁰ · h + 2 ¹ · (h - 1) + 2 ² · (h - 2) + ...... + two ʰ ⁻ ² · 2 + 2 ʰ ⁻ ¹ · 1 + 2 ʰ · 0 Recorded as ①;
① It is simplified as: Sn = h + 2 ¹ · (h - 1) + 2 ² · (h - 2) + ...... + two ʰ ⁻ ² · 2 + 2 ʰ ⁻ ¹;
For ① equals to the left and right sides of the sign multiplied by 2, it is recorded as formula ②:
②: 2Sn = 2¹ · h + 2² · (h - 1) + 2³ · (h - 2) + ...... + 2ʰ⁻¹ · 2 + 2ʰ;
Subtract formula ① from formula ②, where the operand of formula ② is shifted one bit to the right to align the parts with the same index (i.e. dislocation subtraction):

Simplified formula ③:
③ : Sn = -h + 2¹ + 2² + 2³ + ...... + 2ʰ⁻¹ + 2ʰ;
Use the summation formula of proportional series for the exponential part:

Get:

Sn =2 ʰ ⁺ ¹ - (h + 2) in the above process, the relationship between N and h is: h = log З (n+1) - 1. Substitute it into SN to get: Sn =(n+1)(log2(n+1)-1+2)
After simplification: Sn = n - log ν (n + 1)
For the logarithmic function, when n approaches a certain value, the result tends to be flat relative to the x-axis, and the change range is small. Therefore, the asymptotic complexity is O(n).

Downward adjustment process

  1. If the parent is already a leaf node, the whole adjustment process ends.
  2. If the root node is defined as parent, its left child node is: child = 2 * parent+ 1;
  3. Find the maximum value of the left and right children on the premise that there is a right child. Because the storage structure of the heap is an array, judge whether there is a right child, that is, judge whether the subscript of the right child is out of bounds, that is, child + 1 < len indicates that it is not out of bounds. If there is a right child and the left child is smaller than the right child, then child + +; That is, the right child is child;
  4. Determine the size of parent and child, child and father nodes, and exchange if the child node is large;
  5. Because the nature of the heap at the parent location may be destroyed, treat the child as a parent, redefine the child based on the parent, and repeat the above process downward.

Code example:

 public void adjustDown(int root,int len){
 	int parent = root;
 	int child = 2 * parent+ 1;//Left child
 	while(child < len){
		//Find the maximum value of the left and right children, provided there is a right child
        //If there is a right child and the left child is smaller than the right child, then child++
 		if(child + 1 < len && elem[child] < elem[child + 1]){
 		child++;
 		}
 		//Judge the node size of the child and the father. If the child node is large, exchange it
 		if(elem[child] > elem[parent]){
 			int tmp = elem[child];
 			elem[child] = elem[parent];
 			 elem[parent] = tmp;
 			 parent = child;
 			 child = 2* parent +1;
 		}else{
 		 //When there are no more nodes that can be adjusted during the adjustment process, the break ends directly
	 		break;
 		}
 	} 	
 }

3, Application of heap (priority queue)

The data structure should provide two basic operations, one is to return the highest priority object, and the other is to add a new object. This data structure is called priority queue. Use the heap to build priority queues.

3.1 queuing

Queued process (taking a large number of piles as an example):

  1. First, put the array by tail interpolation;
  2. Compare its value with that of its parents. If the value of its parents is large, it meets the nature of the heap and the insertion ends;
  3. Otherwise, exchange the value of its and parent position, and repeat steps 2 and 3;
  4. Up to the root node.

Code example:

	/**
     * Heap up adjustment
     * @param child
     */
    public void adjustUp(int child){
        int parent = (child - 1) / 2;
        while (child > 0){
            if(this.elem[child] > this.elem[parent]){
                int tmp = this.elem[child];
                this.elem[child] = this.elem[parent];
                this.elem[parent] = tmp;
                child = parent;
                parent = (child - 1) / 2;
            }else {
                break;
            }
        }
    }
    
 public void push(int val){
        if(isFull()){
            this.elem = Arrays.copyOf(this.elem,this.elem.length * 2);
        }
        this.elem[this.usedSize] = val;//10
        usedSize++;//11
        adjustUp(this.usedSize - 1);//10
    }
    //Determine whether it is full
    public boolean isFull(){
        return this.usedSize == this.elem.length;
    }

3.2 operation - out of queue (highest priority)

In order to prevent the structure of the heap from being damaged, the top element of the heap is not deleted directly, but replaced by the last element of the array, and then readjusted into the heap by adjusting downward.

 public void pop(){
        if(isEmpty()){
            return;
        }
        //Exchange the element of the 0 subscript with the last element
        int tmp = this.elem[0];
        this.elem[0] = this.elem[usedSize - 1];
        this.elem[usedSize - 1] = tmp;
        usedSize--;
        //Downward adjustment
        adjustDown(0,usedSize);
    }
    //Judge whether it is empty
    public boolean isEmpty(){      
        return this.usedSize == 0;
    }

3.3 return to the first element of the team (highest priority)

Just return the heap top element.

//Returns the first element of the queue
    public int  peek(){
        if(isEmpty()){
            return -1;
        }
        //Get the team head element
       return this.elem[0];
    }

3.4 heap sorting

  1. First create a large pile and adjust each tree;
  2. Start heap sorting, swap first, and then adjust downward until end is 0.
	//Heap sort
    //First create a lot and adjust each tree
    //Start heap sorting, swap first, and then adjust until end is 0
    public void heapSort(){
        int end = this.usedSize - 1;
        while (end > 0){
            int tmp = this.elem[0];
            this.elem[0] = this.elem[end];
            this.elem[end] = tmp;
            adjustDown(0,end);
            end--;
        }
    }

4, Object comparison

4.1 equals

equal can only be compared according to equality, not greater than or less than

public static void main(String[] args) {
        Student student1 = new Student("zhang",12);
        Student student2 = new Student("li",89);
        System.out.println(student1.equals(student2));//false overrides the equals method. If it is not overridden, the equals method of Object is called by default
    }

4.2 Comparble

For user-defined types, if you want to compare by size: when defining a class, implement the Comparble interface, and then override the compareTo method in the class.
Comparable is Java Lang, which can be used directly.

class Student implements Comparable<Student>{

    public String name;
    public int age;


    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    //Override compareTo method
    public int compareTo(Student o) {
        return this.age - o.age;
    }
}

4.3 Comparator

Override the compare method in Comparator;
Comparator is Java The generic interface class in util package must be imported into the corresponding package.

class Student{

    public String name;
    public int age;


    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age &&
                Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

class AgeComparator implements Comparator<Student> {
    @Override
    //The first argument to compare is the inserted element
    public int compare(Student o1, Student o2) {
        return o1.getAge() - o2.getAge();//Default small heap
        //return o2.getAge() - o1.getAge();// Heaps
    }
}
class NameComparator implements Comparator<Student>{
    @Override
    public int compare(Student o1, Student o2) {
        return o1.getName().compareTo(o2.getName());
    }
}

Test code:

 public static void main(String[] args) {
        Student[] students = new Student[2];
        students[0] = new Student("ang",102);
        students[1] = new Student("za",92);
        //Sort by age
        Arrays.sort(students, new AgeComparator());
        //Sort by first letter
//        Arrays.sort(students, new NameComparator());
        System.out.println(Arrays.toString(students));
    }

Output results:

4.4 comparison of three methods

Override methodexplain
Object.equalsBecause all classes inherit from Object, you can override them directly, but you can only compare whether they are equal or not
Comparable.compareToThe interface needs to be implemented manually, which is highly intrusive. However, once implemented, this class is used in order every time, which belongs to internal order
Comparator.compareIt is necessary to implement a comparator object, which is less invasive to the comparison class, but more invasive to the algorithm code

5, Comparison method of PriorityQueue

The bottom layer of PriorityQueue in the collection framework uses heap structure, so its internal elements must be able to match the size. PriorityQueue adopts two methods: compare and Comparator.

  1. Comparble is the default internal comparison method. If a user inserts a custom type object, the object must implement the comparble interface and override the compareTo method.
  2. You can also choose to use the Comparator object. If you insert a custom type object, you must provide a Comparator class to implement the Comparator interface and override the compare method.
 public static void main(String[] args) {
        //The default heap size is 10
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        //The default is small heap sorting
        queue.offer(61);
        queue.offer(21);
        queue.offer(1);
        System.out.println(queue);//[1, 61, 21]
    }

It can be seen from the output queue that its priority queue is sorted by small heap by default.

Combined with the comparison results of method PriorityQueue in 4.3:

 public static void main(String[] args) {
        //Compare by initials
//        PriorityQueue<Student> priorityQueue = new PriorityQueue<>(new NameComparator());
        //Comparison by age
        PriorityQueue<Student> priorityQueue = new PriorityQueue<>(new AgeComparator());
        priorityQueue.offer(new Student("ang",12));
        priorityQueue.offer(new Student("li",4));
        System.out.println(priorityQueue);
    }

Output results:

6, TopK problem

TopK problem
Find the largest K or the smallest K in an array.
If you want to find the maximum k elements, first generate a small top heap with the first k elements, which is used to store the current maximum k elements. Then, start scanning from the k+1 element, and compare it with the top of the heap (the smallest element in the heap). If the scanned element is larger than the top of the heap, replace the elements at the top of the heap, and adjust the heap to ensure that the k elements in the heap are always the current maximum k elements. Until all n-k elements are scanned, the k elements in the final heap are the required TopK.

Code example:
Find the top three largest elements:

 public static void TopK(int arr[],int k){
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
//                return o2 - o1;// Heaps
                 return o1 - o2;//Small pile
            }
        });
        for (int i = 0; i < arr.length; i++) {
        //First put the first k elements into the queue
            if (maxHeap.size() < k){
                maxHeap.offer(arr[i]);
            }else {
                int top = maxHeap.peek();
//                If (top > arr [i]) {/ / find the first three smallest
                if (top < arr[i]){ //Find the top three largest
                    maxHeap.poll();
                    maxHeap.offer(arr[i]);
                }
            }
        }
        System.out.println(maxHeap);
    }
 public static void main(String[] args) {
        //Find the first three smallest numbers
        //Create a heap of size 3
        int[] arr = {1,31,2,10,7,35,21,19,56};
        TopK(arr,3);
    }

Output results:

Example: Force buckle 373 Find and minimum K-pair numbers

 public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
        PriorityQueue<List<Integer>> maxHeap = new PriorityQueue<>(k, new Comparator<List<Integer>>() {
            @Override
            public int compare(List<Integer> o1, List<Integer> o2) {
                return (o2.get(0) + o2.get(1) - (o1.get(0) + o1.get(1)));//Heaps
            }
        });
        for (int i = 0; i <nums1.length; i++) {
            for (int j = 0; j < nums2.length; j++) {
                if(maxHeap.size() < k){
                    List<Integer> pair = new ArrayList<>();
                    pair.add(nums1[i]);
                    pair.add(nums2[j]);
                    maxHeap.offer(pair);
                }else {
                    List<Integer> top = maxHeap.peek();
                    //If the current element is smaller than the top of the heap, pop the top of the heap, and then put the small data into the heap
                    int topValue = top.get(0)+top.get(1);
                    if (topValue > nums1[i] +nums2[j]){
                        maxHeap.poll();
                        List<Integer> pair = new ArrayList<>();
                        pair.add(nums1[i]);
                        pair.add(nums2[j]);
                        maxHeap.offer(pair);
                    }
                }
            }
        }
        //In the middle of the heap is the final result
        List<List<Integer>>  ret = new ArrayList<>();
        for (int i = 0; i < k && !maxHeap.isEmpty(); i++) {
            ret.add(maxHeap.poll());
        }
        return ret;
    }

above.

Topics: Java Algorithm data structure