内部排序之堆排序

堆排序(Heap Sort)只需要一个记录大小的辅助空间,每个待排序的记录仅占有一个存储空间。

(1)基本概念
a)堆:设有n个元素的序列:
{k1, k2, ..., kn}

对所有的i=1,2,...,(int)(n/2),当满足下面关系:
ki≤k2i,ki≤k2i+1

或 ki≥k2i,ki≥k2i+1

这样的序列称为堆。
堆的两种类型:
根结点最小的堆----小根堆。
根结点最大的堆----大根堆。
根结点称为堆顶,即:在一棵完全二叉树中,所有非叶结点的值均小于(或均大于)左、右孩子的值。
b)堆排序:是一种树型选择排序,特点是,在排序过程中,把R[1..n]看成是一个完全二叉树的存储结构,利用完全二叉树双亲结点和孩子结点的内在关系,在当前无序区中选择关键字最大(最小)的记录。
(2)堆排序步骤:
1、从k-1层的最右非叶结点开始,使关键字值大(或小)的记录逐步向二叉树的上层移动,最大(或小)关键字记录成为树的根结点,使其成为堆。
2、逐步输出根结点,令r[1]=r[i](i=n,,n-1,...,2),在将剩余结点调整成堆。直到输出所有结点。我们称这个自堆顶到叶子的调整过程为“筛选”。
(3)要解决的两个问题:
1、如何由一个无序序列建成一个堆;
2、输出一个根结点后,如何将剩余元素调整成一个堆。
将一个无序序列建成一个堆是一个反复“筛选”的过程。若将此序列看成是一个完全二叉树,则最后一个非终端结点是第floor(n/2)个元素,由此“筛选”只需从第floor(n/2)个元素开始。
堆排序中需一个记录大小的辅助空间,每个待排的记录仅占有一个存储空间。堆排序方法当记录较少时,不值得提倡。当n很大时,效率很高。堆排序是不稳定的。
堆排序的算法和筛选的算法如第二节所示。为使排序结果是非递减有序排列,我们在排序算法中先建一个“大顶堆”,即先选得一个关键字为最大的记录并与序列中最后一个记录交换,然后对序列中前n-1个记录进行筛选,重新将它调整为一个“大顶堆”,然后将选得的一个关键字为最大的记录(也就是第一个元素)与当前最后一个记录交换(全局看是第n-1个),如此往复,直到排序结束。由到,筛选应按关键字较大的孩子结点向下进行。

堆排序的算法描述如下:

用C语言代码实现如下:

#include "iostream"
using namespace std;

#define MAXSIZE 20

typedef struct
{
	int key;
	//其他数据信息
}RedType;

typedef struct
{
	RedType r[MAXSIZE+1];
	int length;
}Sqlist;

typedef Sqlist HeapType;  //堆采用顺序表存储表示

void HeapAdjust(HeapType &H,int s,int m)   //已知H.r[s...m]中记录的关键字出H.r[s].key之外均满足堆的定义,本函数调整H.r[s]的关键字,使H.r[s...m]成为一个大顶堆(对其中记录的关键字而言)
{
	int j;
	RedType rc;
	rc=H.r[s];
	for(j=2*s;j<=m;j*=2)   //沿key较大的孩子结点向下筛选
	{
		if(j<m && (H.r[j].key<H.r[j+1].key))     //j为key较大的记录的下标
			++j;
		if(rc.key>=H.r[j].key)           //rc应插入在位置s上
			break;
		H.r[s]=H.r[j];      //将左、右孩子较大的结点与父节点进行交换,建成大顶堆
		s=j;
	}
	H.r[s]=rc;             //插入
}

void HeapSort(HeapType &H)      //对顺序表H进行堆排序
{
	int i;
	for(i=H.length/2;i>0;--i)   //由一个无序序列建成一个大顶堆,将序列看成是一个完全二叉树,则最后一个非终端节点是第n/2个元素
		HeapAdjust(H,i,H.length);
	for(i=H.length;i>1;--i)
	{
		H.r[0]=H.r[1];   //将堆顶记录和当前未经排序的子序列H.r[1...i]中最后一个记录相互交换
		H.r[1]=H.r[i];
		H.r[i]=H.r[0];
		HeapAdjust(H,1,i-1);    //将H.r[1...i-1]重新调整为大顶堆
	}
}//HeapSort

