我的编程空间,编程开发者的网络收藏夹
学习永远不晚

C++超详细实现堆和堆排序过像

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

C++超详细实现堆和堆排序过像

有关二叉树的性质:

1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 个结点.

2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 .

3. 对任何一棵二叉树, 如果度为0其叶结点个数为 , 度为2的分支结点个数为 ,则有 = +1

4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h= . (ps: 是log以2 为底,n+1为对数)

5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对 于序号为i的结点有:

1. 若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点

2. 若2i+1<n,左孩子序号:2i+1 若2i+1>=n则无左孩子

3. 若2i+2<n,右孩子序号:2i+2 若2i+2>=n则无右孩子

有关堆

存储结构:

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结 构存储。现实中我们通常把堆(一种完全二叉树)使用顺序结构的数组来存储

堆的概念和结构:

堆的性质:

堆中某个节点的值总是不大于或不小于其父节点的值;

堆总是一棵完全二叉树。

上面这些都是复制粘贴的, 想看了随便看看。下面给出自己的一些总结:

C++实现堆

Heap.h

#pragma once
#include<iostream>
#include<assert.h>
#include<algorithm>
#include<Windows.h>
using namespace std;
typedef int DataType;
class Heap
{
public:
	Heap() :a(new DataType[1]), size(0), capacity(1) {}
	~Heap()
	{
		delete[]a;
		a = nullptr;
		size = capacity = 0;
	}
public:
	void Push(const DataType& x);
	void Pop();    // 删除堆顶的数据
	DataType Top()const;
	bool Empty()const;
	int Size()const;
	void Swap(DataType& a, DataType& b);
	void print();
public:
	void AdjustUp(int child);
	void AdjustDown(int size, int parent);
private:
	DataType* a;
	int size;
	int capacity;
};

Heap.cpp

