C++实现二叉树及堆的示例代码

1 树

树是一种非线性数据结构,它是由n个有限结点组成的具有层次关系的集合。把它叫树是因为它是根朝上,叶子朝下的
来上图瞧瞧

1.1 树的相关名词

2 二叉树

2.1 二叉树的概念

一颗二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根结点加上两棵分别称为左子树和右子树。
如图所示:

二叉树有以下特点:

1、每个二叉树最多有两颗子树,所以二叉树不存在度为2的结点。
2、二叉树的子树有左右之分,其子树的顺序不能颠倒。

2.2 二叉树的性质

1、若规定根结点的层数为第一层,则一颗非空二叉树的第i层上最多有z^(k-1)个结点
2、若规定根结点的层数为第一层,则深度为h的二叉树的最大结点数是2^k-1个。
3、对于任何一颗二叉树,度为0的结点(叶子结点)的个数为n0 ,度为2的结点个数为n2则一定有,n0 = n2 + 1。
4、若规定根结点的层数为第一层,具有N个结点的满二叉树的深度h = log2(N+1)[说明:以2为底的N+1的对数],这个可以由性质2推导得到。
5、对于具有n个结点的完全二叉树,如果按照从上到下从左到右的数组顺序对所有结点从0开始编号,即对于数组下标i的结点有:
1)i>1,i位置的父结点的在数组中的下标为(i-1)/2.
2)i位置结点的左孩子结点的下标为2i+1,右结点下标为2i+2。

2.3 特殊的二叉树

1、满二叉树:一个二叉树,如果每一层的结点数都达到了最大,如果这个二叉树的层次是k,则其结点数是2^k-1。
2、完全二叉树:用最直白的话来说就是,树的深度或者高度为k,前k-1层的结点都是满的,只有最后的第k层不满但是从左到右的结点必须是连续的。
其实满二叉树是一种特殊的完全二叉树。
如图所示:

图为完全二叉树,要是最后一层全满则为满二叉树。
满足:
2^k-1-x = N;
x的取值范围是[0,2^(k-1)-1]

3 堆

3.1 堆的概念

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费,而完全二叉树更适合用顺序存储,顺序存储的随机访问特性,会右巨大的优点。我们通常把堆(是一种完全二叉树)采用顺序存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构一个是OS中管理内存的一块区域分段。
堆分为大根堆和小根堆。
大根堆:父亲结点>=孩子结点
小根堆:父亲结点<=孩子结点

上面是堆的逻辑结构,下面是物理结构

3.2 堆的实现

首先构建堆我们要了解一个算法,叫向下调整算法。我们以小根堆为例,我们把图示的完全二叉树构建为小堆,这个二叉树有个条件是根结点的两个子树都是小堆才可以进行向下调整算法。

3.2.1 向下调整算法

根结点的左右子树都是小堆,根结点27和左右子树的根结点较小的那一个交换位置,然后依次进行,直到叶子结点。就把最小的15结点上浮到堆顶的位置。这个算法的前提是根节点的左右子树都是小堆,如果我们想把任意的的数组构建成小堆显然不满足条件。在下面我们介绍把任意数组构建成小堆的办法。
向下调整算法的代码如下:

void AdjustDown(HeapDataType* a, int n, int root)
{
	//父子下标的初始化
	int parent = root;
	int child = parent * 2 + 1;
	//循环向下调整,把最小值(或者最大值浮到堆顶)
	while (child < n)
	{
		//选出左右孩子中较小的孩子,作为child,child+1 < n保证下标不能越界
		if (child+1 < n && a[child + 1] < a[child])
		{
			++child;
		}
		//父亲比孩子小二者交换位置,并更新迭代孩子的位置
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			//迭代parent child
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;//当孩子 >= 父亲的时候,满足小堆的条件,跳出循环节课,否则就会死循环
		}
	}
}

3.2.2 堆的构建

