算法与数据体系课笔记之-7.加强堆与堆结构相关题目

7.加强堆与堆结构相关题目分析 总览

在这里插入图片描述

笔记思维导图链接

算法与数据结构思维导图

参考左程云算法课程

常见题目汇总:

题1:最大线段重合问题(堆实现)

题目描述:

  • 给定很多线段,每个线段都有两个数组[start, end],

  • 表示线段开始位置和结束位置,左右都是闭区间

  • 规定:
    1)线段的开始和结束位置一定都是整数值
    2)线段重合区域的长度必须>=1

  • 返回线段最多重合区域中,包含了几条线段

题解:

在这里插入图片描述

代码实现:

package class07;

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

public class Code01_CoverMax {
	// 创建line线段类对象,将二维数组对象转为线段对象,方便处理arr[a,b]->line(a,b)
	public static class Line {
		private int start;
		private int end;

		public Line(int start, int end) {
			super();
			this.start = start;
			this.end = end;
		}
	}

	// 方法1:暴力统计算法
	public static int maxCover1(int[][] lines) {
		// 1. 确定线段最小和最大范围,锁定要校验的对象
		int min = Integer.MAX_VALUE;
		int max = Integer.MIN_VALUE;
		for (int i = 0; i < lines.length; i++) {
			min = Math.min(min, lines[i][0]);
			max = Math.max(max, lines[i][1]);
		}
		// 2. 以每个在范围内的数的.5为基点,遍历统计包含该数的所有线段数
		int cover = 0;
		for (double p = min + 0.5; p < max; p += 1) {
			int cur = 0;
			for (int i = 0; i < lines.length; i++) {
				if (lines[i][0] < p && lines[i][1] > p) {
					cur++;
				}
			}
			cover = Math.max(cover, cur);
		}
		return cover;
	}

	// 方法2:使用最小堆结构
	public static int maxCover2(int[][] m) {
		// 1. 将线段按照左边界大小进行排序
		// 先将二维数组转为线段对象,方便比较排序处理
		// 将各二维数组new出线段对象放在一个一维的线段类型数组中
		Line[] lines = new Line[m.length];
		for (int i = 0; i < lines.length; i++) {
			lines[i] = new Line(m[i][0], m[i][1]);
		}
		Arrays.sort(lines, (a, b) -> a.start - b.start);

		// 2. 依次遍历所有左边界,并维护最小堆
		PriorityQueue<Integer> minHeap = new PriorityQueue<>();
		int res = 0;
		for (int i = 0; i < lines.length; i++) {
			// 弹入前,先判断弹出不满足的线段
			// 注意,等于也弹出,等于,说明线段相连,重合部分为0
			while (!minHeap.isEmpty() && lines[i].start >= minHeap.peek())
				minHeap.poll();
			// 弹干净后,再弹入右边界值
			minHeap.add(lines[i].end);
			// 3. 统计每个基点的重合线段数,并比较出最大值
			res = Math.max(res, minHeap.size());
		}
		return res;
	}

	// for test
	public static int[][] generateLines(int N, int L, int R) {
		int size = (int) (Math.random() * N) + 1;
		int[][] ans = new int[size][2];
		for (int i = 0; i < size; i++) {
			int a = L + (int) (Math.random() * (R - L + 1));
			int b = L + (int) (Math.random() * (R - L + 1));
			if (a == b) {
				b = a + 1;
			}
			ans[i][0] = Math.min(a, b);
			ans[i][1] = Math.max(a, b);
		}
		return ans;
	}
	
	public static class StartComparator implements Comparator<Line> {

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

	}

	public static void main(String[] args) {

		Line l1 = new Line(4, 9);
		Line l2 = new Line(1, 4);
		Line l3 = new Line(7, 15);
		Line l4 = new Line(2, 4);
		Line l5 = new Line(4, 6);
		Line l6 = new Line(3, 7);

		// 底层堆结构,heap
		PriorityQueue<Line> heap = new PriorityQueue<>(new StartComparator());
		heap.add(l1);
		heap.add(l2);
		heap.add(l3);
		heap.add(l4);
		heap.add(l5);
		heap.add(l6);

		while (!heap.isEmpty()) {
			Line cur = heap.poll();
			System.out.println(cur.start + "," + cur.end);
		}

		System.out.println("test begin");
		int N = 100;
		int L = 0;
		int R = 200;
		int testTimes = 200000;
		for (int i = 0; i < testTimes; i++) {
			int[][] lines = generateLines(N, L, R);
			int ans1 = maxCover1(lines);
			int ans2 = maxCover2(lines);
			if (ans1 != ans2) {
				System.out.println("Oops!");
			}
		}
		System.out.println("test end");
	}
}

复杂度分析:

  • 1.线段排序为O(NlogN)

  • 2.循环遍历左边界,并维护最小堆,统计比较O(NlogN):

    ​ for循环: 所有线段考察一次O(N)
    ​ 小根堆复杂度: 所有线段结尾位置, 最多进一次, 最多出一次, 一共进出2N
    ​ 小根堆每次调整代价logN(shiftUP,shiftDownd堆化都是logn)

  • 总复杂度O(N*logN)

