(排序四)堆排序详解

1)完全二叉树;父节点i,子节点对应2*i+1,2*i+2

在一个数组中,可以脑补一个完全二叉树

《(排序四)堆排序详解》

2)堆:

1.大根堆:在一个完全二叉树中,任何一个字树的最大值都是这颗子树的头部(根节点永远是所在子树中最大的)

2.建立大根堆:给你一个数组,建立大根堆
遍历数组,每遍历一个元素a[i]保证0-i范围上是大根堆,在遍历到i+1时,保证0-i+1范围上是大根堆。(完全二叉树上每增加一个元素进行调整,保证形成大根堆)直到for循环完毕,数组遍历完毕

2.调整过程(上浮):构建堆 

进一个数a[i],跟父位置比较,如果大于父位置,交换,然后在新的位置上继续比较,如果大于新位置上的父位置,交换,直到小于当前父位置停止。
建立一个大根堆的复杂度:每加进一个数,最多swap(logn)次—(当前形成的二叉树高度为logn)所以数组中所有数字加进来复杂度是O(N*logN)

《(排序四)堆排序详解》

第一种方式构建堆代码_上浮代码:

for (int i = 0; i < arr.length; i++) {
			heapInsert(arr, i);
		}
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;
		}
	}

   优化:下沉构建堆

存在构建堆的过程为O(N)的方法(默认为此数组已经是一个大根堆,从倒数第二排最右边的数开始检验,是否符合大根堆,不符合的话,进行下沉操作)依次按排检验,直到堆顶检验完毕,此构建堆的过程是O(n)   

证明:每一层开始检验(1*n/4+2*n/8+3*n/16+…+(log2N – 1)*n/(2^ log2N)

 

 

《(排序四)堆排序详解》

时间复杂度为O(N)

注意:

第一种入栈,每进入一个数都可能需要上浮log2i次,累计上浮最多次数(2*1+4*2+8*3+16*4+…+n/2*(log2N-1))当二叉树层数越多的时候,越多的叶子节点个数乘以层数。O(当前层节点个数*距顶距离)求和

第二种入栈:(下沉)从底部开始,向上遍历,O(当前层节点个数*距底部距离)求和 ,遍历过程中,离底部变远,但是子节点数量减少

《(排序四)堆排序详解》

 

调整过程(下沉)

3.下沉

当此大根堆中有一个数字变小了,怎么恢复大根堆呢:
这个变小的数字的位置中,找他的两个孩子,左右孩子进行比计较,找到最大值然后与他比较,子节点>根节点时,与相应子节点交换(下沉一个位置),在新的位置继续比较,根节点>两个字节点。

public static void heapify(int[] arr, int index, int size) {
		int left = index * 2 + 1;
		while (left < size) {//左孩子没有越界(如果左孩子越界,那么index位置已经是叶节点不许要下沉)
			int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
			largest = arr[largest] > arr[index] ? largest : index;
			if (largest == index) {
				break;
			}
			swap(arr, largest, index);
			index = largest;
			left = index * 2 + 1;
		}
	}

注意:堆的大小在数组中是可以伸缩的,和heapsize表示堆的大小(在下沉时,注意不要访问到没入堆的数字,(没入堆的数字也一样在数组中,此时堆之形成到了i,i后面的元素仍在数组中,只是没有还调整成大根堆)

4.减堆操作:弹出堆顶再变成大根堆

堆得最后一个数与堆顶进行交换,heapSize–,然后新的堆顶进行下沉操作

堆排序:

先遍历数组,使整个数组变成大根堆,然后依次进行减堆操作,(弹出堆顶,重调成大根堆)时间复杂度:O(NlogN)重建堆的过程。额外空间复杂度O(1)

public static void heapSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		for (int i = 0; i < arr.length; i++) {
			heapInsert(arr, i);
		}
		int size = arr.length;
		swap(arr, 0, --size);
		while (size > 0) {
			heapify(arr, 0, size);
			swap(arr, 0, --size);
		}
	}
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;
		}
	}
public static void heapify(int[] arr, int index, int size) {
		int left = index * 2 + 1;
		while (left < size) {//左孩子没有越界(如果左孩子越界,那么index位置已经是叶节点不许要下沉)
			int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
			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;
	}

 

 

 

     

    原文作者:算法
    原文地址: https://www.twblogs.net/a/5bdde71c2b717720b51b0313
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