【恋上数据结构】二叉堆学习笔记

news2024/11/29 0:37:17

二叉堆

需求分析

在这里插入图片描述

Top K 问题

什么是 Top K 问题?

从海量数据中找出前 K 个数据。

  • 比如:从 100 万个整数中找出最大的 100 个整数
  • Top K 问题的解法之一:可以用数据结构 “” 来解决。

堆是一种【完全二叉树】,可以分为【最大堆】和【最小堆】。只要是堆,里面的元素就会具备可比较性。

  • 在最大堆中,父节点的值大于等于(>=)其子节点的值;
  • 在最小堆中,父节点的值小于等于(<=)其子节点的值。

在这里插入图片描述

堆的基本接口设计

public interface Heap<E> {
	int size();	// 元素的数量
	boolean isEmpty();	// 是否为空
	void clear();	// 清空
	void add(E element);	 // 添加元素
	E get();	// 获得堆顶元素
	E remove(); // 删除堆顶元素
	E replace(E element); // 删除堆顶元素的同时插入一个新元素
}

二叉堆(Binary Heap)

着重注意索引的规律

在这里插入图片描述

floor(向下取整):只取前面的整数。

最大堆

添加

思路

一步步往上与父节点比较,并进行位置交换。

在这里插入图片描述

交换位置的优化

一般交换位置需要 3 行代码,可以进一步优化

  • 将新添加节点备份,确定最终位置才摆放上去
  • 循环比较,交换父节点位置 -> 循环比较,单纯父节点下移,最后确定位置了直接覆盖
  • 省去了每次都交换位置并且覆盖的操作
实现
	@Override
	public void add(E element) {
		elementNotNullCheck(element);
		ensureCapacity(size + 1);
		elements[size++] = element;
		siftUp(size - 1);
	}

	private void elementNotNullCheck(E element) {
		if (element == null) {
			throw new IllegalArgumentException("element must not be null");
		}
	}

	private void ensureCapacity(int capacity) {
		int oldCapacity = elements.length;
		if (oldCapacity >= capacity) {
			return;
		}
		
		// 新容量为旧容量的1.5倍
		int newCapacity = oldCapacity + (oldCapacity >> 1);
		E[] newElements = (E[]) new Object[newCapacity];
		for (int i = 0; i < size; i++) {
			newElements[i] = elements[i];
		}
		elements = newElements;
	}

	/**
	 * 让index位置的元素上滤
	 * @param index
	 */
	private void siftUp(int index) {
//		E e = elements[index];
//		while (index > 0) {
//			int pindex = (index - 1) >> 1;
//			E p = elements[pindex];
//			if (compare(e, p) <= 0) return;
//			
//			// 交换index、pindex位置的内容
//			E tmp = elements[index];
//			elements[index] = elements[pindex];
//			elements[pindex] = tmp;
//			
//			// 重新赋值index
//			index = pindex;
//		}
		E element = elements[index];
		while (index > 0) {
			int parentIndex = (index - 1) >> 1;
			E parent = elements[parentIndex];
			if (compare(element, parent) <= 0) {
				break;
			}
			
			// 将父元素存储在index位置
			elements[index] = parent;
			
			// 重新赋值index
			index = parentIndex;
		}
		elements[index] = element;
	}

删除

思路

一般来说,如果我们要删除某个元素的话,我们通常会拿到最后一个元素先覆盖它的位置,然后再把最后一个元素删掉,相当于同学们直接将 43 的值覆盖掉 0 这个位置的值,要再把这个值清空。

为什么?

因为这个操作是 O(1) 级别的,删除最后一个元素。

具体流程如下图所示:
在这里插入图片描述

流程图解

在这里插入图片描述

