数据结构——堆

本文详细介绍了树的基本概念,包括根、父、子、叶节点以及度的概念,重点讨论了二叉树和堆的特性,包括完全二叉树、小堆和大堆的定义,以及如何通过递归和数组实现建堆和堆排序的过程。
摘要由CSDN通过智能技术生成

一、树

树是一种非线性数据结构,它由节点(node)和连接这些节点的边(edge)组成。树的基本结构类似于真实世界中的树,有根(root)、枝干(branches)、叶子(leaves)等概念。

树结构中有几个重要的概念:

  1. 根节点(Root):树的顶端节点,是树的起始点,其上没有父节点。

  2. 父节点(Parent):一个节点连接着它下面的节点,这个节点就是下面节点的父节点。

  3. 子节点(Child):一个节点下面连接着的节点,这些节点就是该节点的子节点。

  4. 叶子节点(Leaf):没有子节点的节点称为叶子节点,也可以简称为叶子。

  5. 树的高度(Depth):从根节点到该节点的唯一路径上的节点数目。

  6. 节点的高度(Height):从该节点到叶子节点的最长路径上的节点数目。

  7. 子树(Subtree):树中的任意一个节点和其所有子节点组成的结构。

  8. :在树结构中,度是指一个节点拥有的子节点的数量。节点的度可以分为入度和出度两种情况:

  9. 入度:对于有向树(Directed Tree),一个节点的入度是指指向该节点的边的数量。
  10. 出度:一个节点的出度是指从该节点出发的边的数量。

树结构的应用非常广泛,在计算机科学中,树被用于实现各种数据结构和算法,如二叉搜索树、堆、树状数组等。在数据库系统中,树结构也被用于构建索引,提高数据的检索效率。在人工智能领域,决策树被用于分类和回归分析等任务。

二、二叉树和堆

1、与单纯的树不同,二叉树的一个父节点的最多有两个子节点(也可能是两颗子树),而二叉树中又有完全二叉树满二叉树,完全二叉树至多有一个度为一的结点,而且叶子结点是从左向右分布。

堆就是通过模拟完全二叉树完成的,但是底层是数组,因为完全二叉树的父节点与子节点排列顺序存在一个规律。

有了这个规律我们就可以像操作二叉树一样操作数组。

2、大堆和小堆

在小堆中父节点的值是小于子节点的,而大堆中父节点是大于子节点的。

3、建堆

建堆的过程就是调整子节点和父节点的关系

这几个图是连续的,圈起来的数字是表示步骤顺序

过程知道了,代码怎么写

首先搞一个结构体,嫌麻烦可以不搞,准备一个数组就好了

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int Data;
typedef struct STP
{
	Data* x;
	int top;//元素个数
	int size;
	int capacity;//数组大小
}TSP;
void swap(Data* x,Data* y);//交换

void adjust1(TSP* root, int i, int end);//下调
void adjust2(TSP* root, int i);//上浮
void Bypile1(TSP* root, int size);//size是元素个数,root是结构体指针,adjust
void HeapSort1(TSP* proot, int size);

void Bypile2(TSP* root, int size);
void adjsut3(TSP* proot, int i, int end);//下调
void adjsut4(TSP* proot, int end);//上浮
void HeapSort2(TSP* proot, int size);

void print(TSP* root, int size);
void insertion(TSP* proot, Data x);//插入数据

我有两个版本

先看第一个

递归版本

调整上浮的函数adjsut1

void adjust2(TSP* proot, int i)//插入和建堆用,上浮
{//参数proot是结构体指针,i 表示是调整到那个位置了,
	//Data* a = proot->x + end-1;
	int parent = (i-1)/2;//先找一下他爹
	if (proot->x[i] < proot->x[parent])//我们判断当前要调整的元素,
	{//要建大堆是爹比儿子大,建小堆是儿子比爹大。我这里建小堆
		swap(&(proot->x[i]), &(proot->x[parent]));//比他爹小就调整,交换
	}
	else//比它爹大就说明是符合小堆的,不用调
		return;
	i = parent;//这个是多此一举
	adjust2(proot, i);//递归子问题,因为每一颗子树都可以是一个堆
}

建堆

void Bypile1(TSP* proot,int size)//建堆
{
	int end = 1;//这个end是用来限制调整范围,也就是图中的 i 
	while (end<size)//枚举下表为 1 到最后一个数字
	{
		adjust2(proot, end);//对每一个数字进行调整
		end++;
	}
}

