4.比较器与堆

堆结构

逻辑概念上就是完全二叉树结构

完全二叉树,通俗的说就是,要么这一层是满的,在不满的这一层也是从左到右依次变满的。

如果下标从0开始算,那么

  • 左孩子(下标):2*i+1
  • 右孩子:2*i+2
  • 父节点:(i-1)/2

但是有时候0弃而不用,从1开始算起

  • 左:2*i(i<<1)
  • 右:2*i+1((i<<1)|1)
  • 父:i/2(i>>1)

为什么?因为频繁操作i的时候,位运算效率更高!

大根堆:每一棵子树,最大值都是自己头节点的值

小根堆:每一棵子树,最小值都是自己头节点的值

package com.harrison.four;

import java.util.Comparator;

//用数组实现一个大根堆结构
//之后找出大根堆中的最大值并删除
//且剩下的数仍要求是一个大根堆结构
public class Code02_Heap {
    
    
	public static class MyMaxHeap {
    
    
		private int[] heap;// 用数组表示堆
		private int limit;
		private int heapSize;

		public MyMaxHeap(int limit) {
    
    
			heap = new int[limit];
			this.limit = limit;
			heapSize = 0;
		}

		public boolean isEmpty() {
    
    
			return heapSize == 0;
		}

		public boolean isFull() {
    
    
			return heapSize == limit;
		}

		public void push(int value) {
    
    
			if (heapSize == limit) {
    
    
				throw new RuntimeException("heap is full");
			}
			heap[heapSize] = value;
			heapInsert(heap, heapSize++);
		}

		private void swap(int[] arr, int i, int j) {
    
    
			int tmp = arr[i];
			arr[i] = arr[j];
			arr[j] = tmp;
		}

		// 要求返回大根堆中的最大值,并且在大根堆中把最大值删除
		// 且剩下的数,依然要求保持为大根堆结构
		public int pop() {
    
    
			int ans = heap[0];
			// 将大根堆中最后的数与第一个数交换,且大根堆缩小一个数
			swap(heap, 0, --heapSize);
			// 从零位置开始寻找比自己大的孩子
			heapify(heap, 0, heapSize);
			return ans;
		}

		// 堆结构的两个关键操作:从某个位置开始往上看
		// 动态地建立大根堆
		// 如果收了N个数,时间复杂度为logN
		private void heapInsert(int[] arr, int index) {
    
    
			// arr[index]新进来的数,依次往上移动
			// 移动到0位置或者比父节点小就不要往上移动
			while (arr[index] > arr[(index - 1) / 2]) {
    
    
				swap(arr, index, (index - 1) / 2);
				index = (index - 1) / 2;
			}
		}

		// 堆结构的两个关键操作:从某个位置开始往下调,时间复杂度logN
		// 停:两个孩子都不比我大、没有孩子了
		public void heapify(int[] arr, int index, int heapSize) {
    
    
			int left = index * 2 + 1;// 左孩子下标
			// 如果有左孩子,没有有右孩子?有可能有有可能没有
			// 如果没有左孩子,一定没有右孩子
			while (left < heapSize) {
    
    // 判断左孩子有没有越界,只在我们自己想象的范围里,而不是整个数组
				// 把左右孩子中值大的孩子的下标给largest
				// 右-> 1)有右孩子 && 右孩子比左孩子大
				int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
				largest = arr[largest] > arr[index] ? largest : index;
				if (largest == index) {
    
    
					break;
				}
				// index位置和较大孩子互换
				swap(arr, largest, index);
				index = largest;
				left = index * 2 + 1;
			}
		}
	}

	// 不使用堆结构的暴力办法
	public static class RightMaxHeap {
    
    
		private int[] arr;// 用数组表示堆
		private int limit;
		private int size;

		public RightMaxHeap(int limit) {
    
    
			arr = new int[limit];
			this.limit = limit;
			size = 0;
		}

		public boolean isEmpty() {
    
    
			return size == 0;
		}

		public boolean isFull() {
    
    
			return size == limit;
		}

		public void push(int value) {
    
    
			if (size == limit) {
    
    
				throw new RuntimeException("heap is full");
			}
			arr[size++] = value;
		}