实现
	@Override
	public E remove() {
		emptyCheck();
		
		int lastIndex = --size;
		E root = elements[0];
		elements[0] = elements[lastIndex];
		elements[lastIndex] = null;
		
		siftDown(0);
		return root;
	}

	/**
	 * 让index位置的元素下滤
	 * @param index
	 */
	private void siftDown(int index) {
		E element = elements[index];
		int half = size >> 1;
		// 第一个叶子节点的索引 == 非叶子节点的数量
		// index < 第一个叶子节点的索引
		// 必须保证index位置是非叶子节点
		while (index < half) { 
			// index的节点有2种情况
			// 1.只有左子节点
			// 2.同时有左右子节点
			
			// 默认为左子节点跟它进行比较
			int childIndex = (index << 1) + 1;
			E child = elements[childIndex];
			
			// 右子节点
			int rightIndex = childIndex + 1;
			
			// 选出左右子节点最大的那个
			if (rightIndex < size && compare(elements[rightIndex], child) > 0) {
				child = elements[childIndex = rightIndex];
			}
			
			if (compare(element, child) >= 0) {
				break;
			}

			// 将子节点存放到index位置
			elements[index] = child;
			// 重新设置index
			index = childIndex;
		}
		elements[index] = element;
	}

replace

接口:删除堆顶元素的同时插入一个新元素

	@Override
	public E replace(E element) {
		elementNotNullCheck(element);
		
		E root = null;
		if (size == 0) {
			elements[0] = element;
			size++;
		} else {
			root = elements[0];
			elements[0] = element;
			siftDown(0);
		}
		return root;
	}

批量建堆

批量建堆,有 2 种做法

  1. 自上而下的上滤 – 本质是添加
  2. 自下而上的下滤 – 本质是删除

注意:【自上而下的滤】和【自下而上的滤】不可以批量建堆,因为执行起来对整体来说没有什么贡献,依然还是乱的。

自上而下的上滤

在这里插入图片描述

自下而上的下滤

在这里插入图片描述

效率对比
  • 如下图所示,显然是【自下而上的下滤】效率更高。
  • 可把图中蓝色部分看作是节点数量,箭头直线看作是工作量。
  • 最下层的节点最多,这一部分在【自下而上的下滤】中的工作量较小。

在这里插入图片描述

复杂度计算

深度之和 vs 高度之和

在这里插入图片描述

公式推导

在这里插入图片描述

实现

1、修改构造函数

	public BinaryHeap(E[] elements, Comparator<E> comparator)  {
		super(comparator);
		
		if (elements == null || elements.length == 0) {
			this.elements = (E[]) new Object[DEFAULT_CAPACITY];
		} else {
			size = elements.length;
       // this.elements = elements // 不能这么写,因为不安全
			int capacity = Math.max(elements.length, DEFAULT_CAPACITY);
			this.elements = (E[]) new Object[capacity];
			for (int i = 0; i < elements.length; i++) {
				this.elements[i] = elements[i];
			}
      // 批量建堆
			heapify();
		}
	}

解释:

this.elements = elements 会导致外部传进来的数组和堆内的数组挂钩,如果后续修改了外包数组的元素值,会影响批量建堆的输出。

2、批量建堆方法编写

	/**
	 * 批量建堆
	 */
	private void heapify() {
		// 自上而下的上滤
//		for (int i = 1; i < size; i++) {
//			siftUp(i);
//		}
		
		// 自下而上的下滤
		for (int i = (size >> 1) - 1; i >= 0; i--) {
			siftDown(i);
		}
	}

完整代码

/**
 * 二叉堆(最大堆)
 */
@SuppressWarnings("unchecked")
public class BinaryHeap<E> extends AbstractHeap<E> implements BinaryTreeInfo {
	private E[] elements;
	private static final int DEFAULT_CAPACITY = 10;
	
	public BinaryHeap(E[] elements, Comparator<E> comparator)  {
		super(comparator);
		
		if (elements == null || elements.length == 0) {
			this.elements = (E[]) new Object[DEFAULT_CAPACITY];
		} else {
			size = elements.length;
			int capacity = Math.max(elements.length, DEFAULT_CAPACITY);
			this.elements = (E[]) new Object[capacity];
			for (int i = 0; i < elements.length; i++) {
				this.elements[i] = elements[i];
			}
			heapify();
		}
	}
	
	public BinaryHeap(E[] elements)  {
		this(elements, null);
	}
	