插入函数

void insertion(TSP* proot, Data x)//插入
{
	Data* a = NULL;
	if (proot->x==NULL)
	{
		a = (Data*)malloc(sizeof(Data));
		proot->capacity =1;
	}
	if (proot->size == proot->capacity)
	{
		a = (Data*)realloc(proot->x,2 * (proot->capacity) * sizeof(Data));
		proot->capacity =2*(proot->capacity);
		assert(a);
	}
	if (a != NULL)
		proot->x = a;
		//proot->x[++proot->top] = x;
	assert(proot->x);
	proot->x[++proot->top] = x;
	proot->size++;
	adjust2(proot,proot->top);
	//adjsut4(proot, proot->top);
}

测试

TSP root = {NULL,-1,0,0};
TSP* proot = &root;
insertion(proot,2);
insertion(proot,5);
insertion(proot,7);
insertion(proot,1);
insertion(proot,4);
insertion(proot, 6);
insertion(proot, 3);
insertion(proot, 9);
insertion(proot, 20);
insertion(proot, 18);
insertion(proot, 12);

非常的OK

堆排序

在这个小堆中堆顶的元素是最小的,我们把这个元素和最后一个元素交换,再调整这个堆,然后再把堆顶元素和倒数第二个元素交换,这样后面一部分的元素就是升序排列,重复这个过程一直交唤到第一个元素那么数组就变得有序了。

这是一个小堆

最终结果

代码实现

void adjust1(TSP* proot,int i,int end)//下调
{//参数proot是结构体指针,i 表示是调整到那个位置了,end是限制范围的
	//Data* a = proot->x + end-1;
	int child = 2 * i + 1;
	if (2 * i + 1 > end )//左右子树都没有
		return;
	if (2 * i + 2 <= end )
	{//左右子树都有,选小的(建小堆就要选小的)
		child = (proot->x[2 * i + 1]) < (proot->x[2 * i + 2]) ? 2 * i + 1 : 2 * i + 2;
	}
	if (proot->x[child] < proot->x[i])//建大堆我们判断当前要调整的元素,
	{/*/建小堆就要用小于号*/		  //是否大于堆顶元素。
		swap(&(proot->x[i]), &(proot->x[child]));//比堆顶元素大就调整,交换
		i = child;
	}
	else//如果父节点比子节点小就已经是小堆了
		return;
	adjust1(proot, i,end);//递归子问题,因为每一颗子树都可以是一个堆
}
void HeapSort1(TSP* proot, int size)
{
	Bypile1(proot, size);
	int i = size-1, tmp = 0;
	while (i > 0)
	{
		swap(&(proot->x[i]), &(proot->x[0]));
		i--;//先减减还是先调整,看你自己,如果懂了,参数应该也知道怎么调
		adjust1(proot,0,i);
	}
}

结果

循环的版本,思路是一样的

void HeapSort2(TSP* proot,int size )
{
	Bypile2(proot, size);
	int i = size-1,tmp=0;
	while (i > 0)
	{
		swap(&(proot->x[i]), &(proot->x[0]));
		adjsut3(proot,0,i);
		i--;
	}
}

void Bypile2(TSP* proot, int size)
{
	for(int i = (size-1)/2; i >= 0 ; i--)
	{
		adjsut3(proot, i,size-1);//下调
	}
}
void adjsut3(TSP* proot,int i,int end)//下调
{
	int child = 2*i+1,parent=i;
	while (child < end)
	{
		if (2 * parent + 2 < end && proot->x[child] > proot->x[child + 1])
			++child;
		if (proot->x[child] < proot->x[parent])
		{
			swap(&(proot->x[child]), &(proot->x[parent]));
			parent = child;
			child = 2 * parent + 1;
		}
		else
			break;
	}
}
void adjsut4(TSP* proot, int end)//上浮
{
	int parent = (end-1) / 2,child = end;
	while (parent >= 0)
	{

		if (proot->x[child] < proot->x[parent])
		{
			swap(&(proot->x[child]), &(proot->x[parent]));
			child = parent; parent = (child-1) / 2;
		}
		else
			break;
	}
}

删除、销毁等的函数可以自己写和链表什么的差不多

接一个题目   . - 力扣(LeetCode)

  • 19
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值