堆排序算法

烈酒焚心 提交于 2020-02-16 11:41:37

算法原理

堆排序定义:

  n 个 序列 Al, A2,…, An 称为 堆, 有 下面 两种 不同 类型 的 堆。

  小 根 堆: 所有 子 结点 都 大于 其父 节点, 即 Ai ≤ A2i 且 Ai ≤ A2i+ 1。

  大 根 堆: 所有 子 结点 都 小于 其父 节点, 即 Ai ≥ A2i 且 Ai ≥ A2i+ 1。

(数组是从0开始计算的,为了平衡考虑,使用 A2i + 1 和 A2i + 2)

  若将 此 序列 所 存储 的 向量 A[ 1... n] 看 为一 棵 完全 二 叉 树 的 存储 结构, 则 堆 实质上 是 满足 如下 性质 的 完全 二 叉 树: 树 中 任一 非 叶 结点 的 关键字 均不 大于( 或不 小于) 其 左、 右 子 节点( 若 存在) 的 关键字。

  因此 堆 排序( HeapSort) 是 树 形 选择 排序。 在 排序 过程中, 将 R[ l... n] 看成 一 棵 完全 二 叉 树 的 顺序 存储 结构, 利用 完全 二 叉 树 中 双亲 结点 和 孩子 结点 之间 的 内在 关系, 在当 前 无序 区 中 选择 关键字 最大( 或 最小) 的 记录。

  用 大 根 堆 排序 的 基本 思想:

(1) 先 将 初始 A[ 1... n] 建成 一 个大 根 堆, 此 堆 为 初始 的 无序 区。

(2) 再将 关键字 最大 的 记录 A[ 1]( 堆 顶) 和 无序 区 的 最后 一个 记录 A[ n] 交换, 由此 得到 新的 无序 区 A[ 1... n- 1] 和 有序 区 A[ n], 且 满足 A[ 1... n- 1] ≤ A[ n]。

(3) 由于 交换 后 新的 根 A[ 1] 可能 违反 堆 性质, 故 应将 当前 无序 区 A[ 1... n- 1] 调整 为 堆。 然后 再次 将 A[ 1... n-1] 中 关键字 最大 的 记录 A[ 1] 和 该区 间的 最后 一个 记录 A[ n- 1] 交换, 由此 得到 新的 无序 区 A[ 1... n- 2] 和 有序 区 A[ n- 1... n], 且 仍 满足 关系 A[ 1... n- 2] ≤ A[ n- 1... n], 同样 要将 A[ 1... n- 2] 调整 为 堆。

(4) 对 调整 的 堆 重复 进行 上面 的 交换, 直到 无序 区 只有 一个 元素 为止。

  构造 初始 堆 必须 用到 调整 堆 的 操作, 现在 说明 Heapify 函数 思想方法。

  每 趟 排序 开始 前, A[ l… i] 是以 A[ 1] 为 根 的 堆, 在 A[ 1] 与 A[ i] 交换 后, 新的 无序 区 A[ 1… i- 1] 中 只有 A[ 1] 的 值 发生了 变化, 故 除 A[ 1] 可能 违反 堆 性质 外, 其余 任何 结点 为 根 的 子 树 均 是 堆。 因此, 当 被 调整 区间 是 A[ low… high] 时, 只须 调整 以 A[ low] 为 根 的 树 即可。

  可以 使用“ 筛选 法” 进行 堆 的 调整。 A[ low] 的 左、 右 子 树( 若 存在) 均 已是 堆, 这 两 棵 子 树 的 根 A[ 2low] 和 A[ 2low+ 1] 分别 是 各自 子 树 中 关键字 最大 的 节点。 若 A[ low] 不小于 这 两个 孩子 节点 的 关键字, 则 A[ low] 未 违反 堆 性质, 以 A[ low] 为 根 的 树 已是 堆, 无须 调整; 否则 必须 将 A[ low] 和 它的 两个 孩子 节点 中 关键字 较大 者 进行 交换, 即 A[ low] 与 A[ large] (A[ large]= max( A[ 2low], A[ 2low+ 1])) 交换。 交换 后又 可能 使节 点 A[ large] 违反 堆 性质。 同样, 由于 该 节点 的 两 棵 子 树( 若 存在) 仍然是 堆, 故 可 重复 上述 调整 过程, 对 以 A[ large] 为 根 的 树 进行 调整。 此 过程 直至 当前 被 调整 的 节点 已 满足 堆 性质, 或者 该 节点 已是 叶子 为止。 上述 过程 就 像 过筛 子 一样, 把 较小 的 关键字 逐 层 筛 下去, 而将 较大 的 关键字 逐 层 选上 来。

算法草稿

 

代码实现

#include <stdio.h>
#include <stdlib.h>


#define SUCCESS		0
#define PARAM_ERR	-1

int BuildMaxHeap(int * array, int low, int high){
	if(NULL == array){
		printf("%s para error", __func__);
		return PARAM_ERR;
	}

//	printf("Enter BuildMaxHeap low = %d, high = %d\n", low, high);
	
	int left = 0, right = 0; 	/*子树的索引*/
	int max = low; 				/*最大子节点的索引,默认[low]是堆的根 */
	int temp = 0;

	/* 
	 * low 是根堆,默认[low] 是最大的点做在的位置
	 * 不用 2 * low 和 2 * low + 1, 是因为对于0为根的对来说,子树就都在右边了,树就不平生了
	 * left = 2 * low;
	 * right = 2 * low + 1;	 
	 * 当然,这么用也没什么错误,就是树不太平衡,像个瘸子 ;-D
	 */
	left = 2 * low + 1;
	right = 2 * low + 2;

	/*
	 * 有左子节点 且 [low] 不是最大节点,max取得左子树
	 * 注意比较前,需要保证左子节点存在:left <= high
	 */
	if((left <= high) && (array[left] > array[low])){
		max = left;
	} else {
		max = low;
	}

	/*
	 * 有右子节点 且 [low] 不是最大节点,max取得左子树
	 * 注意比较前,需要保证左子节点存在:right <= high
	 */
	if((right <= high) && (array[right] > array[max])){
		max = right;
	}

	if(max != low){
		temp = array[max];
		array[max] = array[low];
		array[low] = temp;

		/* 对交换的子树进行重新建堆*/
		BuildMaxHeap(array, max, high);		
	}

//	printf("Left BuildMaxHeap low = %d, high = %d\n", low, high);
	
	return SUCCESS;

}

