数据结构:二叉树的顺序结构--堆

这篇具有很好参考价值的文章主要介绍了数据结构:二叉树的顺序结构--堆。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

朋友们、伙计们,我们又见面了,本期来给大家解读一下二叉树--堆的相关知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!

C 语 言 专 栏:C语言:从入门到精通

数据结构专栏:数据结构

个  人  主  页 :stackY、

目录

前言:

1.堆的概念及结构

2.堆的实现

2.1创建堆

2.2初始化堆

2.3向堆中插入数据

2.4向上调整算法 

2.5判断堆是否为空

2.6删除堆中的数据

2.7向下调整算法

2.8有效元素个数、堆顶元素

2.9代码测试 

2.10完整代码


前言:

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结 构存储。
现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统 虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
数据结构:二叉树的顺序结构--堆

1.堆的概念及结构

如果有一个关键码的集合K = {},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:数据结构:二叉树的顺序结构--堆数据结构:二叉树的顺序结构--堆(i = 0,1,2,3,4,......n-1)被称为小堆或数据结构:二叉树的顺序结构--堆数据结构:二叉树的顺序结构--堆 (i = 0,1,2,3,4,......n-1)被称为大堆,将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
图示:
数据结构:二叉树的顺序结构--堆

简单的来讲:

1.大堆就是每一个孩子节点都要比它的父亲节点小,这只是规定了父亲节点和孩子节点之间的大小关系,并没有规定兄弟节点之间的关系,两个兄弟节点的大小不确定

2. 小堆就是每一个孩子节点都要比它的父亲节点大,这只是规定了父亲节点和孩子节点之间的大小关系,并没有规定兄弟节点之间的关系,两个兄弟节点的大小不确定

因此堆并不一定是有序的。

堆的性质:
1.堆中某个节点的值总是不大于或不小于其父节点的值。
2.堆总是一棵完全二叉树
数据结构:二叉树的顺序结构--堆

2.堆的实现

堆是一种特殊的完全二叉树,因此实现堆的思路就是使用顺序表,同样的我们也是分模块来写,创建头文件:Heap.h、源文件:Heap.c和Test.c,堆在这里主要实现的接口是:初始化、插入,删除(删除堆顶元素)、判空、有效元素个数、堆顶元素、销毁。话不多说我们直接开始:

注意:这里实现的是以小堆为例

2.1创建堆

堆的创建就是创建一个顺序表,然后再进行堆的一系列操作:

头文件:Heap.h

//创建堆
//顺序表
typedef int HPDataType;

typedef struct Heap
{
	HPDataType* a;
	int size = 0;  //有效元素个数
	int capacity;  //容量
}Heap;

2.2初始化堆

这些接口实现都比较简单,直接上手即可:

头文件:Heap.h

//初始化
void HeapInit(Heap* php);

源文件:Heap.c

//初始化
void HeapInit(Heap* php)
{
	php->a = NULL;  
	php->size = 0;
	php->capacity = 0;
}

2.3向堆中插入数据

插入堆在这里插入的是堆的最后面,对应的也就是顺序表的最后一个位置,当插入进去之后还面临一个问题:我们创建的就是小堆,那么插入的数据如果小于它的父亲,就需要向上调整,如果调整之后还小于它新的父亲的,那么还是得继续向上调整,直到符合小根堆的逻辑

数据结构:二叉树的顺序结构--堆

因此插入过程可以分为这么几步:

1. 先为堆开辟一块空间。

2. 检测容量,不够还需扩容。

3. 插入,判断是否需要进行向上调整。

头文件:Heap.h

//插入
void HeapPush(Heap* php, HPDataType x);

源文件:Heap.c

//插入
void HeapPush(Heap* php, HPDataType x)
{
	assert(php);
	//插入数据的过程
	//容量不够则需要扩容
	//判断空间是否不足
	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
		//为堆开辟空间
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		php->capacity = newcapacity;
		php->a = tmp;
	}
	//插入数据
	php->a[php->size] = x;
	php->size++;
	//向上调整
	AdjustUp(php->a, php->size - 1);
}

向上调整算法我们再来分装一个函数

2.4向上调整算法 

向上调整我们的第一步首先要找它的父亲节点(在顺序表的逻辑下:如果它是左孩子,那么它的父亲节点的下标就是它的下标-1然后除2,如果它是右孩子,那么它的父亲节点的下标就是它的下标-2然后除2,但是呢?我们进行的是整数除法,没有余数,因此无论是左孩子还是右孩子都是使用-1除2的方法进行计算)。第二步判断孩子是否小于父亲,小于的话孩子和父亲就要交换位置,大于的话表示符合小堆逻辑,直接跳出即可,然后再让交换后的父亲节点变为新的孩子节点,然后再求新的父亲节点,依次类推,直到调整到堆顶,堆顶元素的下标为0,那么就表示孩子节点的下标为0时就不需要再调整了,退出即可:

