排序——堆排序

本节继续复习排序算法。这次复习排序算法中的堆排序。 堆排序属于选择排序。

目录

什么是堆?

堆排序

堆排序的思想

堆排代码

向下调整算法

 堆排整体


什么是堆?

在复习堆排序之前, 首先我们需要回顾一下什么是堆 。

堆的本质其实是一个数组。 它的物理结构本质上是一个数组。 但是它的逻辑结构是一棵完全二叉树。我们在判断一个数组是否是一个堆的时候根据的就是它的逻辑结构。

那么怎么根据它的逻辑结构判断是否是一个堆。 

首先堆的逻辑结构的二叉树的每一个孩子节点都大于它的每一个父亲, 就是堆。 这种堆叫做小堆。 如果它的逻辑结构的每一个孩子节点都小于它的父亲节点。 它同样是个堆, 同时这种堆叫做大堆。 

如图数组就是一个小堆

将该小堆的逻辑结构画出来就是这样的:

我们可以看到,在这棵二叉树之中的每一个父亲节点都小于它的孩子节点。 那么他就是一个小堆。 

大堆就是它的每个父亲节点都大于它的每个孩子节点

如图:

堆排序

堆排序的思想

堆排序的思想利用了大堆或小堆的结构。

我们从上面的解析可以发现。 大堆中, 第一个元素一定是整个数组中最大的那个元素;小堆中,第一个元素一定是数组中最小的那个元素。

堆排的核心就是每一趟都将堆的第一个元素和堆的最后一个元素交换位置。 然后让这个堆的元素个数减一。 同时通过向下调整算法重新建堆。 循环往复。 不断选出最大或者最小的那个数据放到堆的最后。

不知道向下调整算法可以去回顾本节:堆——c语言实现堆结构-优快云博客

现在我们来分析一下堆的排序过程。 

我们使用小堆进行堆排:

 

第一步,将第一个元素也就是堆顶的元素和最后一个元素交换:

然后堆的大小减一:

向下调整算法重新建堆: 

 堆的第一个元素再次和堆最后一个元素交换位置:

 堆的大小减一:

 然后向下调整算法重新建堆:

然后堆的第一个元素再和堆的最后一个元素交换位置, 然后堆的大小减一, 然后向下调整算法调堆。 循环,一直到堆只剩下一个元素位置。 排好后就是这样的:​​​​​​​ 现在已经有序,同时是降序。 这时我们使用的是小堆排的。其实这里就是用大小堆控制升序降序。 小堆排的就是降序, 大堆排的就是升序。 

堆排代码

向下调整算法

我们先再来实现一次向下调整算法:


//向下调整建大堆。
void AdjustDownSort(int* a, int sz, int parent)
{

}

a是要建堆的数组, sz是数组的大小。 parent是建堆的堆顶节点 

 


//向下调整建大堆。
void AdjustDownSort(int* a, int sz, int parent)
{
	int child = parent * 2 + 1;//先假设左孩子小。 

	
}

 我们先假设左孩子比较大(注意, 我这里要建的是大堆)。


//向下调整建大堆。
void AdjustDownSort(int* a, int sz, int parent)
{
	int child = parent * 2 + 1;//先假设左孩子小。 

	while (child < sz) //控制条件是孩子不能超出整个数据。 
	{
		
        child = 2 * parent + 1;
	}
}

然后使用child指针控制循环结束。 当child指针超出整个数组的时候就不用再向下调整了。 

 


//向下调整建大堆。
void AdjustDownSort(int* a, int sz, int parent)
{
	int child = parent * 2 + 1;//先假设左孩子小。 

	while (child < sz) //控制条件是孩子不能超出整个数据。 
	{
		if (child + 1 < sz && a[child] < a[child + 1])//让child指向大的那个孩子。说明建大堆, 排升序。 
		{
			child++;
		}
		
			child = 2 * parent + 1;

		

	}
}

 因为我们是假设的左孩子比较大, 所以进入循环之后我们要比较一下左右孩子。如果右孩子更大的话就假设错误, child指针需要加加。

 


//向下调整建大堆。
void AdjustDownSort(int* a, int sz, int parent)
{
	int child = parent * 2 + 1;//先假设左孩子小。 

	while (child < sz) //控制条件是孩子不能超出整个数据。 
	{
		if (child + 1 < sz && a[child] < a[child + 1])//让child指向大的那个孩子。说明建大堆, 排升序。 
		{
			child++;
		}
		//
		if (a[child] > a[parent]) 
		{
			Swap(&a[child], &a[parent]);
			parent = child;//继续向下调整。 父亲便孩子, 孩子变成孩子的孩子。
			child = 2 * parent + 1;

		}
		else 
		{
			break;//如果父亲比小的孩子还要大, 那么这个父亲就是一个堆。 就不需要再调整建堆了。
		}
		//

	}
}

 然后在每次循环中都比较一下父亲节点和两个孩子中更小的节点。 如果孩子比父亲还要大。 就交换孩子节点和父亲节点。