int initMaxHeap(int * array, int size){
	if(NULL == array){
		printf("%s para error", __func__);
		return PARAM_ERR;
	}

//	printf("Enter initMaxHeap\n");
	
	int i = 0;
#ifdef DEBUG
	int k = 0;
#endif	

	/*
	 * 只是到 size /2 而不是 size,因为后半部分通过 2*i +1  和  2 * i + 2 子树方式完成了递归的构建
	 * 前半部分在初始化的时候,要逐个建堆
	 * for(i = size - 1; i >= 0; i--) 整个建堆也没有错,就是效率会差一些,随着数组越大,效率越差
	 * 注意要包含 0 和 size / 2 , 是个闭区间,不然少一个初次的建堆
	 * 还有一点非常重要,需要从后向前初始化,相当于需要先初始化好子树,再初始化父一级的树,这样才可以
	 * 反过来的初始化顺序是错误的,父一级错误,再初始化子一级也错误,二者就更加错误了
     */
	for(i = size / 2; i >= 0; i--){
		BuildMaxHeap(array, i, size - 1);
	}
	
#ifdef DEBUG		
	printf("%s: size = %d\n", __func__, size);				
	printf("init Heap [");
	for(k =0; k < size; k++){
		printf("  %d  ", array[k]);
	}
	printf("]  \n");
	printf("\n");
#endif


//	printf("Left initMaxHeap\n");
	
	return SUCCESS;
}

int HeapSort(int * array, int size){
	if(NULL == array){
		printf("%s para error", __func__);
		return PARAM_ERR;
	}

	int i = 0, j = 0;
	int temp = 0;
#ifdef DEBUG
		int k = 0;
#endif

//	printf("Enter HeapSort\n");
	
	/*初始化构建大根堆*/
	initMaxHeap(array, size);

	/* 每轮提取构建有序区,然后对新的无序区的堆再次平衡 */
	for(i = size - 1 ; i > 0; i--){ /* 无序区逐步往前缩减,最后一个直接认为在有序区 */
		/* 交换 [0] 和 [i], i 进入有序区*/
		temp = array[0];
		array[0] = array[i];
		array[i] = temp;		

#ifdef DEBUG		
		printf("heapsize = %d, [max] = %d, 已经swap([0], [%d]) \n", i + 1, temp, i);				
		printf("[");
		/*有序区域*/
		for(k =0; k <= i; k++){
			printf("  %d  ", array[k]);
		}
		printf("]  , ");
		
		/*无序区域*/
		printf(" [");
			for(k = i + 1; k < size; k++){
				printf("  %d  ", array[k]);
		}
		printf("]\n");
		printf("\n");
#endif

		/* 构建堆 [0, i-1] */
		BuildMaxHeap(array, 0, i - 1);		
	}		

//	printf("Left HeapSort");
	return SUCCESS;
	
}


int main(int argc, char ** argv){
	int array[10] = {7,3,5,8,0,9,1,2,4,6};
	int i = 0;

	printf("Before sort: \n");
	for(i = 0; i < 10; i++){
		printf("  %d  ", array[i]);
	}
	printf("\n");
	

	HeapSort(array, 10);

	printf("after sort: \n");
	for(i = 0; i < 10; i++){
		printf("  %d  ", array[i]);
	}
	printf("\n");
	
	return 0;
}




调试编译

gcc HeapSort.c -DDEBUG

调试输出

Before sort:
  7    3    5    8    0    9    1    2    4    6
initMaxHeap: size = 10
init Heap [  9    8    7    4    6    5    1    2    3    0  ]

heapsize = 10, [max] = 9, 已经swap([0], [9])
[  0    8    7    4    6    5    1    2    3    9  ]  ,  []

heapsize = 9, [max] = 8, 已经swap([0], [8])
[  3    6    7    4    0    5    1    2    8  ]  ,  [  9  ]

heapsize = 8, [max] = 7, 已经swap([0], [7])
[  2    6    5    4    0    3    1    7  ]  ,  [  8    9  ]

heapsize = 7, [max] = 6, 已经swap([0], [6])
[  1    4    5    2    0    3    6  ]  ,  [  7    8    9  ]

heapsize = 6, [max] = 5, 已经swap([0], [5])
[  1    4    3    2    0    5  ]  ,  [  6    7    8    9  ]

heapsize = 5, [max] = 4, 已经swap([0], [4])
[  0    2    3    1    4  ]  ,  [  5    6    7    8    9  ]

heapsize = 4, [max] = 3, 已经swap([0], [3])
[  1    2    0    3  ]  ,  [  4    5    6    7    8    9  ]

heapsize = 3, [max] = 2, 已经swap([0], [2])
[  0    1    2  ]  ,  [  3    4    5    6    7    8    9  ]

heapsize = 2, [max] = 1, 已经swap([0], [1])
[  0    1  ]  ,  [  2    3    4    5    6    7    8    9  ]

after sort:
  0    1    2    3    4    5    6    7    8    9

另外找到一个博客,写的不错,大家也可以参考

https://www.cnblogs.com/Java3y/p/8639937.html

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!