初级2 题目三 堆排序

1. 想要理解堆排序,首先就要了解完全二叉树,因为堆排序就是在完全二叉树的基础上进行操作的。假设存在一棵完全二叉树(图中没画完整,用省略号替代了该有的结点),有 k+1 层(从第 0 层算起):

其实完全可以不在意二叉树内的编号是多少,编号是从 0 开始还是从 1 开始,亦或者从 2,3 开始,这都不重要。只需记住一点:每层父亲与下一层的左儿子相差多少。以上图最左边为例,其实每层父亲与左儿子的差值就是父亲那层节点的总数。记住这一点,就容易理解多了,假如父亲节点为 i,且父亲那层节点总数为 n,那么左儿子的节点自然就为 i+n。如果节点 i 是在第 0 层,那么完全二叉树的节点通式就可以写成:

编号从 0 开始,左儿子节点都是 2i+1;编号从 1 开始,左儿子节点都是 2i;编号从 2 开始,左儿子节点都是 2i-1。为什么会是这样呢?根据不同的 i 取值,将父亲节点*2,找其与左儿子节点值的关系。(感觉这里应该有个更加强大的通式,而不是因为 i 的不同取值导致的节点关系不同,待定

2. 堆,在逻辑上是完全二叉树,但是物理上其实就是数组。此外,堆排序有两个准备工作,一是插入堆,二是调整堆,最后才能完成堆排序。

插入堆:给定一个大根堆,传入待插入元素的下标,将这个元素插入到已有的大根堆中,形成新的大根堆。

插入过程中最有意思的点,是找到插入元素的父节点。正常情况下,找其父节点肯定要判断 index 是左孩子还是右孩子。在编号从 0 开始的条件下,左孩子的公式是 2*i+1(假设父节点下标为 i),右孩子公式为 2*i+2。反推便是,若孩子下标 index 为左孩子,则父节点为 (index-1)/2;若孩子下标 index 代表右孩子,父节点则为 (index-2)/2。但在实际编程中,这两个可以用一句代码进行代替,即 farther_index = (index-1)/2; 这样做的原因是:如果 index 为左孩子,正好求其父节点;如果 index 是右孩子,那么 index 一定是偶数(2i+2),index-1 便是奇数,(index-1)/2 在计算机中执行时,奇数产生的 .5 会自动被抹去,相当于 (index-2)/2,所以对于右孩子,在计算机中执行 (index-1)/2 就是求父节点。

/*
 * 建立大根堆,是由底向上建立的
 * *array: 大根堆首地址
 * index:插入数的下标,默认插入数先放在了大根堆的最后一位
 */
void HeapInsert(int *array, int index){
	if(index == 0){
		return; // 不用管,因为就它一个结点,一定是大根堆	
	}	
	while(*(array+index) > *(array+(index-1)/2)){  // 儿子大于父亲
		SWAP(*(array+index), *(array+(index-1)/2), int);
		index = (index-1)/2;  // 交换之后儿子变父亲,继续向上对比
	}
}

 看上面代码可以发现,插入堆的过程实际上自底向上的过程。

调整堆:给定一个堆,从传入的 index 节点开始,自顶向下逐渐调整,形成大根堆。注意这里有个隐含的东西,index 节点之前仍旧保持大根堆,index 节点之后大根堆才被破坏。

/*
 * 调整大根堆,是自顶向下调整的
 * *array: 大根堆首地址
 * index:从该节点开始,向下调整大根堆
 * heap_size:大根堆的元素个数
 */
void Heapify(int *array,int index,int heap_size){
	
	while(2*index+1 < heap_size){ // 只要左孩子还在
		int index_max = 2*index+1;
		
		if(2*index+2 < heap_size){ // 如果右孩子在,先比较左右孩子
			index_max = *(array+2*index+1) > *(array+2*index+2)?(2*index+1):(2*index+2);
		}
		
		if(*(array+index) < *(array+index_max)){ // 再把较大的孩子与父亲进行比较
			SWAP(*(array+index), *(array+index_max), int);
			index = index_max;
		}
		else{
			return;  // 如果父节点大,那么后续都不用换了
		}
	}
}

堆排序:堆排序实际上就是在建立大根堆的基础上,将堆顶的元素弹出,然后堆的大小递减,再重新调整堆,形成了一个新的大根堆。之后重复之前的步骤,弹出堆顶元素,直到大根堆只有一个元素为止,这样的一个循环过程。

大根堆的堆顶实质是就是数组的 0 号元素,也是堆中元素的最大值。弹出堆顶元素其实就是堆顶元素和大根堆的末位置元素进行互换。

void HeapSort(int *array, int length){
	
	// 1.先建立大根堆
	for(int i=0;i<length;i++){
		HeapInsert(array, i);
	}
	int heap_size = length;
	
	while(heap_size > 1){
		SWAP(*(array), *(array+heap_size-1), int); // 2. 弹出堆顶元素
		heap_size--; // 弹出之后,堆元素个数更新
		Heapify(array, 0, heap_size);  // 3. 调整堆
	}
}

完整代码:

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define SWAP(a,b,type) do{type temp=a;a=b;b=temp;}while(0)

void Generate(int **array, int length, int upper_bound){
	*array = malloc(sizeof(int)*length);
	if(*array){
		for(int i=0;i<length;i++){
			*(*array+i) = rand() % upper_bound;
		}
	}else{
		printf("Error!");
	}
}

/*
 * 建立大根堆,是由底向上建立的
 * *array: 大根堆首地址
 * index:插入数的下标,默认插入数先放在了大根堆的最后一位
 */
void HeapInsert(int *array, int index){
	while(*(array+index) > *(array+(index-1)/2)){  // 儿子大于父亲
		SWAP(*(array+index), *(array+(index-1)/2), int);
		index = (index-1)/2;  // 交换之后儿子变父亲,继续向上对比
	}
}

/*
 * 调整大根堆,是自顶向下调整的
 * *array: 大根堆首地址
 * index:从该节点开始,向下调整大根堆
 * heap_size:大根堆的元素个数
 */
void Heapify(int *array,int index,int heap_size){
	
	while(2*index+1 < heap_size){ // 只要左孩子还在
		int index_max = 2*index+1;
		
		if(2*index+2 < heap_size){ // 如果右孩子在,先比较左右孩子
			index_max = *(array+2*index+1) > *(array+2*index+2)?(2*index+1):(2*index+2);
		}
		
		if(*(array+index) < *(array+index_max)){ // 再把较大的孩子与父亲进行比较
			SWAP(*(array+index), *(array+index_max), int);
			index = index_max;
		}
		else{
			return;  // 如果父节点大,那么后续都不用换了
		}
	}
}

void HeapSort(int *array, int length){
	
	// 1.先建立大根堆
	for(int i=0;i<length;i++){
		HeapInsert(array, i);
	}
	int heap_size = length;
	
	while(heap_size > 1){
		SWAP(*(array), *(array+heap_size-1), int); // 2. 弹出堆顶元素
		heap_size--; // 弹出之后,堆元素个数更新
		Heapify(array, 0, heap_size);  // 3. 调整堆
	}
}

int main()
{
    srand(time(NULL));
   	
	int *array;
	int length = 10;
	int upper_bound = 10;
	
	Generate(&array, length, upper_bound);
	
	printf("初始数组:\n");
	for(int i=0;i<length;i++){
		printf("%d ", *(array+i));
	}
    
	for(int i=0;i<length;i++){
		HeapInsert(array, i);
	}
	printf("\n大根堆:\n");
	for(int i=0;i<length;i++){
		printf("%d ", *(array+i));
	}
	
	printf("\n弹出最大值后的大根堆:\n");
	SWAP(*(array), *(array+length-1), int);
	for(int i=0;i<length;i++){
		printf("%d ", *(array+i));
	}
	
	printf("\n重新调整后的大根堆:\n");
	Heapify(array, 0, 9);
	for(int i=0;i<length;i++){
		printf("%d ", *(array+i));
	}
	
	HeapSort(array, length);
	printf("\n堆排序之后的数组:\n");
	for(int i=0;i<length;i++){
		printf("%d ", *(array+i));
	}
	
	free(array);
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值