如果孩子节点比父亲节点要小。 那么就说明这个堆已经建好了。 就跳出循环。

 堆排整体

一、


//堆排序
void HeapSort(int* a, int sz) 
{
		
}

 首先, 堆排的函数名以及传参。 除了快排传参基本都是传要排序的数组和数组的大小。

二、


//堆排序
void HeapSort(int* a, int sz) 
{
	int end = sz - 1;


	//先建堆, 向下调整建大堆
	for (int i = (end - 1) / 2; i >= 0; i--)//从最后一个节点的父亲开始向下调整。  一棵子树一棵子树的建成堆。最后整体成堆
	{
		AdjustDownSort(a, sz, i);//向下调整算法建大堆。
	}
	//

	while (end > 0) 
	{
		Swap(&a[0], &a[end]);//让第n个数据和堆顶最大的那个数据交换, 就能让第end个数据是最大的那个数据。 
		AdjustDownSort(a, end, 0);//第n个数据和堆顶数据交换后,前end - 1个数据只有堆顶不符合堆, 堆顶左右子树都是堆, 这时满足
		//向下调整算法。 然后向下调整。 
		end--;
	}
	
}

堆排的实现过程就是我们上面分析的过程。 首先需要一个堆。 所以先建堆。 

建完堆之后就交换堆顶和最后一个数据。 然后堆的大小减一调堆。循环往复。  

 

 

### 堆排序算法详解 堆排序是一种基于比较的高效排序算法,其核心思想是利用堆这种数据结构来完成排序操作。堆可以被看作是一棵完全二叉树,并且满足堆积性质:对于最大堆而言,任意节点的关键字都不小于其子节点的关键字;而对于最小堆,则相反。 #### 一、基本概念 堆排序分为两种主要形式——最大堆和最小堆。在最大堆中,父节点始终大于等于其子节点[^4]。因此,在一个数组表示的最大堆中,第一个元素总是当前集合中的最大值。同样地,在最小堆中,父节点始终小于等于其子节点。 #### 二、主要过程 堆排序的过程主要包括以下几个方面: 1. **建堆** 将输入的数据构建成一个初始堆(通常是从最后一个非叶子节点向上逐层调整)。这一阶段的目标是使整个数据集符合堆定义的要求。 2. **堆调整** 当移除堆顶元素后,需要重新调整剩下的部分以保持堆特性不变。这一步骤称为“下沉”,即将新的根节点与其较大的孩子交换位置直到恢复堆属性为止[^3]。 #### 三、特点分析 - 时间复杂度稳定为 O(n log n),无论最好情况还是最坏情况下都适用; - 是一种原地排序方法,不需要额外存储空间; - 不稳定性:由于可能涉及多次覆盖写入操作,所以它不是稳定的排序方式[^2]。 #### 四、C代码实现示例 以下是使用 C 编程语言编写的简单版本的堆排序程序: ```c #include <stdio.h> // 调整堆函数 void heapify(int arr[], int n, int i){ int largest = i; // 初始化最大为根节点 int l = 2*i + 1; // 左子节点 int r = 2*i + 2; // 右子节点 if (l < n && arr[l] > arr[largest]) largest = l; if (r < n && arr[r] > arr[largest]) largest = r; if(largest !=i ){ swap(&arr[i], &arr[largest]); heapify(arr,n,largest); } } // 主要堆排序逻辑 void heapsort(int arr[],int n){ for(int i=n/2 -1;i>=0;i--){ heapify(arr,n,i); } for(int i= n-1 ;i>0;i--){ swap(&arr[0],&arr[i]); heapify(arr,i,0); } } ``` 上述代码展示了如何通过递归调用来维护堆结构并最终完成排序任务[^1]。 --- ### Java 实现示例 如果考虑另一种主流编程语言如 Java 的话,也可以按照相似思路编写如下所示的堆排序类: ```java public class HeapSort { public void sort(int[] array) { int length = array.length; // 构造初始堆 for (int i = length / 2 - 1; i >= 0; i--) { adjustHeap(array, i, length); } // 进行n-1次循环处理 for (int j = length - 1; j > 0; j--) { // 把当前最大的放到最后面去 swap(array, 0, j); // 对前面j-1个数再次进行堆化 adjustHeap(array, 0, j); } } private static void adjustHeap(int[] array,int index ,int size){ int temp=array[index]; for(int k=index*2+1;k<size;k=k*2+1){ if(k+1<size&&array[k]<array[k+1]){ k++; } if(temp>=array[k])break; array[index]=array[k]; index=k; } array[index]=temp; } private static void swap(int[] data, int a, int b){ int tmp=data[a]; data[a]=data[b]; data[b]=tmp; } } ``` 这段代码实现了完整的堆排序流程,包括初始化堆以及后续每次删除后的重排工作。 ---
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值