void InputL(Sqlist &L)
{
	int i;
	printf("Please input the length:");
	scanf("%d",&L.length);
	printf("Please input the data needed to sort:\n");
	for(i=1;i<=L.length;i++)    //从数组的第1个下标开始存储,第0个下标作为一个用于交换的临时变量
		scanf("%d",&L.r[i].key);
}

void OutputL(Sqlist &L)
{
	int i;
	printf("The data after sorting is:\n");
	for(i=1;i<=L.length;i++)
		printf("%d ",L.r[i].key);
	printf("\n");
}

int main(void)
{
	Sqlist H;
	InputL(H);
	HeapSort(H);
	OutputL(H);
	system("pause");
	return 0;
}

不使用上面的结构体的另外一种方法如下:

/*
*堆排序
*/
#include "iostream"
using namespace std;
#define N 10

int array[N];

void man_input(int *array)
{
	int i;
	for(i=1;i<=N;i++)
	{
		printf("array[%d]=",i);
		scanf("%d",&array[i]);
	}
}
void mySwap(int *a,int *b)//交换
{
	int temp;

	temp=*a;
	*a=*b;
	*b=temp;
}
void heap_adjust(int *heap,int root,int len)     //对堆进行调整,使下标从root到len的无序序列成为一个大顶堆
{
	int i=2*root;
	int t=heap[root];

	while(i<=len)
	{
		if(i<len)
		{
			if(heap[i]<heap[i+1])
				i++;
		}

		if(t>=heap[i])
			break;
		heap[i/2]=heap[i];
		i=2*i;
	}
	heap[i/2]=t;

}

void heapSort(int *heap,int len)      //堆排序
{
	int i;
	for(i=len/2;i>0;i--)    //由一个无序序列建成一个大顶堆,将序列看成是一个完全二叉树,则最后一个非终端节点是第len/2个元素
	{
		heap_adjust(heap,i,len);
	}
	for(i=len;i>=1;i--)
	{
		mySwap(heap+i,heap+1);    //将堆顶记录与最后一个记录相互交换
		heap_adjust(heap,1,i-1);   //将下标为1~i-1的记录重新调整为大顶堆
	}
}
void print_array(int *array,int n)
{
	int k;
	for(k=1;k<n+1;k++)
	{
		printf("%d\t",array[k]);
	}
}
int main(void)
{

	man_input(array);
	heapSort(array,N);

	printf("\nAfter sorted by the heap_sort algorithm:\n");        
	print_array(array,N);   //打印堆排序结果
	system("pause");
	return 0;
}


