广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++中二叉堆排序详解
  • 558
分享到

C++中二叉堆排序详解

二叉堆排序c++c语言二叉树堆排序 2023-01-07 12:01:27 558人浏览 薄情痞子
摘要

目录1. 前言什么是二叉堆?2 堆的数据结构2.1 二叉堆的抽象数据结构2.2 基础 api 实现2.3 上沉算法2.4 下沉算法3. 堆排序4. 后记1. 前言 什么是二叉堆? 二

1. 前言

什么是二叉堆?

二叉堆是有序的 完全二叉树,在完全二叉树的基础上,二叉堆 提供了有序性特征:

二叉堆 的根结点上的值是整个堆中的最小值最大值

根结点上的值是整个堆结构中的最小值时,此堆称为最小堆。最小堆中,任意节点的值大于父结点的值。

根结点上的值是整个堆结构中的最大值时,则称堆为最大堆。最大堆中,任意节点的值小于父结点的值。

根据完全二叉树的特性,二叉堆的父结点与子结点之间满足下面的关系:

如果知道了一个结点的位置 i,则其左子结点在 2*i 位置,右子结点在 2*i+1 位置。

Tips: 前提是存在有子结点。

如果知道了一个结点的位置 i,则其父结点在 i除以 2 的位置。

Tips: 根结点没有父结点。

如上图所示:

值为 5 的结点在 2 处,则其左结点 12 的位置应该在 2*2=4 处,而实际情况也是在 4 位置。其右子结点 13 的位置应该在 2*2+1=5 的位置,实际位置也是在 5 位置。

值为 19 的结点现在 7 位置,其父结点的根据公式 72 等于 3(取整),应该在 3 处,而实际情况也是在 3 处(位置在 3、 值为 8 的结点是其父结点)。

2 堆的数据结构

2.1 二叉堆的抽象数据结构

当谈论某种数据结构的抽象数据结构时,最基本的 API 无非就是增、删、改、查。

二叉堆的基本抽象数据结构:

Heap() :创建一个新堆。 insert(data): 向堆中添加新节点(数据)。 getRoot(): 返回最小(大)堆的最小(大)元素。 removeRoot() :删除根节点。 isEmpty():判断堆是否为空。 findAll():查询堆中所有数据。

根据二叉堆的特性,顺序存储应该成为堆的首选方案。

如有数列=[8,5,12,15,19,13,1],可以先创建一个一维数组

数组第 0 位置初始为 0,从第 2 个位置也就是索引号为 1 的地方开始存储堆的数据。如下图,二叉堆中的数据在数组中的对应存储位置。

2.2 基础 API 实现

设计一个 Heap 类封装对二叉堆的操作方法,类中方法用来实现最小堆。

#include <iOStream>
using namespace std;
 
template<typename T>
class Heap{
    private:
    	
    	//数组
    	T heapList[100];
    	//实际大小
		int size=0; 
		
	public:
		
		 
		Heap(){
		} 
		
		
		T getRoot();
		
		
		T removeRoot();
    	
    	
		T removeRoot_();
		void removeRootByRecursion(int parentIdx );
		
		 
		void setRoot(T val);
		
		
		int insert(T  val);
		
		 
		bool isEmpty();
    
    	
		int insert_(T  val);
		int insertByRecursion(int  pos);
		
		
		void findAll() {
			for(int i=0; i<=size; i++)
				cout<<this->heapList[i]<<"\t";
			cout<<endl;
		}	 
}; 

Heap 类中的属性详解:

heapList:使用数组存储二叉堆的数据,初始时,列表的第 0 位置初始为默认值 0

Tips: 为什么要设置列表的第 0 位置的默认值为 0

这个 0 也不是随意指定的,有其特殊数据含义:用来描述根结点的父结点编号或者说根结点没有父结点。

size:用来存储二叉堆中数据的实际个数。

Heap 类中的方法介绍:

isEmpty:检查是不是空堆。逻辑较简单。


template<typename T>
bool Heap<T>::isEmpty(){
	return Heap::size==0;
}

setRoot:创建根结点。保证根节点始终存储在列表索引为 1 的位置。


template<typename T>
void Heap<T>::setRoot(T val) {
	if( Heap<T>::heapList[1]==0  )
		Heap<T>::heapList[1]=val;
		Heap<T>::size++;
}