		public int pop() {
    
    
			int maxIndex = 0;
			for (int i = 0; i < size; i++) {
    
    
				if (arr[i] > arr[maxIndex]) {
    
    
					maxIndex = i;
				}
			}
			int ans = arr[maxIndex];
			arr[maxIndex] = arr[--size];
			return ans;
		}
	}

	public static class Mycomparator implements Comparator<Integer> {
    
    
		@Override
		public int compare(Integer o1, Integer o2) {
    
    
			return o2 - o1;
		}
	}

	public static void main(String[] args) {
    
    
		int testTimes = 1000000;
		int value = 1000;
		int limit = 100;
		for (int i = 0; i < testTimes; i++) {
    
    
			int curLimit = (int) (Math.random() * limit) + 1;
			MyMaxHeap my = new MyMaxHeap(curLimit);
			RightMaxHeap test = new RightMaxHeap(curLimit);
			int curOpTimes = (int) (Math.random() * limit);
			for (int j = 0; j < curOpTimes; j++) {
    
    
				if (my.isEmpty() != test.isEmpty()) {
    
    
					System.out.println("Oops!");
				}
				if (my.isFull() != test.isFull()) {
    
    
					System.out.println("Oops!");
				}
				if (my.isEmpty()) {
    
    
					int curValue = (int) (Math.random() * value);
					my.push(curValue);
					test.push(curValue);
				} else if (my.isFull()) {
    
    
					if (my.pop() != test.pop()) {
    
    
						System.out.println("Oops!");
					}
				} else {
    
    
					if (Math.random() < 0.5) {
    
    
						int curValue = (int) (Math.random() * value);
						my.push(curValue);
						test.push(curValue);
					} else {
    
    
						if (my.pop() != test.pop()) {
    
    
							System.out.println("Oops!");
						}
					}
				}
			}
		}
		System.out.println("finish!");
	}
}

堆排序
  • 将数组变成大根堆
  • 将[0]与[N-1]交换,然后堆缩小一个元素,以此循环

如果用户一次性给出了数组里的所有元素,且只要求数组变为大根堆结构,不用有序,则时间复杂度可以优化为:O(N)

package com.harrison.four;

import java.util.Arrays;

//堆排序
/**
 * 
 * @author Harrison
 * 1.先让整个数组都变成大根堆结构,建立堆的过程:
 *   1)从上到小的方法heapify(),时间复杂度为O(N*logN)
 *   2)从下到上的方法heapInsert(),时间复杂度为O(N)
 * 2.把堆的最大值和堆末尾的值交换,然后减少堆的大小之后,再去调整堆,一直周而复始,
 *   时间复杂度为O(N*logN)
 * 3.堆的大小减少成0之后,排序完成
 */
public class Code04_HeapSort {
    
    
	// 堆排序额外空间复杂度O(1)
	public static void heapSort(int[] arr) {
    
    
		if (arr == null || arr.length < 2) {
    
    
			return;
		}
		// O(N*logN)
		// 堆排序核心代码
		for(int i=0; i<arr.length; i++) {
    
    //O(N)
			heapInsert(arr, i);//O(logN)
		}
		
		// 如果只要求数组变为大根堆结构,不一定要求有序
		// 可以优化为O(N)
//		for (int i = arr.length - 1; i > 0; i--) {
    
    
//			heapify(arr, i, arr.length);
//		}
		
		int heapSize = arr.length;
		swap(arr, 0, --heapSize);
		// O(N*logN)
		while (heapSize > 0) {
    
    // O(N)
			heapify(arr, 0, heapSize);// O(logN)
			swap(arr, 0, --heapSize);// O(1)
		}
	}

	private static void swap(int[] arr, int i, int j) {
    
    
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}

	// arr[index]刚来的数,往上
	public static void heapInsert(int[] arr, int index) {
    
    
		while (arr[index] > arr[(index - 1) / 2]) {
    
    
			swap(arr, index, (index - 1) / 2);
			index = (index - 1) / 2;
		}
	}