<think>我们正在讨论C++中的sort_heap函数。用户的问题集中在两个方面: 1. sort_heap函数是否会破坏堆的结构? 2. sort_heap函数的时间复杂度是多少? 根据引用[1]中的内容: “使用sort_heap(f, l)处理过的区间因为已经有序,就不再是一个heap了。” 因此,我们可以明确回答第一个问题:sort_heap函数会破坏堆的结构,因为经过sort_heap处理后,容器中的元素已经变成完全有序(升序或降序),而不再满足堆的性质(父节点大于或小于子节点,但不是完全有序)。 关于第二个问题,sort_heap的时间复杂度。引用[2]提到: “堆排序算法的时间复杂度为O(nlogn)。其中,构建大根堆的时间复杂度为O(n),依次取出最大元素并调整剩余元素的总时间复杂度为O(nlogn)。所以,堆排序算法的总时间复杂度为O(n+nlogn)=O(nlogn)。” 而sort_heap函数实际上就是堆排序算法的实现。因此,sort_heap的时间复杂度为O(n log n)。 另外,引用[4]也列举了堆排序的时间复杂度为O(nlogn)。 下面我们详细说明: 在C++中,使用sort_heap函数前,必须保证容器已经是一个堆(通常通过make_heap建立)。sort_heap函数会将堆转换成有序序列(默认升序,因为最大堆排序后是升序,最小堆排序后是降序?实际上,在C++中,默认建立的是最大堆,所以sort_heap后得到的是升序序列)。 步骤: 1. 首先,需要有一个堆(通过make_heap创建)。 2. 然后调用sort_heap,它会将堆中的元素进行排序。 注意:排序后,原来的堆结构被破坏,整个序列变成有序序列,不再是一个堆。 时间复杂度:O(n log n),其中n是元素个数。 示例代码(参考引用[1]): vector<int> v1{6, 1, 2, 5, 3, 4}; make_heap(v1.begin(), v1.end()); // 建立最大堆,此时堆顶是6 sort_heap(v1.begin(), v1.end()); // 排序后,v1变成{1,2,3,4,5,6},升序,但不再是一个堆。 因此,如果后续还需要使用堆结构(如需要push_heap或pop_heap),则不能使用sort_heap,因为sort_heap破坏了堆结构。 总结: - sort_heap会破坏堆结构,将堆转化为有序序列。 - sort_heap的时间复杂度为O(n log n)。 最后,根据用户的要求,生成相关问题。</think>### C++ `sort_heap` 函数详解 #### **一、`sort_heap` 是否会破坏堆结构?** **是的,`sort_heap` 会彻底破坏堆结构**。该函数会将堆转换为完全有序序列,失去堆的特性: ```cpp std::vector<int> vec = {3, 1, 4, 1, 5, 9}; std::make_heap(vec.begin(), vec.end()); // 构建最大堆 [9,5,4,1,1,3] std::sort_heap(vec.begin(), vec.end()); // 堆结构被破坏 // 结果:vec = [1,1,3,4,5,9] (有序但非堆)[^1] ``` **关键点**: 1. 调用 `sort_heap` 后容器**不再满足堆性质**(父节点 ≥ 子节点) 2. 后续若需堆操作,必须重新调用 `make_heap` 建堆 3. 堆排序算法本质:建堆 → 排序 → 堆结构消失[^2] --- #### **二、`sort_heap` 时间复杂度分析** **时间复杂度**:严格 $O(n \log n)$ **详细分解**: 1. **建堆阶段** (`make_heap`):$O(n)$ - 从最后一个非叶子节点向上调整 2. **排序阶段** (`sort_heap`):$O(n \log n)$ - 执行 $n-1$ 次 `pop_heap` 操作(每次 $O(\log n)$) - 每次 `pop_heap` 包含:交换堆顶/末尾 → 向下调整 **数学证明**: $$ \begin{align*} T(n) &= O(n) + \sum_{i=1}^{n-1} O(\log i) \\ &= O(n) + O(\log n!) \\ &= O(n) + O(n \log n) \quad \text{(斯特林公式)} \\ &= O(n \log n) \end{align*} $$ 此效率优于快速排序的最坏情况 $O(n^2)$,归并排序相当[^2][^4]。 --- #### **三、堆排序完整流程** ```cpp #include <algorithm> #include <vector> void heap_sort(std::vector<int>& arr) { // 1. 建堆 O(n) std::make_heap(arr.begin(), arr.end()); // 2. 排序 O(n log n) std::sort_heap(arr.begin(), arr.end()); // 此时arr有序但非堆! } // 示例 int main() { std::vector<int> data = {3, 1, 4, 1, 5, 9, 2, 6}; heap_sort(data); // 结果: [1,1,2,3,4,5,6,9] } ``` --- #### **四、堆排序 vs 其他排序算法** | **算法** | 平均时间复杂度 | 最坏时间复杂度 | 是否稳定 | 空间复杂度 | |----------------|----------------|----------------|----------|------------| | 堆排序 | $O(n \log n)$ | $O(n \log n)$ | 不稳定 | $O(1)$ | | 快速排序 | $O(n \log n)$ | $O(n^2)$ | 不稳定 | $O(\log n)$ | | 归并排序 | $O(n \log n)$ | $O(n \log n)$ | 稳定 | $O(n)$ | | 插入排序 | $O(n^2)$ | $O(n^2)$ | 稳定 | $O(1)$ | **优势场景**: - 需要 $O(1)$ 额外空间时(嵌入式系统) - 避免快速排序最坏情况(如预先排序的数据) - 实时计算中动态获取 Top K 元素[^4] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值