Bootstrap

堆排序的前提,建堆

堆排序就是借助堆的性质来实现排序,通过建堆,然后调整可以实现对需排序数组排序。下面来具体解释:

1:堆是完全二叉树,分为大根堆和小根堆,大根堆即是每个父结点大于等于左右子结点,小根堆即是每个父结点小于等子结点,下面两种图分别是一个大根堆和一个小根堆:

这里的70相对于56和30就是父结点,而56和30就是70的左右子结点。

2:还需要知道的是,完全二叉树中,若知父结点位置为i,则左右子结点为2i+1和2i+2,若已知子结点为i,则父结点为(i-1)/2,

例如此处0,1,2,3,4,5,6代表其在数组中的下标,对于5和6来说,2是其父结点,(5-1)/2和(6-1)/2都是2,有的地方写的是i/2-1,如果这样的话5/2-1就是1了,所以应该是(i-1)/2。

3:向下调整算法,这个算法的前提是要调整的根结点的左右子树必须是堆,所以要先建堆。例如:

以27为根结点,左右两个红框分别是其左右子树,并且观察可知,左右子树都是小根堆,那么就可以利用向下调整算法来实现对根结点的调整,使得变成小根堆。具体流程如下:

1)找到左右子结点小的那一个结点;

2)比较该子结点与父结点,如果子结点小,则与父结点交换;

3)交换完再更新父结点的位置,继续向下调整。

最后一个与父结点比较之后,可以看出父结点比子结点小,则不需要进行交换。根据上面步骤完成后,可以得到最后变成了小根堆。需要注意的是,上面过程只是一个例子,如果要把输入的数组建堆,由于向下调整算法根结点的左右子树必须是堆,就必须要从最后一个父结点开始,都要进行向下调整,使得其本身是堆,然后再做为父结点的子树,这样父结点的左右子树是堆,才能进行向下调整。

口语化表述就是:

以这颗树为例,想要调整为小根堆,对27这个结点调整就得保证以15和19为根的树是小根堆堆,同理,想要把15和19为根的树变成小根堆,就得保证15的左右子树(即以18和28为根的树)和19的左右子树(即以34和65为根的树)为小根堆,同理想要18、28、34和65的左右子树为小根堆,就得对其左右子树进行调整,直到叶子结点(左右子结点为空)为止,因为叶子结点就一个树,可以认为其是堆。

通过上述过程,即可建出一个小堆,建大堆方法类似,下面是建堆的参考代码:

void AdjustDown(int* a, int k, int parent)
{
	int child = 2 * parent + 1;

	while (child < k)
	{
		//找左右孩子小的那一个
		if (child + 1 < k && a[child + 1] < a[child])
			child++;
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}
int main()
{
    int arr[10] = {3,1,5,23,64,7,45,37,9,23};
    int k = sizeof(arr)/sizeof(arr[0]);
    int i = 0;
    for (i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(arr,k,i);
	}
}

上述代码注意的是:找左右子结点小的那个,我是先让左为小,然后去和右比较,右如果小的话,则对child++。循环停止条件为child>k,当父结点更新到叶子结点时,左右子结点就会大于边界,即数组个数k。所以小于k时继续调整。

这是进行堆排序的前提,后面还会实现堆排序,以及TopK问题的解决。

;