C++数据结构之堆详解

目录
  • 堆的概念
    • 提示:完全二叉树
  • 堆的性质
  • 最大堆最小堆
  • 代码
    • 定义
      • 有限数组形式
      • 动态数组形式
    • 操作
      • 向下调整结点
      • 建立堆
      • 初始化
      • 打印堆
    • 测试
      • main函数
      • 结果
    • 完整代码

堆的概念

堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象,即是一种顺序储存结构的完全二叉树。

提示:完全二叉树

完全二叉树:对一棵深度为k、有n个结点二叉树编号后,各节点的编号与深度为k的满二叉树相同位置的结点的编号相同,这颗二叉树就被称为完全二叉树。[2]

堆的性质

  • 堆中某个结点的值总是不大于或不小于其父结点的值
  • 堆总是一棵完全二叉树
  • 除了根结点和最后一个左子结点可以没有兄弟结点,其他结点必须有兄弟结点

最大堆最小堆

  • 最大堆:根结点的键值是所有堆结点键值中最大者,且每个结点的值都比其孩子的值大。
  • 最小堆:根结点的键值是所有堆结点键值中最小者,且每个结点的值都比其孩子的值小。

代码

定义

有限数组形式

int Heap[1024];    //顺序结构的二叉树

若某结点编号为i,且存在左儿子和右儿子,则他们分别对应

Heap[i*2+1];      //左儿子
Heap[i*2+2];      //右儿子

其父节点

Heap[i/2];		//i的父节点

动态数组形式

在项目开发中,经常以动态数组形式出现,在本文主要对这种形式进行介绍

//默认容量
#define DEFAULT_CAPCITY 128

typedef struct _Heap
{
	int *arr;		//储存元素的动态数组
	int size;		//元素个数
	int capacity;	//当前存储的容量
}Heap;

操作

只使用InitHeap()函数进行初始化即可,AdjustDown()与BuildHeap()仅为堆建立时的内部调用

向下调整结点

  • 以创建最大堆为例
  • 将“判断最大子结点是否大于当前父结点”处的>=改为<=即可创建最小堆
//向下调整,将当前结点与其子结点调整为最大堆
//用static修饰禁止外部调用
static void AdjustDown(Heap& heap, int index)
{
	int cur = heap.arr[index];	//当前待调整结点
	int parent, child;

	/*
		判断是否存在子结点大于当前结点。
		若不存在,堆是平衡的,则不调整;
		若存在,则与最大子结点与之交换,交换后该子节点若还有子结点,则以此方法继续调整。
	*/
	for (parent = index; (parent * 2 + 1) < heap.size; parent = child)
	{
		child = parent * 2 + 1;	//左子结点

		//取两个子结点中最大节点,(child+1)<heap.size防止越界
		if (((child + 1) < heap.size && (heap.arr[child] < heap.arr[child + 1])))
			child++;

		//判断最大子结点是否大于当前父结点
		if (cur >= heap.arr[child])	//将此处的>=改成<=可构建最小堆
		{
			//不大于,不需要调整
			break;
		}
		else
		{
			//大于,则交换
			heap.arr[parent] = heap.arr[child];
			heap.arr[child] = cur;
		}

	}
}

建立堆

//建立堆,用static修饰禁止外部调用
static void BuildHeap(Heap& heap)
{
	int i;
	//从倒数第二层开始调整(若只有一层则从该层开始)
	for (i = heap.size / 2 - 1; i >= 0; i--)
	{
		AdjustDown(heap, i);
	}
}

初始化

//初始化堆
//参数:被初始化的堆,存放初始数据的数组, 数组大小
bool InitHeap(Heap &heap, int *orginal, int size)
{
	//若容量大于size,则使用默认量,否则为size
	int capacity = DEFAULT_CAPCITY>size?DEFAULT_CAPCITY:size;

	heap.arr = new int[capacity];	//分配内存,类型根据实际需要可修改
	if(!heap.arr) return false;		//内存分配失败则返回False

	heap.capacity = capacity;		//容量
	heap.size = 0;					//元素个数置为0

	//若存在原始数组则构建堆
	if(size>0)
	{
		/*
		内存拷贝,将orginal的元素拷贝到堆数组arr中
		size*sizeof(int)为元素所占内存空间大小
		*/
		memcpy(heap.arr,orginal, size*sizeof(int));
		heap.size = size;	//调整大小
		BuildHeap(heap);	//建堆
	}

	return true;
}

打印堆

  • 实际上是一个层序遍历[4]
//以树状的形式打印堆
void PrintHeapAsTreeStyle(Heap hp)
{
	queue<int> que;
	int r = 0;
	que.push(r);
	queue<int> temp;

	while (!que.empty())
	{
		r = que.front();
		que.pop();

		if (r * 2 + 1 < hp.size) temp.push(r * 2 + 1);
		if (r * 2 + 2 < hp.size) temp.push(r * 2 + 2);

		if (que.empty())
		{
			cout << hp.arr[r] << endl;
			que = temp;
			temp = queue<int>();
		}
		else
			cout << hp.arr[r] << " ";

	}
}