题2:加强堆设计

要实现的功能:

  • 堆上某个位置元素调整
  • 删除堆上指定位置元素
  • 快速查询到堆上指定元素位置

题解:

在这里插入图片描述

代码实现:

package class07;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;

/**
 * E一定要是非基础类型,有基础类型需要包一层 因为哈希表对于基础类型,key按值存储,不能储存重复的值
 */
public class HeapGreater<E> {
	private ArrayList<E> data; // 堆结构的底层数组结构
	private HashMap<E, Integer> indexMap; // 反向索引表,快速拿到指定元素对应数组的位置索引
//	private int heapSize; // 堆的大小,也可用数组data的size表示,data自己维护了自己size大小,不同于capacity
	private Comparator<? super E> comparator; // 自定义较器,可以有重复值,比较器里将内存地址比较

	public HeapGreater(Comparator<E> c) { // 用户传入比较器
		data = new ArrayList<>();
		indexMap = new HashMap<>();
//		heapSize = 0;
		comparator = c;
	}

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

	public int size() {
		return data.size();
	}

	// 增,同时维护反向索引表
	public void add(E e) {
		data.add(e);// 堆的size在data中已经维护好了
		indexMap.put(e, data.size() - 1); // 注意边界条件
		siftUp(data.size() - 1);
	}

	// 默认比较器小根堆
	private void siftUp(int k) {
		// 往上heapify过程,主要k不能为根节点,
		while (comparator.compare(data.get(k), data.get(parent(k))) < 0) {
			// 注意,不能只交换元素,反向索引表也要更新交换,故自定义swap方法
			swap(k, parent(k));
			k = parent(k);
		}
	}

	private void swap(int i, int j) {
		// 交换数组元素
		E e1 = data.get(i);
		E e2 = data.get(j);
		data.set(i, e2);
		data.set(j, e1);
		// 交换反向索引表的健值对
		indexMap.put(e1, j);
		indexMap.put(e2, i);
	}

	// 查
	public E peek() {
		if (data.isEmpty()) {
			throw new IllegalArgumentException("can't findMax from an empty heap");
		}
		return data.get(0);
	}

	// 删
	public E poll() {
		E res = peek();
		swap(0, data.size() - 1); // 交换
		indexMap.remove(res); // 元素剔除,map中的key也剔除,value在堆化中重新调整
		data.remove(size() - 1); // 数组原结构删除
		siftDown(data, 0, data.size());
		return res;
	}

	private void siftDown(ArrayList<E> data, int k, int size) {
		while (leftChild(k) < size) {
			// 先比较左右子节点,取较小节点的索引
			int j = leftChild(k);
			if(j + 1 < size && comparator.compare(data.get(j + 1), data.get(j)) < 0){
				j ++;
			}
			// 处理父子节点,保证根节点最小
			if (comparator.compare(data.get(j), data.get(k)) < 0) {
				swap(j, k);
			}
			k = j;
		}
	}

	// 加强堆增强方法:快速查询判断堆中指定元素
	public boolean contains(E e) {
		return indexMap.containsKey(e);
	}

	public int getIndex(E e) {
		return indexMap.get(e);
	}

	// 加强堆增强方法:改了某个值后,重写调整出堆原有结构
	public void resign(E e) {
		int k = indexMap.get(e);
		siftUp(k);
		siftDown(data, k, data.size());
	}

	// 加强堆增强方法:快速删除指定元素
	public void remove(E e) {
		E replace = data.get(size() - 1);
		int k = indexMap.get(e);
		indexMap.remove(e);
		data.remove(data.size() - 1); // 注意,删除的是交换后的最后一个元素
		if (!e.equals(replace)) {
			indexMap.put(replace, k); // 简化了交换流程,且,如果删除的是最后一个元素,可以不用交换
			data.set(k, replace);
			resign(replace);
		}
//		E replace = data.get(size() - 1);
//		int k = indexMap.get(e);
//		swap(k, data.size() - 1);
//		indexMap.remove(e);
//		data.remove(data.size() - 1);
//		if (!e.equals(replace)) resign(replace);
	}

	// 请返回堆上的所有元素
	public List<E> getAllElements() {
		List<E> ans = new ArrayList<>();
		for (E e : data) { // 数组实例化后,是一个具体对象,可用E表示
			ans.add(e);
		}
		return ans;
	}

	// 返回完全二叉树的数组表示中,一个索引所表示的父亲节点的索引
	private int parent(int index) { // 根节点的父节点索引也是0
		return (index - 1) / 2;
	}

	// 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引
	private int leftChild(int index) {
		return 2 * index + 1;
	}

}

复杂度分析:

  • 查询为O(1), 删除为O(logn),元素调整为O(logn),均为堆化的复杂度
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值