编程珠玑: 14章 堆 14.1实现一个堆,对数组排序 -------解题总结

本文详细介绍了一种基于小顶堆的数据排序算法——堆排序。通过向上调整和向下调整的方法,逐步构建并维护堆的结构,实现了高效的排序过程。文章还提供了完整的C++实现代码,包括堆的构建、调整和排序等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#include <iostream>
#include <stdio.h>
#include <vector>

using namespace std;
/*
问题:实现一个堆排序
分析:
堆,用数组表示的话:设数组x[1..n],则有根节点为x[1],孩子结点下标为2*i,2*i+1,父节点下标为i/2,
大顶堆:任何结点的值>=其父节点的值
采用小顶堆排序的步骤:
1 步骤1:设置第一个元素为根节点,后续插入的结点放在已有结点后面,采用向上调整方法进行调整;
2 步骤2:输出结果: 拷贝一个堆,然后输出拷贝堆中根节点元素,然后交换根节点元素和最末端元素,将除最末端元素以外部分重新
         用向下调整方法调整为堆。
         (或者采用队列,先输入根节点,后续弹出结点,输出结点,这样的话需要建立一个结构体对象:包含元素的值和元素的下标,
		   应该用队列,不能采用队列,因为可能一个孩子结点所在树的元素都小于另一个)


输入:
12(数组元素个数)
19 51 26 40 35 22 17 23 29 15 20 12
输出:
12 15 17 19 20 22 23 26 29 35 40 51

关键:
1采用小顶堆排序的步骤:
1】 步骤1:设置第一个元素为根节点,后续插入的结点放在已有结点后面,采用向上调整方法进行调整;
2】 步骤2:输出结果: 拷贝一个堆,然后输出拷贝堆中根节点元素,然后交换根节点元素和最末端元素,将除最末端元素以外部分重新
         用向下调整方法调整为堆。
         (或者采用队列,先输入根节点,后续弹出结点,输出结点,这样的话需要建立一个结构体对象:包含元素的值和元素的下标,
		   应该用队列,不能采用队列,因为可能一个孩子结点所在树的元素都小于另一个)

2 堆排序是选择排序,因为:每次都是选择一个最小的元素,次最小的元素,...(小堆顶的堆顶最小)。
  选择排序:第i趟从后面n-i个元素中选取最小的元素作为有序子序列第i个元素

3 不需要临时数组拷贝结果,直接在原始数组上操作
//这边交换n-1趟就可使得数组有序
	for(int i = size  ; i >= 1 ; i--)//重复size长度次,否则会漏掉一个元素
	{
		//交换堆顶和最末端元素
		swap(results.at(i) , results.at(1));//最后面是最小元素
		//然后进行向下调整
		adjustDown(results , i - 1);//这里设定长度为当前长度减1,因为最后一个元素不要弄了
	}
*/


void swap(int* ptr1 , int* ptr2)
{
	int temp = *ptr1;
	*ptr1 = *ptr2;
	*ptr2 = temp;
}

/*向上调整,先确定数组最后一个元素下标i,然后寻找其父节点下标i/2,判断如果结点的值<父节点的值,就交换结点和父节点,并继续向上调整,
直到满足堆的性质,或者当前结点已经变成根节点
*/
void adjustUp(vector<int>& datas)
{
	if(datas.empty())
	{
		return;
	}
	int i = datas.size() - 1;//由于第一个元素不做数,因此这里下标要减一
	int p;
	while(true)
	{
		//如果当前结点为根节点,直接退出
		if(1 == i)
		{
			break;
		}
		//寻找父节点下标
		p = i / 2;
		//如果结点 < 父节点的值,就交换,交换后,需要另当前结点下标变成其父节点下标
		if( datas.at(i) < datas.at(p) )
		{
			swap(datas.at(i) , datas.at(p));
			i = p;
		}
		//如果发现符合堆的性质,直接退出
		else
		{
			break;
		}
	}
}

//向下调整:从根节点往下,如果发现根节点大于两个孩子结点中的较小值,就交换根节点和两个孩子结点中的较小值,直到处理结点的下标大于数组长度或者符合堆的性质
void adjustDown(vector<int>& datas , int size)
{
	if(datas.empty())
	{
		return;
	}
	int i = 1;
	int c;
	while(true)
	{
		//如果处理结点下标 > 数组长度,直接退出
		if(i > size)
		{
			break;
		}
		//如果发现:当前结点 > 两个孩子结点中的较大值
		c = 2 * i;
		//如果孩子结点下标 > 数组长度,无需处理,直接退出
		if(c > size)
		{
			break;
		}
		//如果该结点还有右孩子结点
		if(c + 1 <= size)
		{
			//如果右孩子 < 左孩子
			if( datas.at(c + 1) < datas.at(c))
			{
				c++;
			}
		}
		//如果结点 > 两个孩子结点中的较大值,交换两个结点,并使得当前结点为其孩子结点
		if( datas.at(i) > datas.at(c) )
		{
			swap(datas.at(i) , datas.at(c) );
			i = c;
		}
		else
		{
			break;
		}
	}
}

vector<int> heapSort(vector<int>& datas)
{
	vector<int> results;//存放最终的堆
	if(datas.empty())
	{
		return results;
	}
	int size = datas.size();
	results.push_back(-1);
	//首先是建堆,采用向上调整,注意向上调整完以后,不需要从根节点继续向下判断是否满足堆,此时一定满足
	for(int i = 0 ; i < size ; i++)
	{
		int value = datas.at(i);
		results.push_back(value);
		//向上调整
		adjustUp(results);
	}

	//这边交换n-1趟就可使得数组有序
	for(int i = size  ; i >= 1 ; i--)//重复size长度次,否则会漏掉一个元素
	{
		//交换堆顶和最末端元素
		swap(results.at(i) , results.at(1));//最后面是最小元素
		//然后进行向下调整
		adjustDown(results , i - 1);//这里设定长度为当前长度减1,因为最后一个元素不要弄了
	}
	return results;
}

void print(vector<int> results)
{
	if(results.empty())
	{
		cout << "No result " << endl;
	}
	else
	{
		int size = results.size();
		for(int i = size ; i >= 1 ; i--)//第0个元素不需要输出
		{
			cout << results.at(i) << " ";
		}
		cout << endl;
	}
}

void process()
{
	int num;
	vector<int> datas;
	int value;
	vector<int> results;
	while(cin >> num)
	{
		datas.clear();
		for(int i = 0 ; i < num ; i++)
		{
			cin >> value;
			datas.push_back(value);
		}
		//下面进行堆排序
		results = heapSort(datas);
		print(results);
	}
}

int main(int argc , char* argv[])
{
	process();
	getchar();
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值