	public BinaryHeap(Comparator<E> comparator) {
		this(null, comparator);
	}
	
	public BinaryHeap() {
		this(null, null);
	}

	@Override
	public void clear() {
		for (int i = 0; i < size; i++) {
			elements[i] = null;
		}
		size = 0;
	}

	@Override
	public void add(E element) {
		elementNotNullCheck(element);
		ensureCapacity(size + 1);
		elements[size++] = element;
		siftUp(size - 1);
	}

	@Override
	public E get() {
		emptyCheck();
		return elements[0];
	}

	@Override
	public E remove() {
		emptyCheck();
		
		int lastIndex = --size;
		E root = elements[0];
		elements[0] = elements[lastIndex];
		elements[lastIndex] = null;
		
		siftDown(0);
		return root;
	}

	@Override
	public E replace(E element) {
		elementNotNullCheck(element);
		
		E root = null;
		if (size == 0) {
			elements[0] = element;
			size++;
		} else {
			root = elements[0];
			elements[0] = element;
			siftDown(0);
		}
		return root;
	}
	
	/**
	 * 批量建堆
	 */
	private void heapify() {
		// 自上而下的上滤
//		for (int i = 1; i < size; i++) {
//			siftUp(i);
//		}
		
		// 自下而上的下滤
		for (int i = (size >> 1) - 1; i >= 0; i--) {
			siftDown(i);
		}
	}
	
	/**
	 * 让index位置的元素下滤
	 * @param index
	 */
	private void siftDown(int index) {
		E element = elements[index];
		int half = size >> 1;
		// 第一个叶子节点的索引 == 非叶子节点的数量
		// index < 第一个叶子节点的索引
		// 必须保证index位置是非叶子节点
		while (index < half) { 
			// index的节点有2种情况
			// 1.只有左子节点
			// 2.同时有左右子节点
			
			// 默认为左子节点跟它进行比较
			int childIndex = (index << 1) + 1;
			E child = elements[childIndex];
			
			// 右子节点
			int rightIndex = childIndex + 1;
			
			// 选出左右子节点最大的那个
			if (rightIndex < size && compare(elements[rightIndex], child) > 0) {
				child = elements[childIndex = rightIndex];
			}
			
			if (compare(element, child) >= 0) {
				break;
			}

			// 将子节点存放到index位置
			elements[index] = child;
			// 重新设置index
			index = childIndex;
		}
		elements[index] = element;
	}
	
	/**
	 * 让index位置的元素上滤
	 * @param index
	 */
	private void siftUp(int index) {
//		E e = elements[index];
//		while (index > 0) {
//			int pindex = (index - 1) >> 1;
//			E p = elements[pindex];
//			if (compare(e, p) <= 0) return;
//			
//			// 交换index、pindex位置的内容
//			E tmp = elements[index];
//			elements[index] = elements[pindex];
//			elements[pindex] = tmp;
//			
//			// 重新赋值index
//			index = pindex;
//		}
		E element = elements[index];
		while (index > 0) {
			int parentIndex = (index - 1) >> 1;
			E parent = elements[parentIndex];
			if (compare(element, parent) <= 0) {
				break;
			}
			
			// 将父元素存储在index位置
			elements[index] = parent;
			
			// 重新赋值index
			index = parentIndex;
		}
		elements[index] = element;
	}
	
	private void ensureCapacity(int capacity) {
		int oldCapacity = elements.length;
		if (oldCapacity >= capacity) {
			return;
		}
		
		// 新容量为旧容量的1.5倍
		int newCapacity = oldCapacity + (oldCapacity >> 1);
		E[] newElements = (E[]) new Object[newCapacity];
		for (int i = 0; i < size; i++) {
			newElements[i] = elements[i];
		}
		elements = newElements;
	}
	
	private void emptyCheck() {
		if (size == 0) {
			throw new IndexOutOfBoundsException("Heap is empty");
		}
	}
	
	private void elementNotNullCheck(E element) {
		if (element == null) {
			throw new IllegalArgumentException("element must not be null");
		}
	}

	@Override
	public Object root() {
		return 0;
	}

