堆排序与栈

123 阅读3分钟

1.比较器

1)比较器的实质就是重载比较运算符

2)比较器可以很好的应用在特殊标准的排序上

3)比较器可以很好的应用在根据特殊标准排序的结构上

4)写代码变得异常容易,还用于范型编程

2.堆结构

(1)完全二叉树:一个树满节点或者在变满的路上(从左到右依次递加),不满的一定在最后一层

相当于数组与二叉树的对应--->>>连续并对应

image.png

(2)当需要找出对应的父节点或者子节点时,使用下面的公式:

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

image.png

(3)堆

  • 大顶堆:最大数在节点最上面,这里增加一个heapsize用于记录堆中存储的元素个数,将其用二叉树的遍历方式画出二叉树的图形
// 新加进来的数,现在停在了index位置,请依次往上移动,
// 移动到0位置,或者干不掉自己的父亲了,停!
private void heapInsert(int[] arr, int index) {
    // [index] [index-1]/2
    // index == 0
    while (arr[index] > arr[(index - 1) / 2]) {
        swap(arr, index, (index - 1) / 2);
        index = (index - 1) / 2;
    }
}
  • 数字下沉:将较大的孩子节点与父节点进行比较,从index位置往下看,不断的下沉。如果较大的孩子都不比index位置的数大,已经没有孩子了。
private void heapify(int[] arr, int index, int heapSize) {
    int left = index * 2 + 1;//左孩子
    while (left < heapSize) {//未越界时
        // 如果有左孩子,有没有右孩子,可能有可能没有!//将较大的孩子的下标 给largest 
        //右孩子:left + 1,如果有右孩子且大于左孩子就返回右孩子值,否则返回左孩子
        int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ?left + 1:left;
        //将较大孩子与父PK,
        largest = arr[largest] > arr[index] ?largest:index;
        if (largest == index) {
            break;
        }
        // index和较大孩子,要互换
        swap (arr,largest,index);
        index = largest;
        left = index * 2 + 1;
    }
}

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

(4)堆时间复杂度:heapify与heapInsert都为O(logN)

image.png

题目: ---无序数组调整为大根堆与小根堆---

思路:

  • 先将整个数组调整为大根堆 (记录大根堆的大小heapSize)--将数组调整为大根堆

image.png

  • 将大根堆最大值6与2交换,同时将heapSize调整为5,重新 将2进行下沉,重新得到一个大根堆。再将heapSize 设为4,将4与0交换,擦去4

image.png

代码实现

//堆排序额外空间复杂度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, 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)
    }
}

// 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 swap(int[] arr, int i, int j) {
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}

时间复杂度分析:O(N*logN)O(N)

// O(N)
for (int i = arr.length - 1; i >= 0; i--) {
    heapify(arr, i, arr.length);
}

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

思路:在有序的位置下对数组进行排序--》》》移动位置不超过k

image.png

这里使用小根堆排序的时候,如何确认是有序数组???

//使用小跟堆:每次都对前一个数大小进行heapify()
//每次弹出的结果即为当前数组的最小数
public static void sortedArrDistanceLessK(int[] arr, int k) {
    if (k k== 0) {
        return;
    }
    //默认小根堆
    PriorityQueue<Integer> heap = new PriorityQueue<>();
    int index = 0;
    // 0...K-1
    for (; index <= Math.min(arr.length - 1, k - 1); index++) {
        heap.add(arr[index]);
    }
    int i = 0;
    for (; index < arr.length; i++, index++) {
        heap.add(arr[index]);
        arr[i] = heap.poll();
    }
    while (!heap.isEmpty()) {
        arr[i++] = heap.poll();
    }
}