	// arr[index]位置的数,能否往下移动
	public static void heapify(int[] arr, int index, int heapSize) {
    
    
		int left = index * 2 + 1; // 左孩子的下标
		while (left < heapSize) {
    
     // 下方还有孩子的时候
			// 两个孩子中,谁的值大,把下标给largest
			// 1)只有左孩子,left -> largest
			// 2) 同时有左孩子和右孩子,右孩子的值<= 左孩子的值,left -> largest
			// 3) 同时有左孩子和右孩子并且右孩子的值> 左孩子的值, right -> largest
			int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
			// 父和较大的孩子之间,谁的值大,把下标给largest
			largest = arr[largest] > arr[index] ? largest : index;
			if (largest == index) {
    
    
				break;
			}
			swap(arr, largest, index);
			index = largest;
			left = index * 2 + 1;
		}
	}

	public static void comparator(int[] arr) {
    
    
		Arrays.sort(arr);
	}

	// for test
	public static int[] generateRandomArray(int maxSize, int maxValue) {
    
    
		int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
		for (int i = 0; i < arr.length; i++) {
    
    
			arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
		}
		return arr;
	}

	// for test
	public static int[] copyArray(int[] arr) {
    
    
		if (arr == null) {
    
    
			return null;
		}
		int[] res = new int[arr.length];
		for (int i = 0; i < arr.length; i++) {
    
    
			res[i] = arr[i];
		}
		return res;
	}

	// for test
	public static boolean isEqual(int[] arr1, int[] arr2) {
    
    
		if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
    
    
			return false;
		}
		if (arr1 == null && arr2 == null) {
    
    
			return true;
		}
		if (arr1.length != arr2.length) {
    
    
			return false;
		}
		for (int i = 0; i < arr1.length; i++) {
    
    
			if (arr1[i] != arr2[i]) {
    
    
				return false;
			}
		}
		return true;
	}

	// for test
	public static void printArray(int[] arr) {
    
    
		if (arr == null) {
    
    
			return;
		}
		for (int i = 0; i < arr.length; i++) {
    
    
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}

	public static void main(String[] args) {
    
    
		int testTime = 500000;
		int maxSize = 100;
		int maxValue = 100;
		boolean succeed = true;
		for (int i = 0; i < testTime; i++) {
    
    
			int[] arr1 = generateRandomArray(maxSize, maxValue);
			int[] arr2 = copyArray(arr1);
			heapSort(arr1);
			comparator(arr2);
			if (!isEqual(arr1, arr2)) {
    
    
				succeed = false;
				break;
			}
		}
		System.out.println(succeed ? "Nice!" : "Fucking fucked!");

		int[] arr = generateRandomArray(maxSize, maxValue);
		printArray(arr);
		heapSort(arr);
		printArray(arr);
	}
}

与堆有关的题目

已知一个几乎有序的数组。几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离一定不超过K,并且K相对于数组长度来说是比较小的。请选择一个合适的排序策略,对这个数组进行排序

假设K=5,生成一个小根堆,先把K+1个数放在小根堆里,所以只有原始数组里0~5位置的数来到0位置上。然后从小根堆里弹出最小值放在0位置上,接下来1位置上的数只有可能是1 ~ 6位置上的数。依次类推

package com.harrison.four;

import java.util.PriorityQueue;

//与堆有关的题目
public class Code05_SortArrayDistanceLessK {
    
    

	public void sortedArrayDistanceLessK(int [] arr, int k) {
    
    
		//默认小根堆
		PriorityQueue<Integer> heap=new PriorityQueue<>();
		int index=0;
		//0...k
		//如果k比数组长度还大,选两个中较小的一个
		for(; index<=Math.min(arr.length-1, k); index++) {
    
    
			heap.add(arr[index]);
		}
		int i=0;
		for(; index<arr.length; i++,index++) {
    
    
			//添加和弹出谁先谁后都一样,因为题目 “每个元素移动的距离一定不超过K”
			heap.add(arr[index]);
			arr[i]=heap.poll();
		}
		while(!heap.isEmpty()) {
    
    
			arr[i++]=heap.poll();//到后面数组没有数可以加了的话,那就只弹出并依次放好
		}
	}
	
	
	public static void main(String[] args) {
    
    
		// TODO Auto-generated method stub

	}

}

语言提供的堆结构 VS 手写的堆结构
  • 取决于,你有没有动态该信息的需求!

  • 语言提供的堆结构,如果你动态改数据,不保证依然有序

  • 手写堆结构,因为增加了对象的位置表,所以能够满足动态改信息的需求