	@Override
	public Object left(Object node) {
		int index = ((int)node << 1) + 1;
		return index >= size ? null : index;
	}

	@Override
	public Object right(Object node) {
		int index = ((int)node << 1) + 2;
		return index >= size ? null : index;
	}

	@Override
	public Object string(Object node) {
		return elements[(int)node];
	}
}

最小堆

同样使用最大堆的代码,只需要设置一个倒序比较器即可,将小的数认为比较大放在数组前面。

代码如下:

	static void test3() {
		Integer[] data = {88, 44, 53, 41, 16, 6, 70, 18, 85, 98, 81, 23, 36, 43, 37};
		BinaryHeap<Integer> heap = new BinaryHeap<>(data, new Comparator<Integer>() {
			@Override
			public int compare(Integer o1, Integer o2) {
        // 将原本【最大堆】中较小的值放前面,就实现了【最小堆】
				return o2 - o1;
			}
		});
		BinaryTrees.println(heap);
	}

	public static void main(String[] args) {
		test3();
	}

比较器解析

无论是 o1 - o2 还是 o2 - o1

  1. 只要返回正整数,就表示 o1 应该在 o2 的右边。
  2. 而返回负整数则表示 o1 应该在 o2 的左边。

示例说明:

1、向数组中加入 20,Integer[] data = {10, 20}

o1 - o2 = -10 – 10, 20

o2 - o1 = 10 – 20, 10

2、再向数组中加入 30,Integer[] data ={10, 20, 30}

o1 - o2

  • 10 - 30 = -20 – 10, 30, 20
  • 20 - 30 = -10 – 【10, 20, 30】

o2 - o1

  • 30 - 10 = 20 – 30, 10
  • 20 - 10 = 10 – 【30, 20, 10】

总结

无论是升序还是降序,只要返回正整数,就表示第一个元素应该在第二个元素的右边。

Top K 问题

问题分析

题目:从 n 个整数中,找出最大的前 k 个数 (k 远远小于 n)

  1. 如果使用【排序算法】进行全排序,需要 O(nlogn) 的时间复杂度。

  2. 如果使用【二叉堆】来解决,可以使用 O(nlogk) 的时间复杂度来解决

    • 新建一个小顶堆
    • 扫描 n 个整数

具体细节:

  1. 先将遍历到的前 k 个数放入堆中;
  2. 从第 k + 1 个数开始,如果大于堆顶元素,就使用 replace 操作(删除堆顶元素,将第 k + 1 个数添加到堆中)

代码实现

	static void test4() {
		// 新建一个小顶堆
		BinaryHeap<Integer> heap = new BinaryHeap<>(new Comparator<Integer>() {
			@Override
			public int compare(Integer o1, Integer o2) {
				return o2 - o1;
			}
		});
		
		// 找出最大的前k个数
		int k = 3;
		Integer[] data = {51, 30, 39, 92, 74, 25, 16, 93, 
				91, 19, 54, 47, 73, 62, 76, 63, 35, 18, 
				90, 6, 65, 49, 3, 26, 61, 21, 48};
		for (int i = 0; i < data.length; i++) {
			if (heap.size() < k) { // 前k个数添加到小顶堆
				heap.add(data[i]); // logk
			} else if (data[i] > heap.get()) { // 如果是第k + 1个数,并且大于堆顶元素
				heap.replace(data[i]); // logk
			}
		}
		// O(nlogk)
		BinaryTrees.println(heap);
	}

	public static void main(String[] args) {
		test4();
	}

内部方法分析

1、heap.get() 获取堆顶元素

	@Override
	public E get() {
		emptyCheck();
		return elements[0];
	}

2、heap.replace(data[i]);

删除堆顶元素的同时插入一个新元素,即将大于堆顶的数组元素加进去。

3、这是个最小堆

堆顶元素一直是最小的。

问题 2

如果是找出最小的前 k 个数呢?

  1. 顶堆
  2. 如果小于堆顶元素,就使用 replace 操作

堆排序

概念

堆排序(Heap Sort)是一种基于堆数据结构的排序算法,它利用了堆的特性来进行排序。