测试

main函数

int main()
{
	Heap hp;
	int vals[] = { 1,2,3,87,93,82,92,86,95 };

	if (!InitHeap(hp, vals, sizeof(vals) / sizeof(vals[0])))
	{
		fprintf(stderr, "初始化堆失败!\n");
		exit(-1);
	}

	PrintHeapAsTreeStyle(hp);

	return 0;
}

结果

95
93 92
87 1 82 3
86 2

完整代码

#include <iostream>
#include <queue>

using namespace std;

//默认容量
#define DEFAULT_CAPCITY 128
typedef struct _Heap {
	int* arr;
	int size;
	int capacity;
}Heap;

//向下调整,将当前结点与其子结点调整为最大堆
static void AdjustDown(Heap& heap, int index)
{
	int cur = heap.arr[index];	//当前待调整结点
	int parent, child;

	/*
		判断是否存在子结点大于当前结点。
		若不存在,堆是平衡的,则不调整;
		若存在,则与最大子结点与之交换,交换后该子节点若还有子结点,则以此方法继续调整。
	*/
	for (parent = index; (parent * 2 + 1) < heap.size; parent = child)
	{
		child = parent * 2 + 1;	//左子结点

		//取两个子结点中最大节点,(child+1)<heap.size防止越界
		if (((child + 1) < heap.size && (heap.arr[child] < heap.arr[child + 1])))
			child++;

		//判断最大子结点是否大于当前父结点
		if (cur >= heap.arr[child])	//将此处的>=改成<=可构建最小堆
		{
			//不大于,不需要调整
			break;
		}
		else
		{
			//大于,则交换
			heap.arr[parent] = heap.arr[child];
			heap.arr[child] = cur;
		}

	}

}

//建立堆,用static修饰禁止外部调用
static void BuildHeap(Heap& heap)
{
	int i;
	//从倒数第二层开始调整(若只有一层则从该层开始)
	for (i = heap.size / 2 - 1; i >= 0; i--)
	{
		AdjustDown(heap, i);
	}
}

//初始化堆
//参数:被初始化的堆,存放初始数据的数组, 数组大小
bool InitHeap(Heap& heap, int* orginal, int size)
{
	//若容量大于size,则使用默认量,否则为size
	int capacity = DEFAULT_CAPCITY > size ? DEFAULT_CAPCITY : size;

	heap.arr = new int[capacity];	//分配内存,类型根据实际需要可修改
	if (!heap.arr) return false;		//内存分配失败则返回False

	heap.capacity = capacity;		//容量
	heap.size = 0;					//元素个数置为0

	//若存在原始数组则构建堆
	if (size > 0)
	{
		/*
		内存拷贝,将orginal的元素拷贝到堆数组arr中
		size*sizeof(int)为元素所占内存空间大小
		*/
		memcpy(heap.arr, orginal, size * sizeof(int));
		heap.size = size;	//调整大小
		BuildHeap(heap);	//建堆
	}

	return true;
}

//以树状的形式打印堆
void PrintHeapAsTreeStyle(Heap hp)
{
	queue<int> que;
	int r = 0;
	que.push(r);
	queue<int> temp;

	while (!que.empty())
	{
		r = que.front();
		que.pop();

		if (r * 2 + 1 < hp.size) temp.push(r * 2 + 1);
		if (r * 2 + 2 < hp.size) temp.push(r * 2 + 2);

		if (que.empty())
		{
			cout << hp.arr[r] << endl;
			que = temp;
			temp = queue<int>();
		}
		else
			cout << hp.arr[r] << " ";

	}

}