package com.harrison.four;

import java.util.Comparator;

//用数组实现一个大根堆结构
//之后找出大根堆中的最大值并删除
//且剩下的数仍要求是一个大根堆结构
public class Code02_Heap {
    
    
	public static class MyMaxHeap {
    
    
		private int[] heap;// 用数组表示堆
		private int limit;
		private int heapSize;

		public MyMaxHeap(int limit) {
    
    
			heap = new int[limit];
			this.limit = limit;
			heapSize = 0;
		}

		public boolean isEmpty() {
    
    
			return heapSize == 0;
		}

		public boolean isFull() {
    
    
			return heapSize == limit;
		}

		public void push(int value) {
    
    
			if (heapSize == limit) {
    
    
				throw new RuntimeException("heap is full");
			}
			heap[heapSize] = value;
			heapInsert(heap, heapSize++);
		}

		private void swap(int[] arr, int i, int j) {
    
    
			int tmp = arr[i];
			arr[i] = arr[j];
			arr[j] = tmp;
		}

		// 要求返回大根堆中的最大值,并且在大根堆中把最大值删除
		// 且剩下的数,依然要求保持为大根堆结构
		public int pop() {
    
    
			int ans = heap[0];
			// 将大根堆中最后的数与第一个数交换,且大根堆缩小一个数
			swap(heap, 0, --heapSize);
			// 从零位置开始寻找比自己大的孩子
			heapify(heap, 0, heapSize);
			return ans;
		}

		// 堆结构的两个关键操作:从某个位置开始往上看
		// 动态地建立大根堆
		// 如果收了N个数,时间复杂度为logN
		private void heapInsert(int[] arr, int index) {
    
    
			// arr[index]新进来的数,依次往上移动
			// 移动到0位置或者比父节点小就不要往上移动
			while (arr[index] > arr[(index - 1) / 2]) {
    
    
				swap(arr, index, (index - 1) / 2);
				index = (index - 1) / 2;
			}
		}

		// 堆结构的两个关键操作:从某个位置开始往下调,时间复杂度logN
		// 停:两个孩子都不比我大、没有孩子了
		public void heapify(int[] arr, int index, int heapSize) {
    
    
			int left = index * 2 + 1;// 左孩子下标
			// 如果有左孩子,没有有右孩子?有可能有有可能没有
			// 如果没有左孩子,一定没有右孩子
			while (left < heapSize) {
    
    // 判断左孩子有没有越界,只在我们自己想象的范围里,而不是整个数组
				// 把左右孩子中值大的孩子的下标给largest
				// 右-> 1)有右孩子 && 右孩子比左孩子大
				int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
				largest = arr[largest] > arr[index] ? largest : index;
				if (largest == index) {
    
    
					break;
				}
				// index位置和较大孩子互换
				swap(arr, largest, index);
				index = largest;
				left = index * 2 + 1;
			}
		}
	}

	// 不使用堆结构的暴力办法
	public static class RightMaxHeap {
    
    
		private int[] arr;// 用数组表示堆
		private int limit;
		private int size;

		public RightMaxHeap(int limit) {
    
    
			arr = new int[limit];
			this.limit = limit;
			size = 0;
		}

		public boolean isEmpty() {
    
    
			return size == 0;
		}

		public boolean isFull() {
    
    
			return size == limit;
		}

		public void push(int value) {
    
    
			if (size == limit) {
    
    
				throw new RuntimeException("heap is full");
			}
			arr[size++] = value;
		}

		public int pop() {
    
    
			int maxIndex = 0;
			for (int i = 0; i < size; i++) {
    
    
				if (arr[i] > arr[maxIndex]) {
    
    
					maxIndex = i;
				}
			}
			int ans = arr[maxIndex];
			arr[maxIndex] = arr[--size];
			return ans;
		}
	}

	public static class Mycomparator implements Comparator<Integer> {
    
    
		@Override
		public int compare(Integer o1, Integer o2) {
    
    
			return o2 - o1;
		}
	}