堆排序的基本思想如下:

  1. 构建最大堆(或最小堆):将待排序的数组构建成一个最大堆(或最小堆)。
  2. 交换堆顶元素:将堆顶元素与当前未排序部分的最后一个元素交换位置。
  3. 调整堆:对交换后的堆进行调整,使其满足最大堆(或最小堆)的性质。
  4. 重复步骤 2 和 3,直到整个数组排序完成。

代码示例

以下是两个简单的堆排序示例代码:

第一种 – 降序
public class Main2 {
    public static void main(String[] args) {
        Integer[] arr = { 12, 11, 13, 5, 6, 7 };
        BinaryHeap<Integer> heap = new BinaryHeap<>(arr);
        heapSort(heap);
    }

    public static <E> void heapSort(BinaryHeap<E> heap) {
        int size = heap.size();
        for (int i = 0; i < size; i++) {
            // 删除后会再调整堆结构
            E max = heap.remove();
            System.out.print(max + " ");
        }
    }
}

输出结果为:

13 12 11 7 6 5 
第二种 – 升序
public class HeapSort {
    public static void heapSort(Integer[] arr) {
        int n = arr.length;

        // 构建最大堆
        for (int i = n / 2 - 1; i >= 0; i--) {
            heapify(arr, n, i);
        }

        // 交换堆顶元素和未排序部分的最后一个元素,并调整堆
        for (int i = n - 1; i > 0; i--) {
            // 将堆顶元素与当前未排序部分的最后一个元素交换位置
            int temp = arr[0];
            arr[0] = arr[i];
            arr[i] = temp;

            // 调整堆
            heapify(arr, i, 0);
        }
    }

    // 调整堆,使其满足最大堆的性质
    public static void heapify(Integer[] arr, int n, int i) {
        int largest = i; // 初始化堆顶元素为最大值
        int left = 2 * i + 1; // 左子节点的索引
        int right = 2 * i + 2; // 右子节点的索引

        // 判断左子节点是否大于堆顶元素
        if (left < n && arr[left] > arr[largest]) {
            largest = left;
        }

        // 判断右子节点是否大于堆顶元素
        if (right < n && arr[right] > arr[largest]) {
            largest = right;
        }

        // 如果堆顶元素不是最大值,则交换堆顶元素和最大值,并继续调整堆
        if (largest != i) {
            int temp = arr[i];
            arr[i] = arr[largest];
            arr[largest] = temp;

            heapify(arr, n, largest);
        }
    }

    public static void main(String[] args) {
        Integer[] arr = { 12, 11, 13, 5, 6, 7 };
        
        System.out.println("原始数组:");
        for (int num : arr) {
            System.out.print(num + " ");
        }
        System.out.println("\n------------------------");

        BinaryHeap<Integer> heap = new BinaryHeap<>(arr);
        BinaryTrees.println(heap);

        System.out.println("==========================");

        heapSort(arr);

        System.out.println("\n排序后的数组:");
        for (int num : arr) {
            System.out.print(num + " ");
        }
        System.out.println("\n------------------------");

        BinaryHeap<Integer> heap2 = new BinaryHeap<>(arr);
        BinaryTrees.println(heap2);

    }
}

输出结果为:

原始数组:
12 11 13 5 6 7 
------------------------
   ┌──13─┐
   │     │
┌─11─┐ ┌─12
│    │ │
5    6 7
==========================

排序后的数组:
5 6 7 11 12 13 
------------------------
    ┌──13─┐
    │     │
 ┌─12─┐ ┌─7
 │    │ │
11    6 5

注意:

以下这个方法是会对【原数组】的值改变的,heapSort 方法会直接修改原始数组。这意味着在排序之后,原始数组的顺序会被改变。

如果你希望保持原始数组的不变,并在排序后得到一个新的已排序副本,可以使用以下方法:


// 在进行堆排序之前,创建一个原始数组的副本。
Integer[] arr = { 12, 11, 13, 5, 6, 7 };
Integer[] arrCopy = Arrays.copyOf(arr, arr.length);

空间复杂度能否下降至 O(1)?