getRoot:如果是最大堆,则返回二叉堆的最大值,如果是最小堆,则返回二叉堆的最小值。


template<typename T>
T Heap<T>::getRoot() {
	if( !Heap<T>::isEmpty  )
		return	Heap<T>::heapList[1];
}

Tips: 使用数组存储二叉堆数据时,根结点始终保存在索引号为 1 的位置。

前面是几个基本方法,现在实现添加新结点,编码之前,先要知道如何在二叉堆中添加新结点:

2.3 上沉算法

添加新结点采用上沉算法。如下演示上沉算法的实现过程。

新结点添加到已有的二叉堆的最后面。如下图,添加值为 4 的新结点,存储至索引号为 7 的位置。

查找新结点父结点,并与父结点的值比较大小,如果比父结点的值小,则和父结点交换位置。如下图,值为 4 的结点小于值为 8 的父结点,两者交换位置。

交换后再查询是否存在父结点,如果有,同样比较大小、交换,直到到达根结点或比父结点大为止。值为 4 的结点小于值为 5 的父结点,继续交换。交换后,新结点已经达到了根结点位置,整个添加过程可结束。观察后会发现,遵循此流程添加后,没有破坏二叉堆的有序性。

编码实现 insert 方法


template<typename T>
T Heap<T>::insert(T val) {
	//存储在最后一个位置
	int pos= ++Heap<T>::size;
	Heap<T>::heapList[pos]=val;
	int temp=0;
	//上沉算法
	while(1) {
		//找到父结点位置
		int parentIdx=  pos / 2;
		if(parentIdx==0)
			//出口一,没有父结点
			break;
		if( Heap<T>::heapList[pos]>Heap<T>::heapList[parentIdx] )
			//出口二:大于父结点
			break;
		else {
			//和父亲结点交换
			temp=Heap<T>::heapList[pos];
			Heap<T>::heapList[pos]=Heap<T>::heapList[parentIdx];
			Heap<T>::heapList[parentIdx]=temp;
			pos=parentIdx
		}
	}
}

测试向二叉堆中添加数据。

int main(int arGC, char** argv) {
	//实例化堆
	Heap<int> heap;
	//初始化根结点
	heap.setRoot(5);
	//检查根结点是否创建成功
	int rootVal=heap.getRoot();
	cout<<"根结点的值:"<<rootVal<<endl;
	//添加值为 12和值为  13 的 2个新结点,检查添加新结点后整个二叉堆的有序性是否正确。
	heap.insert(12);
	heap.insert(13);
	cout<<"测试一:"<<endl;
	heap.findAll();
	return 0;
}

输出结果:

添加值为 1 的新结点,并检查二叉堆的有序性。

int main(int argc, char** argv) {
	//省略……
    //添加值为 1 的结点
	heap.insert(1);
	cout<<"测试二:"<<endl;
	heap.findAll();
	return 0;
}

继续添加值为 151983 个新结点,并检查二叉堆的状况。

int main(int argc, char** argv) {
	//省略……
	heap.insert(15);
	heap.insert(19);
	heap.insert(8);
	cout<<"测试三:"<<endl;
	heap.findAll();
	return 0;
}

上沉算法同样可以使用递归实现。


template<typename T>
int Heap<T>::insert_(T  val) {
	//存储在最后一个位置
	int pos= ++Heap<T>::size;
	Heap<T>::heapList[pos]=val;
	//调用
	Heap<T>::insertByRecursion(pos);
}
template<typename T>
int Heap<T>::insertByRecursion(int  pos) {
//找到父结点位置
	int parentIdx=  pos / 2;
	if(parentIdx==0)
		//出口一,没有父结点
		return pos;
	if( Heap<T>::heapList[pos]>Heap<T>::heapList[parentIdx] )
		//出口二:大于父结点
		return pos;
	else {
		//和父亲结点交换
		int temp=Heap<T>::heapList[pos];
		Heap<T>::heapList[pos]=Heap<T>::heapList[parentIdx];
		Heap<T>::heapList[parentIdx]=temp;
		//递归 
		Heap<T>::insertByRecursion(parentIdx);
	}
}

2.4 下沉算法

介绍完添加方法后,再来了解一下,如何使用下沉算法删除二叉堆中的结点。

二叉堆的删除操作从根结点开始,如下图删除根结点后,空出来的根结点位置,需要在整个二叉堆中重新找一个结点充当新的根结点。

二叉堆中使用下沉算法选择新的根结点:

找到二叉堆中的最后一个结点,移到到根结点位置。如下图,把二叉堆中最后那个值为 19 的结点移到根结点位置。

最小堆中,如果新的根结点的值比左或右子结点的值大,则和子结点交换位置。如下图,在二叉堆中把 195 的位置进行交换。

Tips: 总是和最小的子结点交换。

交换后,如果还是不满足最小二叉堆父结点小于子结点的规则,则继续比较、交换新根结点直到下沉到二叉堆有序为止。如下,继续交换 1219 的值。如此反复经过多次交换直到整个堆结构符合二叉堆的特性。

removeoot 方法的具体实现:


template<typename T>
T Heap<T>::removeRoot() {
	if(Heap<T>::size==0)return NULL;
	T root=Heap<T>::heapList[1];
	if(Heap<T>::size==1) {
		Heap<T>::size--;
		return root;
	}
	//堆中最后一个结点移动根结点
	Heap<T>::heapList[1]=Heap<T>::heapList[Heap<T>::size];
	Heap<T>::size--;

	//下沉算法
	int parentIdx=1;
	//子结点值
	T minChild;
	//子结点位置
	int idx;
	while(1) {
		//左结点位置
		int leftIdx=parentIdx*2;
		//右结点位置
		int rightIdx=parentIdx*2+1;
		if( leftIdx<=Heap<T>::size && rightIdx<=Heap<T>::size ) {
			//记录较小的结点值和位置
			minChild=Heap<T>::heapList[leftIdx]<Heap<T>::heapList[rightIdx]?Heap<T>::heapList[leftIdx]:Heap<T>::heapList[rightIdx];
			idx=Heap<T>::heapList[leftIdx]<Heap<T>::heapList[rightIdx]?leftIdx:rightIdx;
		} else if( leftIdx<=Heap<T>::size) {
			minChild=Heap<T>::heapList[leftIdx];
			idx=leftIdx;
		} else if( rightIdx<=Heap<T>::size ) {
			minChild=Heap<T>::heapList[rightIdx];
			idx=rightIdx;
		}else{
			//没有子结点 
			break;
		}
		//是否交换
		if( Heap<T>::heapList[parentIdx]>minChild ) {
			Heap<T>::heapList[idx]=Heap<T>::heapList[parentIdx];
			Heap<T>::heapList[parentIdx]=minChild;
			parentIdx=idx;
		} else {
			break;
		}
	}
	return root;
} 

测试在二叉堆中删除结点:

int main(int argc, char** argv) {
    //省略……
	cout<<"测试删除一:"<<endl;
	heap.removeRoot();
	heap.findAll();
	return 0;
}

可以看到最后二叉堆的结构和有序性都得到了完整的保持。

"下沉算法" 同样可以使用递归实现。


template<typename T>
T Heap<T>::removeRoot_() {
	if(Heap<T>::size==0)return NULL;
	//根结点值
	T root=Heap<T>::heapList[1];
	//
	if(Heap<T>::size==1) {
		Heap<T>::size--;
		return root;
	}
	//堆中最后一个结点移动根结点
	Heap<T>::heapList[1]=Heap<T>::heapList[Heap<T>::size];
	Heap<T>::size--;
	//调用
	Heap<T>::removeRootByRecursion(1);
	return root;
}

template<typename T>
void Heap<T>::removeRootByRecursion(int parentIdx ) {
	//子结点值
	T minChild;
	//子结点位置
	int idx;
	//左结点位置
	int leftIdx=parentIdx*2;
	//右结点位置
	int rightIdx=parentIdx*2+1;
	if( leftIdx<=Heap<T>::size && rightIdx<=Heap<T>::size ) {
		//记录较小的结点值和位置
		minChild=Heap<T>::heapList[leftIdx]<Heap<T>::heapList[rightIdx]?Heap<T>::heapList[leftIdx]:Heap<T>::heapList[rightIdx];
		idx=Heap<T>::heapList[leftIdx]<Heap<T>::heapList[rightIdx]?leftIdx:rightIdx;
	} else if( leftIdx<=Heap<T>::size) {
		minChild=Heap<T>::heapList[leftIdx];
		idx=leftIdx;
	} else if( rightIdx<=Heap<T>::size ) {
		minChild=Heap<T>::heapList[rightIdx];
		idx=rightIdx;
	} else {
		//没有子结点
		return;
	}
	//是否交换
	if( Heap<T>::heapList[parentIdx]>minChild ) {
		Heap<T>::heapList[idx]=Heap<T>::heapList[parentIdx];
		Heap<T>::heapList[parentIdx]=minChild;
        //递归
		Heap<T>::removeRootByRecursion(idx);
	} else {
		return;
	}
}