	public static void main(String[] args) {
    
    
		int testTimes = 1000000;
		int value = 1000;
		int limit = 100;
		for (int i = 0; i < testTimes; i++) {
    
    
			int curLimit = (int) (Math.random() * limit) + 1;
			MyMaxHeap my = new MyMaxHeap(curLimit);
			RightMaxHeap test = new RightMaxHeap(curLimit);
			int curOpTimes = (int) (Math.random() * limit);
			for (int j = 0; j < curOpTimes; j++) {
    
    
				if (my.isEmpty() != test.isEmpty()) {
    
    
					System.out.println("Oops!");
				}
				if (my.isFull() != test.isFull()) {
    
    
					System.out.println("Oops!");
				}
				if (my.isEmpty()) {
    
    
					int curValue = (int) (Math.random() * value);
					my.push(curValue);
					test.push(curValue);
				} else if (my.isFull()) {
    
    
					if (my.pop() != test.pop()) {
    
    
						System.out.println("Oops!");
					}
				} else {
    
    
					if (Math.random() < 0.5) {
    
    
						int curValue = (int) (Math.random() * value);
						my.push(curValue);
						test.push(curValue);
					} else {
    
    
						if (my.pop() != test.pop()) {
    
    
							System.out.println("Oops!");
						}
					}
				}
			}
		}
		System.out.println("finish!");
	}
}

比较器
  1. 比较器的实质就是重载比较运算符
  2. 比较器可以很好地应用在特殊标准的排序上
  3. 比较器可以很好地应用在特殊标准排序的结构上
  4. 写代码变得异常容易,还用于范型编程
package com.harrison.four;

import java.util.Arrays;
import java.util.Comparator;

//比较器
//任何比较器:
// compare方法里,遵循一个统一的规范:
// 返回负数的时候,认为第一个参数应该排在前面
// 返回正数的时候,认为第二个参数应该排在前面
// 返回0的时候,认为无所谓谁放前面
public class Code01_Comparator {
    
    

	public static class Student {
    
    
		public String name;
		public int id;
		public int age;

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

	public static class IdAscdingComparator implements Comparator<Student> {
    
    
		// 返回负数的时候,第一个参数排在前面
		// 返回正数的时候,第二个参数排在前面
		// 返回0的时候,谁在前面无所谓
		@Override
		public int compare(Student o1, Student o2) {
    
    
			return o1.id - o2.id;
		}
	}

	public static void printStudents(Student[] students) {
    
    
		for (Student student : students) {
    
    
			System.out.println("name:" + student.name + " id:" + student.id + " age:" + student.age);
		}
//		for (int i = 0; i < students.length; i++) {
    
    
//			Student s = students[i];
//			System.out.println(s.name + "," + s.id + "," + s.age);
//		}
	}

	public static class IdShengAgeJiangOrder implements Comparator<Student> {
    
    
		// 根据id从小到大,但是如果id一样,按照年龄从大到小
		@Override
		public int compare(Student o1, Student o2) {
    
    
			return o1.id != o2.id ? (o1.id - o2.id) : (o2.age - o1.age);
		}
	}

	public static class IdDescendingComparator implements Comparator<Student> {
    
    

		@Override
		public int compare(Student o1, Student o2) {
    
    
			return o2.id - o1.id;
		}
	}

	// 先按照id排序,id小的,放前面;
	// id一样,age大的,前面;
	public static class IdInAgeDe implements Comparator<Student> {
    
    

		@Override
		public int compare(Student o1, Student o2) {
    
    
			return o1.id != o2.id ? o1.id - o2.id : (o2.age - o1.age);
		}
	}

	public static void printArray(Integer[] arr) {
    
    
		if (arr == null) {
    
    
			return;
		}
		for (int i = 0; i < arr.length; i++) {
    
    
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}
	
	

	public static void main(String[] args) {
    
    
		Student student1 = new Student("A", 2, 20);
		Student student2 = new Student("B", 3, 21);
		Student student3 = new Student("C", 1, 22);
		Student[] students = new Student[] {
    
     student1, student2, student3 };
		System.out.println("第一条打印");
		Arrays.sort(students, new IdAscdingComparator());
		printStudents(students);
	}

}

猜你喜欢

转载自blog.csdn.net/weixin_44337241/article/details/120893936