在当前的实现中,二叉堆的空间复杂度是 O(n),其中 n 是元素的数量。这是因为我们使用一个数组来存储堆的元素。

  • 要将空间复杂度降低到 O(1),我们需要修改数据结构的实现方式

  • 目前的实现方式(BinaryHeap<E>)是使用一个动态数组来存储元素,但这会占用 O(n) 的额外空间。

如果要将空间复杂度降低到 O(1),我们可以考虑使用原始的输入数组来表示堆,而不是创建一个额外的数组。这意味着我们需要在原始数组上进行堆操作,而不是将元素复制到新的数组中。(比如上面写的第二种代码示例)

但是,这样做会对原始数组进行修改,并且在堆操作期间可能会打乱原始数组的顺序。因此,在实际应用中,这种修改可能会有限制,并且需要权衡空间和时间的复杂度。

总结起来,要将空间复杂度降低到 O(1),需要在原始数组上进行操作,但这可能会对原始数组造成修改,并可能会有限制和权衡。具体的实现方式取决于具体的应用场景和需求。

示例代码分析

第一种示例方法复杂度解析

空间复杂度

如果输入的元素个数为 n,且 n 大于 10,那么空间复杂度为 O(n);否则,空间复杂度为 O(1)。

所以最坏空间复杂度为 O(n)

时间复杂度

  • 构建二叉堆的过程具有 O(n) 的时间复杂度,其中 n 是输入数组的长度。
  • 接下来,进行 n 次删除操作,每次删除操作的时间复杂度为 O(logn)。由于删除操作会调整堆的结构,保持最大堆的性质,因此每次删除操作的时间复杂度为O(logn)。
  • 因此,总体上,堆排序的时间复杂度为 O(nlogn)

第二种示例方法复杂度解析

空间复杂度

  • heapSort 方法中,除了输入数组之外,没有使用额外的空间。因此,空间复杂度为 O(1),即常数级别的空间复杂度。

时间复杂度:

  • 构建最大堆的过程具有 O(n) 的时间复杂度,其中 n 是输入数组的长度。

  • 接下来,进行 n-1 次堆调整和交换操作,每次操作的时间复杂度为 O(logn)。

  • 因此,总体上,堆排序的时间复杂度为 O(nlogn)
    *。

  • 目前的实现方式(BinaryHeap<E>)是使用一个动态数组来存储元素,但这会占用 O(n) 的额外空间。

如果要将空间复杂度降低到 O(1),我们可以考虑使用原始的输入数组来表示堆,而不是创建一个额外的数组。这意味着我们需要在原始数组上进行堆操作,而不是将元素复制到新的数组中。(比如上面写的第二种代码示例)

但是,这样做会对原始数组进行修改,并且在堆操作期间可能会打乱原始数组的顺序。因此,在实际应用中,这种修改可能会有限制,并且需要权衡空间和时间的复杂度。

总结起来,要将空间复杂度降低到 O(1),需要在原始数组上进行操作,但这可能会对原始数组造成修改,并可能会有限制和权衡。具体的实现方式取决于具体的应用场景和需求。

示例代码分析

第一种示例方法复杂度解析

空间复杂度

如果输入的元素个数为 n,且 n 大于 10,那么空间复杂度为 O(n);否则,空间复杂度为 O(1)。

所以最坏空间复杂度为 O(n)

时间复杂度

  • 构建二叉堆的过程具有 O(n) 的时间复杂度,其中 n 是输入数组的长度。
  • 接下来,进行 n 次删除操作,每次删除操作的时间复杂度为 O(logn)。由于删除操作会调整堆的结构,保持最大堆的性质,因此每次删除操作的时间复杂度为O(logn)。
  • 因此,总体上,堆排序的时间复杂度为 O(nlogn)

第二种示例方法复杂度解析

空间复杂度

  • heapSort 方法中,除了输入数组之外,没有使用额外的空间。因此,空间复杂度为 O(1),即常数级别的空间复杂度。