3. 堆排序

堆排序指借助堆的有序性对数据进行排序。

需要排序的数据以堆的方式保存。 然后再从堆中以根结点方式取出来,无序数据就会变成有序数据 。

如有数列=[4,1,8,12,5,10,7,21,3],现通过堆的数据结构进行排序。

int main(int argc, char** argv) {
	//实例化堆
	Heap<int> heap;
	int nums[] = {4,1,8,12,5,10,7,21,3};
	int size=sizeof(nums)/4;
    // 创建根节点
	heap.setRoot(nums[0]);
    // 其它数据添加到二叉堆中
	for (int i=1; i<size; i++) {
		heap.insert(nums[i]);
	}
	cout<<"堆中数据:"<<endl;
	heap.findAll();
    // 获取堆中的数据
	for(int i=0; i<size; i++ ) {
		nums[i]= heap.removeRoot();
		heap.findAll();
	}
	for(int i=0; i<size; i++)
		cout<<nums[i]<<"\t";
	return 0;
}

输出结果:

本例中的代码还有优化空间,本文试图讲清楚堆的使用,优化的地方交给有兴趣者。

4. 后记

在树结构上加上一些新特性要求,树会产生很多新的变种,如二叉树,限制子结点的个数,如满二叉树,限制叶结点的个数,如完全二叉树就是在满二叉树的“满”字上做点文章,让这个''满"变成"不那么满"。

在完全二叉树上添加有序性,则会衍生出二叉堆数据结构。利用二叉堆的有序性,能轻松完成对数据的排序。

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

--结束END--

本文标题: C++中二叉堆排序详解

本文链接: https://www.lsjlt.com/news/177063.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

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

