首页 > 编程语言 >6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)

6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)

时间:2024-09-04 22:21:38浏览次数:13  
标签:arr DataType 建堆 结点 c++ int 6.1 child hp

目录

一.堆(Heap)的基本介绍

二.堆的常用操作(以小根堆为例)

三.实现代码

3.1 堆结构定义

3.2 向下调整算法*

3.3 初始化堆*

3.4 销毁堆

3.4 向上调整算法*

 3.5 插入数据

3.6 删除数据

3.7 返回堆顶数据

四.下篇内容

1.堆排序

2.TopK问题


一.堆(Heap)的基本介绍

        了解堆之前我们要简单了解完全二叉树:        

        在二叉树中,我们使用指针来连接每一个结点,最后构成一颗二叉树。而堆是一种使用数组来表示完全二叉树。其满足以下两条规则。

        1.堆中结点值总是大于或者小于其父结点的值。

        2.堆总是一颗完全二叉树。

由此可以推出有两种堆:大根堆和小根堆。

大根堆:根节点的值最大。

小根堆:根节点的值最小。

在堆(二叉树)中,如果一个结点的下标为i

其父亲的结点的下标为 (i-1)/ 2

其左孩子结点的下标为 (i+1)*2 -1  即  i*2 +1

其右孩子结点的下标为 (i+1)*2      即  i*2 + 2

数组的下标由0开始,读者可根据下图进行理解

二.堆的常用操作(以小根堆为例)

//初始化堆
void HeapInit(Heap* php, DataType* arr, int n);

//数组建堆主要依赖的算法(这个算法要求数组的左右子树都是小堆)
//小堆,使用向下调整算法
void Adjustdown(DataType* arr, int n, int root);


//向上调整算法
void Adjustup(DataType* arr, int n, int root);

//销毁堆
void HeapDestory(Heap* php);

//插入数据
void HeapPush(Heap* php, DataType x);

//删除数据
void HeapPop(Heap* php, DataType x);

//求堆顶(根)数据
DataType HeadTop(Heap* php);

//交换两个数据
void swap(DataType* p1, DataType* p2);

三.实现代码

3.1 堆结构定义

//以小根堆为例
typedef int DataType;
typedef struct Heap
{
	DataType* arr;    //数组
	int capacity;     //容量
	int size;         //元素大小
}Heap;

3.2 向下调整算法*

        小根堆使用该算法的前提是左右子树都为小根堆,大根堆的前提是左右子树都为大根堆

该算法是从根结点依次向下找到比自己小(或者大)的结点,然后进行交换。

最后就能将新插入的根节点放到相应的位置

调整规则:

小根堆:根节点每一次与孩子结点中较小的一个交换

大根堆:根节点每一次与孩子结点中较大的一个交换

如下图

代码如下(以小根堆为例)

//向下调整算法
void AdjustDwon(DataType* arr, int n, int root)
{
	//1.小根堆,找出左右孩子中较小的结点
	int parent = root;
	int child = root * 2 + 1;	//表示左孩子
	while (child < n)
	{
		//找到右孩子,如果右孩子比左孩子小,让child++。注意必须存在右孩子才能这么做
		if (child + 1 < n && arr[child + 1] < arr[child])
		{
			child++;
		}

		//如果该孩子比父亲小,就要交换
		if (arr[child] < arr[parent])
		{
			swap(arr[child], arr[parent]);

			//向下继续调整
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			//如果孩子比父亲大,交换结束
			break;
		}
	}
}

3.3 初始化堆*

        初始化堆:将一个随机的数组(数组大小随机,元素大小也随机)转换为堆。

思路:

1.将一个数组拷贝到一个堆结构中

2.利用向下调整算法对整个数组进行调整,由于整个数组不能直接进行向下调整(左右子树不符合堆结构),所以我们使用向下调整算法堆 最后一个结点的父亲结点开始调整,然后依次对这个结点之前的结点开始调整。

3.最后得出完整的堆结构

流程图:

代码

//初始化堆
void HeapInit(Heap* hp, DataType* arr, int n)
{
	//开辟空间,大小为 DataType*n
	hp->arr = (DataType*)malloc(sizeof(DataType) * n);
	assert(hp->arr != nullptr);
	memcpy(hp->arr, arr, sizeof(DataType) * n);
	hp->size = n;
	hp->capacity = n;

	//拷贝好数据后,由于数据是随机的,所以我们使用调整算法建堆
	//我们从最后一个度为2的结点开始向前依次对每一个结点都进行向下调整
	//最后一个结点下标为 n-1 则其父亲结点为(n-1-1)/2
	for (int i = (n - 1 - 1) / 2; i > 0; i--)
	{
		Adjustdown(hp->arr, hp->size, i);
	}
}

3.4 销毁堆

//销毁堆
void HeapDestory(Heap* php)
{
	assert(php);
	free(php->arr);
	php->arr = NULL;
	php->size = 0;
	php->capacity = 0;
}

3.4 向上调整算法*

当我们插入新数据时,这个数据会破坏堆结构(如插入到数组末尾),所以我们需要向上调整

和向下调整算法类似

思路:

        让新增节点依次和自己的父亲比较,然后交换即可

        小根堆:比父亲小,交换。直到比父亲大就结束

        大根堆:比父亲大,交换。直到比父亲小就结束

流程图:

代码