时间复杂度:

  • 构建最大堆的过程具有 O(n) 的时间复杂度,其中 n 是输入数组的长度。
  • 接下来,进行 n-1 次堆调整和交换操作,每次操作的时间复杂度为 O(logn)。
  • 因此,总体上,堆排序的时间复杂度为 O(nlogn)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1286376.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hadoop学习笔记(HDP)-Part.07 安装MySQL

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

手动创建spring bean并注入

文章目录 前言一、jar包中,相同class不同类加载器加载的时候是同一个class嘛&#xff1f;二、利用ConfigurableListableBeanFactory手动注册bean注册bean,并自动注入依赖bean根据类型获取注入的bean,两个bean是一个吗? 三、同一份字节码,class隔离,bean隔离总结 前言 注入一个…

Linux的IO模型——阻塞IO

当要读数据recvfrom时&#xff0c;其实就需要两个阶段&#xff0c;一是将硬盘数据读到内核缓冲区&#xff0c;二是将内核缓冲区数据拷贝到用户缓冲区。而阻塞IO就是在两个阶段中用户进程都必须阻塞等待。

ES通过抽样agg聚合性能提升3-5倍

一直以来&#xff0c;es的agg聚合分析性能都比较差&#xff08;对应sql的 group by&#xff09;。特别是在超多数据中做聚合&#xff0c;在搜索的条件命中特别多结果的情况下&#xff0c;聚合分析会非常非常的慢。 一个聚合条件&#xff1a;聚合分析请求的时间 search time a…

网页开发 CSS

目录 CSS 概述 CSS 引入方式 CSS 选择器 基本选择器 组合选择器 伪类选择器 样式继承 选择器优先级 CSS 属性操作 文本属性 背景属性 边框属性 列表属性 dispaly属性 盒子模型&#xff08;重点&#xff09; float属性&#xff08;重点&#xff09; CSS 概述 C…

基于go文件同步工具的升级迭代

介绍 同样&#xff0c;该工具适用于多个项目不同版本的维护&#xff0c;文件更新和新增的同步(自动创建目录)&#xff0c;支持自动提交svn。 升级迭代 之前的文件同步工具&#xff0c;依赖chrome和http包&#xff0c;有时候js加载页面不太稳定&#xff0c;所以有空闲就升级迭…

leetcode115.从中序与后序遍历序列构造二叉树,手把手带你构造二叉树(新手向)

构造二叉树是树问题中的难点&#xff08;相对于遍历二叉树&#xff09;&#xff0c;一开始做的读者会感觉无从下手&#xff0c;这道题在训练营专栏里讲过&#xff0c;是四道题一起讲的&#xff0c;但是现在看来讲的并不全面、具体&#xff0c;所以想单独出一期再来讲一下如何构…

老师怎样克服公开课的心理恐惧?

公开课是老师进修学习和交流教学经验的必要手段&#xff0c;但是&#xff0c;很多老师在面对公开课时会出现心理恐惧&#xff0c;在讲台上发挥不自如&#xff0c;影响教学效果。下面就是一些克服公开课心理恐惧的方法&#xff1a; 一、充分准备 准备充分是心理恐惧的最好解决方…

2023.12.4 关于 Spring Boot 统一异常处理

目录 引言 统一异常处理 异常全部监测 引言 将异常处理逻辑集中到一个地方&#xff0c;可以避免在每个控制器或业务逻辑中都编写相似的异常处理代码&#xff0c;这降低了代码的冗余&#xff0c;提高了代码的可维护性统一的异常处理使得调试和维护变得更加容易&#xff0c;通…

deepflow本地部署过程

本地服务器配置&#xff0c;32C&#xff0c;48G内存 整个过程需要配置k8s&#xff0c;安装helm, 安装grafana, 安装deepflow以及deepflow-ctl&#xff0c;以及部署demo 在采用sealos进行ALL-IN-ONE部署之前&#xff0c; grafana 先安装它 wget -q -O /usr/share/keyrings/gr…

VSCode + gdb + gdbserver调试ARM程序