下载Word文档
猜你喜欢
  • C++中二叉堆排序详解
    目录1. 前言什么是二叉堆?2 堆的数据结构2.1 二叉堆的抽象数据结构2.2 基础 API 实现2.3 上沉算法2.4 下沉算法3. 堆排序4. 后记1. 前言 什么是二叉堆? 二...
    99+
    2023-01-07
    二叉堆排序c++ c语言二叉树堆排序
  • 彻底搞定堆排序:二叉堆
    目录二叉堆插入删除构建二叉堆代码实现总结二叉堆 什么是二叉堆 二叉堆本质上是一种完全二叉树,它分为两个类型 最大堆:最大堆的任何一个父节点的值,都大于等于它的左、右孩子...
    99+
    2022-11-12
  • C语言数据结构二叉树之堆的实现和堆排序详解
    目录一、本章重点二、堆2.1堆的介绍2.2堆的接口实现三、堆排序一、本章重点 堆的介绍堆的接口实现堆排序 二、堆 2.1堆的介绍 一般来说,堆在物理结构上是连续的数组结构,在逻辑结构...
    99+
    2022-11-13
  • Java超详细讲解排序二叉树
    目录排序二叉树概念排序二叉树类的定义添加节点中序遍历查找节点查找某一节点的父节点删除节点排序二叉树概念 二叉排序树(Binary Sort Tree),又称二叉查找树(Binary ...
    99+
    2022-11-13
  • C语言如何解决二叉堆问题
    这篇文章给大家分享的是有关C语言如何解决二叉堆问题的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一、堆的概念1、概述堆是计算机科学中一类特殊的数据结构的统称。实现有很多,例如:大顶堆,小顶堆,斐波那契堆,左偏堆,...
    99+
    2023-06-26
  • 最小二叉树堆排序怎么利用java 实现
    这篇文章给大家介绍最小二叉树堆排序怎么利用java 实现,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。最小二叉堆定义: 二叉堆是完全二元树或者是近似完全二元树,最小二叉堆是父结点的键值总是小于或等于任何一个子...
    99+
    2023-05-31
    java ava
  • 【数据结构】二叉树-堆实现及其堆的应用(堆排序&topK问题)
    文章目录 一、堆的概念及结构二、堆的实现1.结构的定义2.堆的初始化3.堆的插入4.堆的向上调整5.堆的删除6.堆的向下调整7.取出堆顶元素8.返回堆的元素个数9.判断堆是否为空10.打印堆中...
    99+
    2023-09-05
    数据结构 php 开发语言
  • C++超详细实现堆和堆排序过像
    目录有关堆C++实现堆堆的应用堆排序有关二叉树的性质: 1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 个结点. 2. 若规定根节点的层数为1,则深度为h的二叉树的最...
    99+
    2022-11-13
  • C语言中二叉树的后序遍历详解
    目录一.二叉树的后序遍历.(递归)二.二叉树的后序遍历(迭代)总结首先我们从两个方面讲解二叉树的后序遍历(递归+迭代) 一.二叉树的后序遍历.(递归) 思想: 首先我们从二叉树的根节...
    99+
    2022-11-13
  • C语言数据结构之堆排序详解
    目录1.堆的概念及结构2.堆的实现2.1 堆的向下调整算法2.2 堆的向上调整算法2.3 建堆(数组)2.4 堆排序2.5 堆排序的时间复杂度1.堆的概念及结构 如果有一个关键码的集...
    99+
    2022-11-13
  • c++深入浅出讲解堆排序和堆
    目录堆是什么最大堆最小堆堆排序最终代码关于堆堆是什么 堆是一种特殊的完全二叉树 如果你是初学者,你的表情一定是这样的 别想复杂 首先,你一定见过这种图 咱们暂时不管数字 这就是一个...
    99+
    2022-11-13
  • JAVA十大排序算法之堆排序详解
    目录堆排序知识补充二叉树满二叉树完全二叉树二叉堆代码实现时间复杂度算法稳定性思考总结堆排序 这里的堆并不是JVM中堆栈的堆,而是一种特殊的二叉树,通常也叫作二叉堆。它具有以下特点: ...
    99+
    2022-11-12
  • C语言堆与二叉树的顺序结构与实现
    目录一. 二叉树的顺序结构二. 堆的概念及结构三. 堆的实现四. 堆排序(具有缺陷型)一. 二叉树的顺序结构 普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全...
    99+
    2022-11-13
  • C++超详细分析优化排序算法之堆排序
    堆排序,学习了整整一天才把这个排序彻底搞明白…… 首先第一点,堆排序是直接选择排序的一种优化排序算法。由于直接排序算法的遍历次数过多,导致直接排序算法的时...
    99+
    2023-02-09
    C++堆排序 C++优化排序
  • C语言平衡二叉树详解
    目录调整措施:一、单旋转二、双旋转AVL树的删除操作:删除分为以下几种情况:1.要删除的节点是当前根节点T。2、要删除的节点元素值小于当前根节点T值,在左子树中进行删除。3、要删除的...
    99+
    2022-11-12
  • Python实现堆排序案例详解
    Python实现堆排序 一、堆排序简介 堆排序(Heap Sort)是利用堆这种数据结构所设计的一种排序算法。 堆的结构是一棵完全二叉树的结构,并且满足堆积的性质:每个节点(叶节点除...
    99+
    2022-11-12
  • C语言中堆排序怎么用
    小编给大家分享一下C语言中堆排序怎么用,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!一、堆排序的概念 堆排序(Heapsort):利用堆积树(堆)这种数据结构所设...
    99+
    2023-06-29
  • C++超详细讲解树与二叉树
    目录树树的定义树的名词解释树的表示树的存储结构二叉树的概念及结构二叉树的概念二叉树的性质二叉树的存储结构顺序存储结构链式存储结构树 树的定义 Q:什么是树 A:树是一种 非线性 的数...
    99+
    2022-11-13
  • C++二叉搜索树BSTree使用详解
    目录一、概念二、基础操作1.查找find2.插入Insert3.中序遍历InOrder4.删除erase三、递归写法1.递归查找2.递归插入3.递归删除四、应用五、题目练习一、概念 ...
    99+
    2023-03-09
    C++二叉搜索树BSTree C++二叉搜索树 C++ BSTree
  • C语言之平衡二叉树详解
    目录什么是平衡二叉树平衡二叉树的基本特点为什么会出现平衡二叉树二叉树四种不平衡的情况C语言实现平衡二叉树什么是平衡二叉树 平衡二叉树是具有平衡属性的有序二叉树,所谓的平衡即当前树的左...
    99+
    2023-05-17
    C语言二叉树 C语言平衡二叉树
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作