#include"Heap.h"
void Heap::Swap(DataType& a, DataType& b)
{
	DataType tmp = a;
	a = b;
	b = tmp;
}
void Heap::Push(const DataType& x)
{
	if (size == capacity)
	{
		int newcapacity = capacity == 0 ? 1 : capacity * 2;
		DataType* tmp = new DataType[newcapacity];
		assert(tmp);
		std::copy(a, a + size, tmp);
		delete a;
		a = tmp;
		capacity = newcapacity;
	}
	a[size] = x;
	AdjustUp(size);
	++size;
}
void Heap::Pop() // 删除堆顶的数据
{
	assert(size > 0);
	Swap(a[0], a[size - 1]);
	size--;
	AdjustDown(size, 0);
}
DataType Heap::Top()const
{
	assert(size > 0);
	return a[0];
}
bool Heap::Empty()const
{
	return size == 0;
}
int Heap::Size()const
{
	return size;
}
void Heap::AdjustUp(int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[parent] > a[child])
		{
			Swap(a[parent], a[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
	//int parent = (child - 1) / 2;
	//if(child > 0)
	//{
	//	if (a[parent] > a[child])
	//	{
	//		Swap(a[parent], a[child]);
	//		child = parent;
	//		AdjustUp(child);
	//	}
	//	else
	//	{
	//		return;
	//	}
	//}
}
void Heap::AdjustDown(int size,int parent) // size 是总大小,parent是从哪里开始向下调整 
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (child + 1 < size && a[child + 1] < a[child])
			child++;
		if (a[child] < a[parent])
		{
			Swap(a[child], a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void Heap::print()
{
	for (int i = 0; i < size; ++i)
	{
		cout << a[i] << ' ';
	}
	cout << endl;
}

其实Heap这个类 物理结构就是一个一维数组,只是逻辑结构是一个堆,我们将其想象成一个具有特定规律的完全二叉树:特定规律就是任意一个二叉树的根节点都>=或<=其子节点。

这个Heap类的关键是push和pop函数,与之相关的是向上调整和向下调整函数,这也是堆的精髓所在。

push是在数组尾部也就是堆的最下面插入一个元素,此时应该调用向上调整算法,因为此结点的插入可能破坏了原来的堆的结构,因此,向上调整即可,但是有个前提,即插入此结点之前这个完全二叉树本身符合堆的特性。并且调整只会影响此插入结点的祖宗,不会对其他节点产生影响。

pop是删除堆顶的元素,且只能删除堆顶的元素,因为堆这个数据结构的一个主要功能就是选数:即选出当前堆中最大或者最小的数,并且选数的效率很高。pop删除堆顶元素之后,再进行一下调整即可选出次大或者次小的元素。

那么,怎么删除呢?即将堆顶和末尾的数字交换,然后删除交换后的末尾数字,此时堆顶元素很可能破坏了堆的结构,因此采用向下调整的算法。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。

堆的应用

向上调整算法和向下调整算法不仅仅用于Heap的插入和删除操作,在堆排序等堆的应用中也要使用。

堆排序

传入一个数组,对数组进行排序,且是一个O(N*LogN)的算法,效率很高。

void AdjustUp(int* a, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[parent] > a[child])
		{
			swap(a[parent], a[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
void AdjustDown(int* a,int size, int parent) // size 是总大小,parent是从哪里开始向下调整 
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (child + 1 < size && a[child + 1] < a[child])
			child++;
		if (a[child] < a[parent])
		{
			swap(a[child], a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

HeapSort

void HeapSort(int* a, int n)
{
	// 将传入的数组看作一个完全二叉树,然后调整为堆。
	// 升序调整为大根堆,降序小根堆。
	// 建堆方式1: O(N*LogN)
	// 利用向上调整算法,其实就是堆的插入函数
	//for (int i = 1; i < n; ++i)
	//{
	//	AdjustUp(a, i);
	//}
	// 建堆方式2: O(N)
	// 利用向下调整算法
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}
	// 建好堆之后排序 目前是一个小堆,小堆用来排降序
	// 5 13 17 19 22 27 32 35 38 42 45
    // O(N * LogN);
    int end = n - 1;
	while (end > 0)
	{
		swap(a[0], a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}

前面说过,堆的一个主要或者说唯一作用就是选数,大根堆选出最大数,小根堆选出最小数,先将给定数组调整为堆,若排升序则调整为大根堆,此时a[0]即最大值,将其与数组末尾数组交换,然后进行向下调整即可选出次大值,再进行交换即可。整个逻辑十分像Heap类的删除操作,只是将删除了的堆顶元素放置在数组末尾而已,然后不断进行这个操作,直到整个数组有序。

将数组调整为堆的思路有两个,一种是模拟插入的操作,从头遍历逐个将元素进行向上调整操作,主要是因为向上调整算法必须基于此完全二叉树本身就是一个堆,才可以进行向上调整操作。所以从尾开始向上调整肯定是不行的。

思路二与思路一有相同之处,即利用向下调整算法,向下调整基于此结点的左子树和右子树都是堆,所以直接从头开始向下调整不可以,所以从尾向前遍历进行向下调整,且末尾的叶子结点没有必要调整,所以从第一个结点数>=2的二叉树开始进行向下调整。

HeapSort的逻辑不会受升序和降序的影响,只需要将AdjustUp和AdjustDown的调整逻辑改变即可。

为什么排升序要建大根堆,不建小根堆呢?

首先,如果建小根堆,确实建好之后的数组比较像升序,且此时最小值也已经在数组的a[0]处,但是,选次大的元素时,对于后面a[1] 至 a[n-1]个元素,此时之前堆的兄弟父子关系全都乱了,向上调整和向下调整都不可以,只能重建堆,而重建堆的时间复杂度为O(N)。如此下去,每次挑出最大值都需要O(N),最终的就是O(N)+O(N-1)+...+O(2)... 总的就是O(N^2)了。

而如果建大根堆,a[0]就是最大值,将其与数组末尾进行交换,这个交换操作只是O(1)的操作,最重要的是交换之后,把末尾元素忽视之后的这个完全二叉树,只有堆顶元素不符合堆,只需向下调整一次即可,为O(logN),即可选出次大值,相比于前面的O(N)就快了很多。

到此这篇关于C++超详细实现堆和堆排序过像的文章就介绍到这了,更多相关C++堆和堆排序内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

C++超详细实现堆和堆排序过像

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

C++超详细分析优化排序算法之堆排序

堆是计算机科学中一类特殊的数据结构的统称,通常是一个可以被看做一棵完全二叉树的数组对象。而堆排序是利用堆这种数据结构所设计的一种排序算法。本文将通过图片详细介绍堆排序,需要的可以参考一下
2023-02-09

C语言堆怎么实现和堆排序是什么

这篇文章主要介绍了C语言堆怎么实现和堆排序是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇C语言堆怎么实现和堆排序是什么文章都会有所收获,下面我们一起来看看吧。一、本章重点堆的介绍堆的接口实现堆排序二、堆2
2023-06-29

C++如何实现堆排序

这篇文章主要介绍“C++如何实现堆排序”,在日常操作中,相信很多人在C++如何实现堆排序问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C++如何实现堆排序”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!概述
2023-06-22

c++实现堆排序的示例代码

本文主要介绍了c++实现堆排序的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2023-02-02

Python实现堆排序的方法详解

本文实例讲述了Python实现堆排序的方法。分享给大家供大家参考,具体如下: 堆排序作是基本排序方法的一种,类似于合并排序而不像插入排序,它的运行时间为O(nlogn),像插入排序而不像合并排序,它是一种原地排序算法,除了输入数组以外只占用
2022-06-04

Java实现快速排序和堆排序的示例代码

这篇文章主要为大家详细介绍了快速排序和堆排序的多种语言的实现(JavaScript、Python、Go语言、Java、C++),感兴趣的小伙伴可以了解一下
2022-12-22

用Python实现堆栈中序遍历二叉树的详细步骤

使用堆栈无需递归就能遍历二叉树,下面是一个使用堆栈中序遍历二叉树的算法。算法思路1)创建一个空栈S。2)将当前节点初始化为root3)将当前节点推入S并设置current=current->left直到current为NULL4)
用Python实现堆栈中序遍历二叉树的详细步骤
2024-01-23

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录