int main()
{
	Heap hp;
	int vals[] = { 1,2,3,87,93,82,92,86,95 };

	if (!InitHeap(hp, vals, sizeof(vals) / sizeof(vals[0])))
	{
		fprintf(stderr, "初始化堆失败!\n");
		exit(-1);
	}

	PrintHeapAsTreeStyle(hp);

	return 0;
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • C++ 数据结构 堆排序的实现

    堆排序(heapsort)是一种比较快速的排序方式,它的时间复杂度为O(nlgn),并且堆排序具有空间原址性,任何时候只需要有限的空间来存储临时数据.我将用c++实现一个堆来简单分析一下. 堆排序的基本思想为: 1.升序排列,保持大堆:降序排列,保持小堆: 2.建立堆之后,将堆顶数据与堆中最后一个数据交换,堆大小减一,然后向下调整:直到堆中只剩下一个有效值: 下面我将简单分析一下: 第一步建立堆: 1.我用vector顺序表表示数组: 2.用仿函数实现大小堆随时切换,实现代码复用: 3.实现向下

  • C++实现堆排序示例

    目录 堆的实现 Heap.h 堆的管理及接口 Heap.c 堆各个接口功能的实现 test.c测试 堆的实现 Heap.h 堆的管理及接口 #include<stdio.h> #include<stdlib.h> #include<assert.h> typedef int HPDataType; typedef struct Heap { HPDataType* a; int size; int capacity; }Heap; //堆的向下调整算法 void Adj

  • 详解C++内存的代码区,全局区,栈区和堆区

    目录 代码区: 全局区: 栈区 堆区 总结 今天无意中刷到了一篇关于c++内存的帖子,我发现那个人好像写的不太对,然后同时我自己也发现我对一块还不够了解,所以我干脆就自己去了解整理了一下:首先我们要大概知道四个区都是干什么的 代码区: 顾名思义,就是存放我们写的代码的地方,不过要注意的是存放的是二进制代码. 注意:我们写的所有的写的代码(包括注释.变量.语句等)都会放到代码区中. 全局区: 存放全局,静态变量以及常量. 注意: 1.全局区里有一个部分叫常量区,储存的是常量,如const修饰的全局

  • C++ 自由存储区是否等价于堆你知道吗

    目录 free store" VS "heap" 问题的来源 结论 free store" VS "heap" 当我问你C++的内存布局时,你大概会回答: "在C++中,内存区分为5个区,分别是堆.栈.自由存储区.全局/静态存储区.常量存储区". 如果我接着问你自由存储区与堆有什么区别,你或许这样回答: "malloc在堆上分配的内存块,使用free释放内存,而new所申请的内存则是在自由存储区上,使用delete来

  • C/C++中栈(stack)&堆(heap)详解及其作用介绍

    目录 概述 程序运行中的栈和堆 堆和栈的差异 申请方式和回收方式 申请后系统的响应 申请效率比较 申请大小的限制 堆和栈中的存储内容 概述 栈 (stack) 是为执行线程流出的内存空间. 堆 (head) 是为动态分配预留的空间. 程序运行中的栈和堆 我们以一段代码来举例: #include <iostream> using namespace std; int a = 0; // 全局初始化区 char *pt; // 全局未初始化 int main() { int b; // b在栈区

  • C++基础算法基于哈希表的索引堆变形

    目录 问题来源 问题简述 问题分析 代码展示 问题来源 此题来自于Hackerrank中的QHEAP1问题,考查了对堆结构的充分理解.成功完成此题,对最大堆或者最小堆的基本操作实现就没什么太大问题了. 问题简述 实现一个最小堆,对3种类型的输入能给出正确的操作: "1 v" - 表示往堆中增加一个值为v的元素 "2 v" - 表示删去堆中值为v的元素 "3" - 表示打印出堆中最小的那个元素 注意:题目保证了要删的元素必然是在堆中的,并且在任何时

  • C++程序内存栈区与堆区模型案例分析

    目录 栈区: 栈区代码演示: 堆区: 堆区代码演示: new操作符: new操作符代码演示: 栈区: 由编译器自动分配释放,存放函数的参数值,局部变量等(由编译器管理其“生死”) 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放 栈区代码演示: //内存四区-栈区 /* 栈区: 由编译器自动分配释放,存放函数的参数值,局部变量等(由编译器管理其"生死") 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放 */ #include <iostream&

  • C++实现堆排序实例介绍

    目录 概述: 思路: 代码: 概述: 堆排序是利用构建"堆"的方法确定具有最大值的数据元素,并把该元素与最后位置上的元素交换.可将任意一个由n个数据元素构成的序列按照(a1,a2,...,an),按照从左到右的顺序按层排列构成一棵与该序列对应的完全二叉树. 一棵完全二叉树是一个堆,当且仅当完全二叉树的每棵子树的根值ai≥其左子树的根值a2i,同时ai≥其右子树的根值a 2i+1 (1<i<n/2). 实现堆排序需要实现两个问题: 如何由无序序列建成一个堆?如何在输出堆顶元素

  • c++深入浅出讲解堆排序和堆

    目录 堆是什么 最大堆 最小堆 堆排序 最终代码 关于堆 堆是什么 堆是一种特殊的完全二叉树 如果你是初学者,你的表情一定是这样的

  • 详解c/c++链式堆栈描述进制转换问题示例

    目录 创建栈结构 代码实现 基础操作需要创建链表来存储数据 使用尾插法和尾删法来表示栈中的入栈和出栈 typedef struct node { int data; struct node* next; }Node,*LPNode; LPNode creatnode(int data) { LPNode newnode = (LPNode)malloc(sizeof(Node)); assert(newnode); newnode->data = data; newnode->next = N

随机推荐