数据结构:二叉树的顺序结构--堆

 因此孩子与父亲之间的关系可以总结为:

int Child = Parent*2+1;   //左孩子
int Child = Parent*2+2;  //右孩子
int Parent = (Child-1)/2;  //父亲

源文件:Heap.c

//交换函数
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//向上调整算法
void AdjustUp(HPDataType* a, int child)
{
	//父节点
	int parent = (child - 1) / 2;
	//向上交换
	while (child > 0)
	{
		//判断父子大小关系
		if (a[child] < a[parent])
		{		
			//交换
			Swap(&a[child], &a[parent]);
			//更新父子关系
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

2.5判断堆是否为空

堆为空的条件就是堆中的有效元素个数为0。

头文件:Heap.h

//判空
bool HeapEmpty(Heap* php);

源文件:Heap.c

//判空
bool HeapEmpty(Heap* php)
{
	assert(php);
	return php->size == 0;
}

2.6删除堆中的数据

删除堆元素时并不是删除最后一个元素,这样删除并没有什么意义,因此删除堆中的元素时删除的是堆顶的元素,那么这里就有两种方法:

1. 和顺序表的头删一样,直接挪动数据进行覆盖,但是这样做时间复杂度高,而且挪动数据本来就是一件代价比较大的事情,再加上如果挪动数据进行覆盖的话就会将原本的父子关系打乱,父子关系打乱就有点扯淡了。

数据结构:二叉树的顺序结构--堆

2. 将堆顶的数据和最后一个数据交换,然后删除掉最后一个数据,这时将剩下的元素进行向下调整。

数据结构:二叉树的顺序结构--堆

所以删除数据可以分为这几步:

1. 先判断堆是否为空,为空就不能再删除。

2. 再交换顶部和最后的数据。

3. 再将最后一个元素删除。

4. 再判断是否需要向下调整。

头文件:Heap.h

//删除
void HeapPop(Heap* php);

源文件:Heap.c

void HeapPop(Heap* php)
{
	assert(php);
	//判断堆是否为空
	assert(!HeapEmpty(php));
	//交换堆顶的数据和最后的数据
	Swap(&php->a[0], &php->a[php->size - 1]);
	//删除最后的数据
	php->size--;
	//向下调整
	AdjustDown(php->a, php->size, 0);
}

向下调整算法我们再来分装一个函数

2.7向下调整算法

向下调整算法首先我们需要根据孩子与父亲的下标关系找到堆顶元素的左右孩子,然后找出左右孩子中最小的孩子,然后将其交换,然后再将孩子的位置更新为父节点,再继续找新的父亲节点的左右孩子的最小的节点,再进行交换,依次类推,直到交换到最后一个孩子节点(孩子节点小于等于总结点个数)。

找左右孩子中的最小的我们可以使用假设法,假设左孩子为最小的,然后进行判断,如果不合适,就给左孩子加1,从而得到右孩子,这里还需要注意,如果没有右孩子呢?那就只能和左孩子比较,也就不需要进行比较了(左孩子加一小于等于总结点个数)。

所以我们在设置向下调整算法的时候需要传堆、元素个数、堆顶的下标。

数据结构:二叉树的顺序结构--堆

 源文件:Heap.c

//向下调整
void AdjustDown(HPDataType* a, int n, int parent)
{
	//假设左孩子为左右孩子中最小的节点
	int child = parent * 2 + 1;
	
	while (child < n)  //当交换到最后一个孩子节点就停止
	{
		if (	child + 1 < n  //判断是否存在右孩子
			&& a[child + 1] < a[child]) //判断假设的左孩子是否为最小的孩子
		{
			child++;   //若不符合就转化为右孩子
		}
		//判断孩子和父亲的大小关系
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			//更新父亲和孩子节点
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

注意:

1. 这里的向上调整算法和向下调整算法只适用于堆的操作。

2. 向上调整算法和向下调整算法的时间复杂度是O(logN)。

向上调整和向下调整的最好情况是一次都不调整,最坏的情况是调整一个完全二叉树的高度次,又因为完全二叉树的结点个数为一个范围[ 2^(h-1), 2^h-1]。所以将高度于h于结点个数N结合在一起可以算出高度为[ logN+1,log(N+1)],因此算法的时间复杂度为O(logN)。

2.8有效元素个数、堆顶元素

1. 返回堆顶元素时先要判断堆是否为空,堆顶元素的下标就是0,因此直接返回下标为0的元素。

2. 有效元素的个数就是顺序表中的size,直接返回即可。

头文件:Heap.h

//有效元素个数
int HeapSize(Heap* php);

//获取堆顶元素
HPDataType HeapTop(Heap* php);

源文件:Heap.c

//有效元素个数
int HeapSize(Heap* php)
{
	assert(php);
	return php->size;
}

//获取堆顶元素
HPDataType HeapTop(Heap* php)
{
	assert(php);
	//判断堆是否为空
	assert(!HeapEmpty(php));
	return php->a[0];
}

2.9代码测试 

插入测试:

写完了堆的基本接口之后我们可以调试通过监视窗口来观察一下,我们将一个数据依次插入到堆中,观察他们的变化:

源文件:Test.c

#include "Heap.h"

void HeapTest()
{
	Heap hp;

	//初始化
	HeapInit(&hp);

	//插入
	int arr[] = { 8,5,6,2,1,0,3,4,7,9 };
	for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
	{
		HeapPush(&hp, arr[i]);
	}

}

int main()
{
	HeapTest();
	return 0;
}

数据结构:二叉树的顺序结构--堆

删除测试: 

#include "Heap.h"

void HeapTest()
{
	Heap hp;

	//初始化
	HeapInit(&hp);

	//插入
	int arr[] = { 8,5,6,2,1,0,3,4,7,9 };
	for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
	{
		HeapPush(&hp, arr[i]);
	}

	//删除
	HeapPop(&hp);

}

int main()
{
	HeapTest();
	return 0;
}

数据结构:二叉树的顺序结构--堆

2.10完整代码

头文件:Heap.h

#pragma once

//    堆

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

//创建堆
//顺序表
typedef int HPDataType;

typedef struct Heap
{
	HPDataType* a;
	int size;  //有效元素个数
	int capacity;  //容量
}Heap;

//初始化
void HeapInit(Heap* php);

//销毁
void HeapDestroy(Heap* php);

//插入
void HeapPush(Heap* php, HPDataType x);

//删除
void HeapPop(Heap* php);

//判空
bool HeapEmpty(Heap* php);

//有效元素个数
int HeapSize(Heap* php);

//获取堆顶元素
HPDataType HeapTop(Heap* php);

源文件:Heap.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "Heap.h"

//初始化
void HeapInit(Heap* php)
{
	php->a = NULL;  
	php->size = 0;
	php->capacity = 0;
}

//销毁
void HeapDestroy(Heap* php)
{
	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 0;
}

//交换函数
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//向上调整算法
void AdjustUp(HPDataType* a, int child)
{
	//父节点
	int parent = (child - 1) / 2;
	//向上交换
	while (child > 0)
	{
		//判断父子大小关系
		if (a[child] < a[parent])
		{		
			//交换
			Swap(&a[child], &a[parent]);
			//更新父子关系
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

//插入
void HeapPush(Heap* php, HPDataType x)
{
	assert(php);
	//插入数据的过程
	//容量不够则需要扩容
	//判断空间是否不足
	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
		//为堆开辟空间
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		php->capacity = newcapacity;
		php->a = tmp;
	}
	//插入数据
	php->a[php->size] = x;
	php->size++;
	//向上调整
	AdjustUp(php->a, php->size - 1);
}

//向下调整
void AdjustDown(HPDataType* a, int n, int parent)
{
	//假设左孩子为左右孩子中最小的节点
	int child = parent * 2 + 1;
	
	while (child < n)  //当交换到最后一个孩子节点就停止
	{
		if (	child + 1 < n  //判断是否存在右孩子
			&& 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 HeapPop(Heap* php)
{
	assert(php);
	//判断堆是否为空
	assert(!HeapEmpty(php));
	//交换堆顶的数据和最后的数据
	Swap(&php->a[0], &php->a[php->size - 1]);
	//删除最后的数据
	php->size--;
	//向下调整
	AdjustDown(php->a, php->size, 0);
}


//判空
bool HeapEmpty(Heap* php)
{
	assert(php);
	return php->size == 0;
}

//有效元素个数
int HeapSize(Heap* php)
{
	assert(php);
	return php->size;
}

//获取堆顶元素
HPDataType HeapTop(Heap* php)
{
	assert(php);
	//判断堆是否为空
	assert(!HeapEmpty(php));
	return php->a[0];
}


源文件:Test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "Heap.h"

void HeapTest()
{
	Heap hp;

	//初始化
	HeapInit(&hp);

	//插入
	int arr[] = { 8,5,6,2,1,0,3,4,7,9 };
	for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
	{
		HeapPush(&hp, arr[i]);
	}

	//删除
	//HeapPop(&hp);

}

int main()
{
	HeapTest();
	return 0;
}

朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!文章来源地址https://www.toymoban.com/news/detail-460107.html

到了这里,关于数据结构:二叉树的顺序结构--堆的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 初阶数据结构之---二叉树的顺序结构-堆

    今天要讲的堆,不是操作系统虚拟进程地址空间中(malloc,realloc等开空间的位置)的那个堆,而是数据结构中的堆,它们虽然名字相同,却是截然不同的两个概念。堆的底层其实是 完全二叉树 ,如果你问我,完全二叉树是什么。好吧,那我先从树开始讲起,开始我们今天的

    2024年03月14日
    浏览(41)
  • 数据结构初阶--二叉树的顺序结构之堆

    目录 一.堆的概念及结构 1.1.堆的概念 1.2.堆的存储结构 二.堆的功能实现 2.1.堆的定义 2.2.堆的初始化 2.3.堆的销毁 2.4.堆的打印 2.5.堆的插入 向上调整算法 堆的插入 2.6.堆的删除 向下调整算法 堆的删除 2.7.堆的取堆顶元素 2.8.堆的判空 2.9.堆的求堆的大小 三.堆的创建 3.1.向上调

    2024年02月14日
    浏览(25)
  • 二叉树的顺序结构以及堆的实现——【数据结构】

    W...Y的主页 😊 代码仓库分享  💕 上篇文章,我们认识了什么是树以及二叉树的基本内容、表示方法……接下来我们继续来深入二叉树,感受其中的魅力。 目录  二叉树的顺序结构 堆的概念及结构 堆的实现   堆的创建  堆的初始化与释放空间  堆的插入 堆的删除  堆实

    2024年02月07日
    浏览(24)
  • 数据结构——二叉树的基本概念及顺序存储(堆)

    目录 一.前言 二.树概念及结构 2.1 树的概念 2.2 树的相关概念 2.3 树的表现 2.4 树在实际中的应用(表示文件系统的目录树结构) 三.二叉树的概念及结构 3.1 概念 3.2 特殊的二叉树 3.3 二叉树的性质 3.4 二叉树的存储结构 3.4.1 顺序存储 3.4.2 链式存储 四.二叉树顺序结构及实现

    2024年02月08日
    浏览(21)
  • 【数据结构】二叉树的顺序结构实现及时间复杂度计算(二)

    目录 一,二叉树的顺序结构实现         1,二叉树的顺序结构         2,堆的概念及结构         3,堆的接口实现 1,堆的创建 2,接口函数 3,初始化 4,销毁 5,是否增容 6,交换数据 7,堆向上调整算法 8,插入数据 9,删除数据 10,堆向下调整算法 11,打印数

    2024年02月09日
    浏览(19)
  • 【数据结构—二叉树的基础知识介绍和堆的实现(顺序表)】

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 1.树概念及结构 1.1树的概念 1.2 树的相关概念  1.3 树的表示 1.4 树在实际中的运用(表示文件系统的目录树结构) 2.二叉树概念及结构 2.1概念 2.2 特殊的二叉树: 2.3 二叉树的存储结构

    2024年02月03日
    浏览(22)
  • 数据结构入门(C语言版)二叉树的顺序结构及堆的概念及结构实现应用

    普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用 顺序结构的数组来存储 ,需要注意的是 这里的堆和操作系统虚拟进程地址空间中的堆是两回事 ,一个是 数据结构 ,一

    2023年04月19日
    浏览(27)
  • 数据结构(C语言实现)——二叉树的概念及二叉树顺序结构和链式结构的实现(堆排序+TOP-K问题+链式二叉树相关操作)

    前面学习了数据结构中线性结构的几种结构,顺序表,链表,栈和队列等,今天我们来学习一种非线性的数据结构——树。由于二叉树是数据结构中的一个重点和难点,所以本文着重介绍二叉树的相关概念和性质,以及二叉树的应用。 树是一种非线性的数据结构,它是由n(

    2023年04月21日
    浏览(28)
  • 探索树形数据结构,通识树、森林与二叉树的基础知识(专有名词),进一步利用顺序表和链表表示、遍历和线索树形结构

      结点之间有分支,具有层次关系 树的定义 : 树 (tree)是n(n≥0)个有限集。 若n = 0,则称为空树; 若n 0,则它满足如下两个条件: 有且仅有一个特定的称为根(Root)的结点; 其余结点可分为m(m≥0)个互不相交的有限集T1,T2,T3,.....,Tm,其中每一个集合本身又是一棵树,并称为根的

    2024年02月01日
    浏览(23)
  • 【数据结构】二叉树——顺序结构

    由于每个节点都 只有一个父节点 ,所以我们可通过双亲来表示一棵树。具体方式通过 数组的形式 实现。 根节点的下标为0 按照层序从上到下排序 每层从左向右递增 表示形式: 二维数组 数据的列标为0 ,只需确定行标,即可锁定位置 根节点的父节点下标为 -1 列标为1存父节

    2024年02月02日
    浏览(26)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包