在开发ARM嵌入式端C/C程序时&#xff0c;一般会在PC上编写代码&#xff0c;在Linux服务器上编译&#xff0c;然后将程序复制或挂载到ARM开发板上运行。如果程序出了问题&#xff0c;在不使用gdb的情况下&#xff0c;经常在代码中添加打印&#xff0c;编译&#xff0c;然后在开发…

nodejs+vue+ElementUi小区社区公寓宿舍智能访客预约系统

该系统将采用B/S结构模式&#xff0c;前端部分主要使用html、css、JavaScript等技术&#xff0c;使用Vue和ElementUI框架搭建前端页面&#xff0c;后端部分将使用Nodejs来搭建服务器&#xff0c;并使用MySQL建立后台数据系统&#xff0c;通过axios完成前后端的交互&#xff0c;…

内网渗透Dump Hash之NTDS.dit

Ntds.dit 在活动⽬录中&#xff0c;所有的数据都保存在域控的ntds.dit⽂件中。 ntds.dit是⼀ 个⼆进制⽂件&#xff0c;⽂件路径为域控的%SystemRoot%\ntds\ntds.dit。NTDS.dit 包 含不限于⽤户名、散列值、组、GPP、OU等活动⽬录的信息。系统运维⼈员可以使⽤VSS实 现对该⽂件…

金蝶云星空单据编辑界面,不允许批量填充操作【分条件】

文章目录 金蝶云星空单据编辑界面&#xff0c;不允许批量填充操作【分条件】前提说明案例演示开发设计测试填充值清空值 金蝶云星空单据编辑界面&#xff0c;不允许批量填充操作【分条件】 前提说明 上一个文章的设计&#xff0c;不管是填充值&#xff0c;还是清空值都一律不…

【matlab程序】matlab画螺旋图|旋转图

%% 数学之美====》螺旋线 % 海洋与大气科学 % 20231205 clear;clc;close all; n=10; t=0:0.01:2pin; R=1; xx=nan(length(t),1);yy=nan(length(t),1); for i=1:length(t) xx(i)=Rcos(t(i)); yy(i)=Rsin(t(i)); R=R+1; end figure set(gcf,‘position’,[50 50 1200 1200],‘col…

基于PIPNet的人脸106关键点检测

做美颜需要使用到人脸关键点&#xff0c;所以整理了一下最近的想法。 按模型结构分类&#xff1a; 1.Top-Down: 分为两个步骤&#xff0c;首先&#xff0c;对于原始输入图片做目标检测&#xff0c;比如做人脸检测&#xff0c;将人脸区域抠出&#xff0c;单独送进关键点检测模…

sql-SQL练习生

推荐一款inscode内的模板SQL练习生&#xff0c;此文附带目前所有题的答案 如有错误欢迎斧正~ https://inscode.csdn.net/TPEngineer/SQLBoy 为了更好的体验&#xff0c;请按下面的方法打开&#xff1a; 1.运行一下 2.等待加载 3.在网页打开 温馨提醒&#xff1a;此处做题不会保…

【交叉编译】

一、什么是交叉编译 二、为什么要交叉编译&#xff1f; 三、交叉编译要用到的工具&#xff08;工具链、交叉编译器&#xff09; 四、交叉编译工具链的安装 五、配置环境变量 六、交叉编译工具编译 七、带wiringPi库的交叉编译如何进行 八、软链接、硬链接 九、Linux创建链接命令…

nodejs+vue+ElementUi牙科诊所信息化系统

该系统将采用B/S结构模式&#xff0c;前端部分主要使用html、css、JavaScript等技术&#xff0c;使用Vue和ElementUI框架搭建前端页面&#xff0c;后端部分将使用Nodejs来搭建服务器&#xff0c;并使用MySQL建立后台数据系统&#xff0c;通过axios完成前后端的交互&#xff0c;…

认识异常 ---java

目录 一. 异常的概念 二. 异常的体系结构 三. 异常的分类 三. 异常的处理 3.1 异常的抛出throw 3.2. 异常声明throws 3.3 捕获并处理try-catch finally 3.4异常的处理流程 四. 自定义异常类 一. 异常的概念 在 Java 中&#xff0c;将程序执行过程中发生的不正常行为称为…