//向上调整算法,以小根堆为例
void AdjustUp(DataType* arr, int n, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (arr[child] < arr[parent])
		{
			swap(arr[child], arr[parent]);
			
			//继续向上调整
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

 3.5 插入数据

//插入数据
void HeapPush(Heap* hp, DataType x)
{
	assert(hp);
	//1.增容
	if (hp->size == hp->capacity)
	{
		hp->capacity *= 2;
		DataType* tmp = (DataType*)realloc(hp->arr, sizeof(DataType) * hp->capacity);
		assert(tmp != NULL);
		hp->arr = tmp;
	}
	//2.在数组的插入数据
	hp->arr[hp->size] = x;
	hp->size++;

	//对数组进行向上调整,将小的数据向上调整
	Adjustup(hp->arr, hp->size, hp->size - 1);
}

3.6 删除数据

删除堆顶的数据

我们交换第一个数据和最后一个数据,然后删除最后一个数据。再对堆顶进行向下调整

这样就能满足删除后,整个堆还是满足规则的

//删除数据(删掉堆顶的数据)
//类似于堆排序,交换第一个和最后一个数据。保证根节点的左右子树都是小根堆
void HeapPop(Heap* hp)
{
	assert(hp);
	assert(hp->arr);
	swap(hp->arr[0], hp->arr[hp->size - 1]);
	hp->size--;
	Adjustdown(hp->arr, hp->size, 0);
}

3.7 返回堆顶数据

直接返回0下标处的数据即可

//求堆顶(根)数据
DataType HeadTop(Heap* hp)
{
	assert(hp);
	assert(hp->size > 0);
	return hp->arr[0];
}

四.下篇内容

1.堆排序

2.TopK问题

标签:arr,DataType,建堆,结点,c++,int,6.1,child,hp
From: https://blog.csdn.net/yzcllzx/article/details/141828602

相关文章

  • 面向对象程序设计之链表 list 的简析(C++)
    简介:链表是一个双向的结构,与string与vector不同的是他不支持[]访问,因为链表是由一个节点一个节点连接而成的,并不连续。我们可以在常数量级内对于链表进行插入与删除数据1.构造函数我们在cplusplus.com中可以查到链表总共有四种构造的方式:1.无参构造(默认构造);2.使用n个va......
  • 深入了解链表 list 之的模拟实现 list (C++)
    1.基本框架关于链表我们知道其是一个双向循环结构,并且由许多节点组成,各个节点之间内存空间不一定连续,每个节点均有前驱指针与后继指针,下面我们使用类模版来实现一个适用于存储大部分数据类型的链表,由下面代码我们可以看到一些基础框架与很简单的函数size返回长度与empty判断......
  • C++机试——查找组成一个偶数最近的两个素数
    题目描述任意一个偶数(大于2)都可以由2个素数组成,组成偶数的2个素数有很多种情况,本题目要求输出组成指定偶数的两个素数差值最小的素数对。数据范围:输入的数据满足 4≤n≤1000 4≤n≤1000 输入描述:输入一个大于2的偶数输出描述:从小到大输出两个素数思路      ......
  • C++语言基础--代码框架
    引入    工欲善其事,必先利其器。我们在编写C++代码之前,一定要了解到C++的代码框架。代码框架可以说是我们所有的C++代码都一定具备的。本章将详细解析C++的代码框架。代码框架#include<cstdio>#include<iostream>usingnamespacestd;intmain(){return......
  • C++:异常
    文章目录什么是异常?异常: 报错:一、异常的处理方式1.抛出异常2.捕获异常二、标准异常三、自定义异常什么是异常?异常: 异常这个概念可能会有一些陌生,但是str.at(i)我们并不陌生,当i值越界时就会产生一个异常语句:terminatecalledafterthrowinganinstanceof......
  • windows C++ 并行编程-并发和UWP(三)
    控制执行线程Windows运行时使用COM线程模型。在此模型中,根据对象处理其同步的方式,对象被托管在不同的单元中。线程安全对象托管在多线程单元(MTA)中。必须通过单个线程访问的对象托管在单线程单元(STA)中。在具有UI的应用程序中,ASTA(应用程序STA)线程负责发送窗......
  • windows C++ 并行编程-并发和UWP(一)
    本文介绍当在通用Windows运行时(UWP)应用中使用任务类生成基于Windows线程池的异步操作时要谨记的一些要点。异步编程的使用是Windows运行时应用模型中的关键组成部分,因为它能使应用保持对用户输入的响应。可以启动长期运行的任务,而不必阻止UI线程,并且可以在以后接......
  • linux C++基于共享内存的同步机制
    无缘进程间同步,本来打算使用有名信号量进行同步,但是有名信号量的初始化会受进程启动顺序影响,故使用共享内存进行封装,封装后的使用方法类似二值信号量,代码如下:1#include<sys/ipc.h>//ipc:inter-processcommunication进程通信2#include<sys/shm.h>//shm:shareme......
  • 坐牢第三十五天(c++)
    一.作业1.使用模版类自定义栈代码:#include<iostream>usingnamespacestd;template<typenameT>//封装一个栈classstcak{private:T*data;//intmax_size;//最大容量inttop;//下标public://无参构造函数stcak();//......
  • 2024.9.4C++作业
    #include<iostream>#include<string>usingnamespacestd;classHuman{public:Human(){name="Unknown";age=0;}Human(stringn,inta){name=n;age=a;}~Hu......