把给定的数据化成完全二叉树的逻辑结构如上图所示,这个数组(二叉树)显然不能满足向下调整算法的理想条件,所以我们把问题拆分,比如你先思考下这个问题,把左子树和右子树全部构建成小堆不就满足条件了嘛,但是子树的左右子不是小堆怎么办呢,那么同样的道理,把它也构建成子树就可以了,那么我们可以从叶子结点向上每个结点都执行一边向下调整算法不就可以了嘛。其实我们不必从叶子结点开始因为叶子结点没有子树其实都可以看成是小堆或者大堆。所以从第一个非叶子结点开始调整即可。

也就是从图中紫色圈圈画出来的那个开始调整即可,直到根结点93,就会把一个数组构建成小堆(大堆)。

for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}

(n-1-1)/2为第一非叶子结点下标。

3.2.3 堆排序

有了上面的知识做铺垫,堆排序就可以很好的理解了。
1、把数组构建成大堆或者小堆,位于堆顶的数据就是最大值或者最小值,把堆顶数据和最后的结点位置的数据(数组元素最后一个)互换。然后对前n-1个结点重新向下调整为大堆或者小堆。直到剩下最后一个根节点就排序完成。
只是要升序:构建大堆,要降序:构建小堆,你细细品。
代码如下:

//堆排序
void HeapSort(int* a, int n)
{
	//堆排序的第一步就是构建堆,构建堆的时间复杂度是O(n),此时是小堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}
	//如果是升序,构建大堆
	//如果是降序,构建小堆
	//是反着的,因为要和最后一个进行交换
	int end = n - 1;
	while (end>0)
	{
		//把堆顶(最小或者最大)和最后的的元素交换,然后从0到n-2继续向下调整
		//把次小(次大)的元素也选出来,直到剩最后一个,堆排序完成
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}
### 3.2.4 堆的的增删查改
声明:
```c
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <windows.h>

typedef int HeapDataType;

typedef struct Heap
{
	HeapDataType* arr;
	int size;
	int capacity;
} Heap;

//堆的初始化,内部有堆的创建
void HeapInit(Heap* php, HeapDataType* a, int n);
//堆的销毁
void HeapDestory(Heap* php);
//堆的插入数据
void HeapPush(Heap* php, HeapDataType x);
//堆的删除数据
void HeapPop(Heap* php);
//获取堆顶数据
HeapDataType HeapTop(Heap* php);
//对传入的数组内的数据进行堆排序
void HeapSort(int* a, int n);

定义:

#include "Heap.h"
//堆是一种完全二叉树,用顺序存储(数组)比较好
//用于交换两个数据
void Swap(HeapDataType* n1, HeapDataType* n2)
{
	HeapDataType temp = *n1;
	*n1 = *n2;
	*n2 = temp;
}
//向下调整算法___老重要了,这是理解堆排序和topk问题以及堆这里相关题的基础
//向下调整结束的情况有两个一个是a[parent]<a[child],另一个是从堆顶到数组结束全部比较完
void AdjustDown(HeapDataType* a, int n, int root)
{
	//父子下标的初始化
	int parent = root;
	int child = parent * 2 + 1;
	//循环向下调整,把最小值(或者最大值浮到堆顶)
	while (child < n)
	{
		//选出左右孩子中较小的孩子,作为child,child+1 < n保证下标不能越界
		if (child+1 < n && a[child + 1] < a[child])
		{
			++child;
		}
		//父亲比孩子小二者交换位置,并更新迭代孩子的位置
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			//迭代parent child
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;//当孩子 >= 父亲的时候,满足小堆的条件,跳出循环节课,否则就会死循环
		}
	}
}
//向上调整算法
//用在HeapPush中
void AdjustUp(HeapDataType* a, int n, 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 HeapInit(Heap* php, HeapDataType* a, int n)
{
	HeapDataType* temp = (HeapDataType*)malloc(sizeof(HeapDataType)*n);
	if (temp)
	{
		php->arr = temp;

	}
	else
	{
		printf("内存申请失败!");
		exit(-1);
	}
	//将传进来的数组拷贝给malloc出来的空间,用来后续的堆的创建,删除,插入数据等操作
	memcpy(php->arr, a, sizeof(HeapDataType)*n);
	php->size = n;
	php->capacity = n;

	//把拷贝进来的数组,构建成堆
	//从倒数第一个非叶子节点进行构建(直接把数组画成一个完全二叉树可以直接由图得到第一个
	//非叶子节点的下标)
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(php->arr, php->size, i);
	}
}
//堆排序
void HeapSort(int* a, int n)
{
	//堆排序的第一步就是构建堆,构建堆的时间复杂度是O(n),此时是小堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}
	//如果是升序,构建大堆
	//如果是降序,构建小堆
	//是反着的,因为要和最后一个进行交换
	int end = n - 1;
	while (end>0)
	{
		//把堆顶(最小或者最大)和最后的的元素交换,然后从0到n-2继续向下调整
		//把次小(次大)的元素也选出来,直到剩最后一个,堆排序完成
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

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

//堆的插入数据
void HeapPush(Heap* php, HeapDataType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		php->capacity *= 2;
		HeapDataType* tmp = (HeapDataType*)realloc(php->arr, sizeof(HeapDataType)*php->capacity);
		if (tmp)
		{
			php->arr = tmp;
		}
		else
		{
			printf("扩容失败!\n");
			exit(-1);
		}
	}
	php->arr[php->size++] = x;
	AdjustUp(php->arr, php->size, php->size - 1);
}

//堆的删除数据,要删除堆顶的数据
void HeapPop(Heap* php)
{
	assert(php);
	assert(php->size > 0);
	Swap(&php->arr[0], &php->arr[php->size - 1]);
	php->size--;
	AdjustDown(php->arr, php->size, 0);
}

//获取堆顶数据
HeapDataType HeapTop(Heap* php)
{
	assert(php);
	assert(php->size > 0);
	return php->arr[0];
}

看这里的小伙伴可以自己下去测试一下代码哦。

到此这篇关于C++实现二叉树及堆的示例代码的文章就介绍到这了,更多相关C++实现二叉树及堆内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++使用递归和非递归算法实现的二叉树叶子节点个数计算方法

    本文实例讲述了C++使用递归和非递归算法实现的二叉树叶子节点个数计算方法.分享给大家供大家参考,具体如下: /*求二叉树叶子节点个数 -- 采用递归和非递归方法 经调试可运行源码及分析如下: ***/ #include <stdlib.h> #include <iostream> #include <stack> using std::cout; using std::cin; using std::endl; using std::stack; /*二叉树结点定义*/

  • C++ 数据结构二叉树(前序/中序/后序递归、非递归遍历)

    C++ 数据结构二叉树(前序/中序/后序递归.非递归遍历) 二叉树的性质: 二叉树是一棵特殊的树,二叉树每个节点最多有两个孩子结点,分别称为左孩子和右孩子. 例: 实例代码: #include <iostream> #include <Windows.h> #include <stack> using namespace std; template <class T> struct BinaryTreeNode { int _data; BinaryTree

  • 二叉树遍历 非递归 C++实现代码

    二叉树的非递归遍历 二叉树是一种非常重要的数据结构,很多其它数据结构都是基于二叉树的基础演变而来的.对于二叉树,有前序.中序以及后序三种遍历方法.因为树的定义本身就是递归定义,因此采用递归的方法去实现树的三种遍历不仅容易理解而且代码很简洁.而对于树的遍历若采用非递归的方法,就要采用栈去模拟实现.在三种遍历中,前序和中序遍历的非递归算法都很容易实现,非递归后序遍历实现起来相对来说要难一点. 一.前序遍历 前序遍历按照"根结点-左孩子-右孩子"的顺序进行访问. 1.递归实现 复制代码 代码

  • C++实现二叉树基本操作详解

    树是一种重要的非线性数据结构,二叉树是树型结构的一种重要类型.本学年论文介绍了二叉树的定义,二叉树的存储结构,二叉树的相关术语,以此引入二叉树这一概念,为展开二叉树的基本操作做好理论铺垫.二叉树的基本操作主要包含以下几个模块:二叉树的遍历方法,计算二叉树的结点个数,计算二叉树的叶子结点个数,二叉树深度的求解等内容. 前序遍历(递归&非递归) 访问根节点 前序访问左子树 前序访问右子树 //前序非递归 void PrevOrder() { stack<Node*> s; Node *cu

  • C++二叉树结构的建立与基本操作

    准备数据定义二叉树结构操作中需要用到的变量及数据等. 复制代码 代码如下: #define MAXLEN 20    //最大长度typedef char DATA;    //定义元素类型struct  CBTType                   //定义二叉树结点类型 { DATA data;           //元素数据  CBTType * left;    //左子树结点指针  CBTType * right;   //右子树结点指针 }; 定义二叉树结构数据元素的类型DA

  • c++二叉树的几种遍历算法

    1. 前序/中序/后序遍历(递归实现) 复制代码 代码如下: // 前序遍历void BT_PreOrder(BiTreePtr pNode){ if (!pNode)  return;    visit(pNode);   BT_PreOrder(pNode->left); BT_PreOrder(pNode->right);   }// 中序遍历void BT_PreOrder(BiTreePtr pNode){  if (!pNode)  return;     BT_PreOrder(

  • C++实现二叉树遍历序列的求解方法

    本文详细讲述了C++实现二叉树遍历序列的求解方法,对于数据结构与算法的学习有着很好的参考借鉴价值.具体分析如下: 一.由遍历序列构造二叉树 如上图所示为一个二叉树,可知它的遍历序列分别为: 先序遍历:ABDECFG 中序遍历:DBEAFCG 后序遍历:DEBFGCA 我们需要知道的是,由二叉树的先序序列和中序序列可以唯一地确定一棵二叉树:由二叉树的后序序列和中序序列也可以唯一地确定一棵二叉树:但是如果只知道先序序列和后序序列,则无法唯一确定一棵二叉树. 二.已知二叉树的先序序列和中序序列,求后序

  • C++ 遍历二叉树实例详解

    C++ 遍历二叉树实例详解 2叉数又叫红黑树,关于2叉数的遍历问题,有很多,一般有三种常用遍历方法: (1)前序遍历(2)中序遍历(3)后续遍历 以下是经典示例: #include "stdafx.h" #include<stdio.h> #include<malloc.h> #include <math.h > #define MaxSize 20 typedef struct BiTNode { int data; struct BiTNode

  • 探讨:C++实现链式二叉树(用非递归方式先序,中序,后序遍历二叉树)

    如有不足之处,还望指正! 复制代码 代码如下: // BinaryTree.cpp : 定义控制台应用程序的入口点.//C++实现链式二叉树,采用非递归的方式先序,中序,后序遍历二叉树#include "stdafx.h"#include<iostream>#include<string>#include <stack>using namespace std;template<class T>struct BiNode{ T data; 

  • C++实现二叉树及堆的示例代码

    1 树 树是一种非线性数据结构,它是由n个有限结点组成的具有层次关系的集合.把它叫树是因为它是根朝上,叶子朝下的 来上图瞧瞧 1.1 树的相关名词 2 二叉树 2.1 二叉树的概念 一颗二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根结点加上两棵分别称为左子树和右子树. 如图所示: 二叉树有以下特点: 1.每个二叉树最多有两颗子树,所以二叉树不存在度为2的结点. 2.二叉树的子树有左右之分,其子树的顺序不能颠倒. 2.2 二叉树的性质 1.若规定根结点的层数为第一层,则一颗非空二叉树的

  • C语言实现大顶堆的示例代码

    目录 堆的实现 1.堆结构 2.堆的种类 3.大顶堆代码实现 堆的实现 1.堆结构 逻辑结构上类似于 一棵 “树” 2.堆的种类 大顶堆结构: 一种特殊的树:其每个子节点均比母节点要小 小顶堆结构: 同理:其每个子节点均比母节点要大 结构图示: 3.大顶堆代码实现 这里实现堆 用循序表的方式 ①初始化: typedef int Heaptype; typedef struct Heap { Heaptype* head; int size; //记录堆总容量 int capacity; //记录

  • C++合并二叉树的思路与示例代码

    前言 给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠.你需要将他们合并为一个新的二叉树.合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点. 示例 1: 思路 1.确定递归函数的参数和返回值: 首先那么要合入两个二叉树,那么参数至少是要传入两个二叉树的根节点,返回值就是合并之后二叉树的根节点. 代码如下: TreeNode* mergeTrees(TreeNode* t1, TreeNod

  • Java实现二叉树的示例代码(递归&迭代)

    目录 1.二叉树基本概念见上节:详解Java中二叉树的基础概念(递归&迭代) 2.本次展示链式存储 以此图为例,完整代码如下: //基础二叉树实现 //使用左右孩子表示法 import java.util.*; import java.util.Deque; public class myBinTree { private static class TreeNode{ char val; TreeNode left; TreeNode right; public TreeNode(char va

  • C语言实现堆的简单操作的示例代码

    目录 一.堆的概念 二.堆的实现 三.堆的代码实现 一.堆的概念 (1)定义 如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为小堆(或大堆).将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆. (2)性质 1.堆中某个节点的值总是不大于或不小

  • Java实现基本排序算法的示例代码

    目录 1. 概述 2. 插入排序 2.1 直接插入排序 2.2 希尔排序(缩小增量排序) 3. 选择排序 3.1 直接选择排序 3.2 堆排序 4. 交换排序 4.1 冒泡排序 4.2 快速排序 5. 归并排序 6. 计数排序(非比较类型的排序) 7. 排序算法总结 1. 概述 排序概念:就是将一串记录按照其中某个或某些关键字的大小,递增或递减的排列起来的操作. 稳定性:通俗的将就是数据元素不发生有间隔的交换,例如: 内部排序:数据元素全部放在内存中的排序 外部排序:数据元素太多不能一次加载到内

  • c++中堆栈及创建对象示例代码

    简介 栈(stack),先进后出,位于一级缓存中,操作系统自动分配释放 ,存放函数的参数值,局部变量的值等,被调用时处于存储空间中,调用完毕立即释放. 堆(heap),堆包含一个链表来维护已用和空闲的不连续的内存块,存放在二级缓存中,一般由程序员分配释放. 快速记忆方式: 一级缓存比二级缓存快,栈是一个先进后出列表,存取非常快,所以栈是在一级缓存中. 栈中不能随机取数据,只能取最上面的一个,存放的内容必然要有严格的存取顺序,所以适合函数调用时的形参.局部变量. 栈空间有限,一般PC一级缓存就几M

  • Java语言实现二叉堆的打印代码分享

    二叉堆是一种特殊的堆,二叉堆是完全二元树(二叉树)或者是近似完全二元树(二叉树).二叉堆有两种:最大堆和最小堆.最大堆:父结点的键值总是大于或等于任何一个子节点的键值:最小堆:父结点的键值总是小于或等于任何一个子节点的键值. 打印二叉堆:利用层级关系 我这里是先将堆排序,然后在sort里执行了打印堆的方法printAsTree() public class MaxHeap<T extends Comparable<? super T>> { private T[] data; pr

  • Go/Python/Erlang编程语言对比分析及示例代码

    本文主要是介绍Go,从语言对比分析的角度切入.之所以选择与Python.Erlang对比,是因为做为高级语言,它们语言特性上有较大的相似性,不过最主要的原因是这几个我比较熟悉. Go的很多语言特性借鉴与它的三个祖先:C,Pascal和CSP.Go的语法.数据类型.控制流等继承于C,Go的包.面对对象等思想来源于Pascal分支,而Go最大的语言特色,基于管道通信的协程并发模型,则借鉴于CSP分支. Go/Python/Erlang语言特性对比 如<编程语言与范式>一文所说,不管语言如何层出不穷

  • Java实现8种排序算法的示例代码

    冒泡排序 O(n2) 两个数比较大小,较大的数下沉,较小的数冒起来. public static void bubbleSort(int[] a) { //临时变量 int temp; //i是循环次数,也是冒泡的结果位置下标,5个数组循环5次 for (int i = 0; i < a.length; i++) { //从最后向前面两两对比,j是比较中下标大的值 for (int j = a.length - 1; j > i; j--) { //让小的数字排在前面 if (a[j] <

随机推荐