探索数据结构:树-二叉树-堆(3)
前言:
上一个章节( 树-二叉树-堆(2))我们学习了二叉树的储存结构以及结构的实现,还学习了堆的概念、结构、堆的实现和向上调整和向下调整算法。
所以,今天我们继续来学习堆和二叉树的相关知识!
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/89840037bd2b247a885e39da37c4f2e7.png)
一、堆
1.1堆的插入
注意:这里的插入的前提是已经满足堆的条件的前提下进行的
代码实现:
// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
assert(hp);
//插入之前先判断空间是否充足
if (hp->size == hp->capacity)
{
int newcapacity = hp->capacity == 0 ? 4 : 2 * hp->capacity;
HPDataType* new = (HPDataType*)realloc(hp->a, newcapacity * sizeof(HPDataType));
hp->a = new;
hp->capacity = newcapacity;
}
hp->a[hp->size] = x;
hp->size++;
//插入完后继续执行向下调整,保持堆的结构
AdjustUp(hp->a,hp->size-1);
}
1.2堆的删除
这里堆的删除指的是删除堆顶的数据,那面对这种情况我们该怎么办呢?
这里我们可以将栈顶的数据与栈底的数据做一个交换,然后我们删除栈底的数据,最后在进行向下调整算法,这样就可以删除栈顶数据了。
代码实现:
// 堆的删除
void HeapPop(Heap* hp)
{
assert(hp);
if (hp->size == 0)//此时堆内没有数据了
{
return;
}
Swap(&hp->a[0], &hp->a[hp->size - 1]);
hp->size--;
if (hp->size > 0)
{
AdjustDown(hp->a, hp->size, 0);
}
}
二、堆的应用
2.1堆排序
1.建堆(利用堆删除思想来进行排序)
- 升序 建大堆
- 降序 建小堆
有的人可能会想着如果排序的要求是升序,我直接建一个小堆不就行了吗?
在这里有一种方法可以暂且满足这个升序,但是只是让结果是升序,并没有将数组里面变成升序。代码如下:
int a[] = { 4,2,8,1,5,6,9,7,3,2,23,55,232,66,222,33,7,1,66,3333,999 };
HP hp;
HPInit(&hp);//堆的初始化
for (size_t i = 0; i < sizeof(a)/sizeof(int); i++)
{
HPPush(&hp, a[i]);
}
int i = 0;
while (!HPEmpty(&hp))
{
printf("%d ", HPTop(&hp));
HPPop(&hp);
}
printf("\n");
这里有的人又会有另外一种思路了,我再创建一个数组,每次在删除堆顶的元素时我将堆顶数据先保存到我创建的这个数组里面,这样最后我创建的这个数组里面的内容不就是有序了吗。这个确实是一种方法,但是如果我的数据量非常大的时候,你就需要创建一个非常大的数组,这也导致你代码的效率非常低。代码如下:
int a[] = { 4,2,8,1,5,6,9,7,3,2,23,55,232,66,222,33,7,1,66,3333,999 };
HP hp;
HPInit(&hp);
for (size_t i = 0; i < sizeof(a)/sizeof(int); i++)
{
HPPush(&hp, a[i]);
}
int i = 0;
while (!HPEmpty(&hp))
{
a[i] = HPTop(&hp);//取栈顶的数据放入到数组当中
i++;
HPPop(&hp);
}
printf("\n");
- 升序建大堆的原因:
当你建完大堆后,将堆顶的数据和最后一个数据进行交换,这样最后一个数据就是最大值了,接下来我们只需对剩下的n-1(假设堆内数据个数为n)个数据进行调整建堆就行了,这样最后数组里面的数据就是升序的了。因此降序建小堆也很容易理解了。
2.2 Top-k问题
TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
对于Top-k问题,我们能想到的就是先将数据排个序,然后根据自己的需求去取数据就行,但是前提是数据量少的情况下,如果数据量非常大的情况下,排序的话就非常困难了,所以这里最佳的解决方式就是利用堆来解决这个问题。基本思路如下:
1.用数据集合中前K个元素来建堆
- 求前k个最大的元素,则建小堆。
这样利用堆的删除就可以求出前K个最大的元素。 - 求前k个最小的元素,则建大堆。
这样利用堆的删除就可以求出前K个最小的元素。
2.3堆相关所有代码
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 AdjustDown(HPDataType* a, int n, int parent);//数组 数组个数
//堆的初始化
void HeapInit(Heap* hp);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
bool HeapEmpty(Heap* hp);
Heap.c文件:
#include"Heap.h"
void Swap(HPDataType* a, HPDataType* b)
{
HPDataType tmp = *a;
*a = *b;
*b = tmp;
}
//堆的初始化
void HeapInit(Heap* hp)
{
assert(hp);
hp->a = NULL;
hp->capacity = hp->size = 0;
}
// 堆的销毁
void HeapDestory(Heap* hp)
{
assert(hp);
free(hp->a);
hp->a = NULL;
hp->capacity = hp->size = 0;
}
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 AdjustDown(HPDataType* a, int n, int parent)
{
//假设左孩子小
int child = parent * 2 + 1;
while (child < n )
{
if (a[child] > a[child + 1] && child + 1 < n)//假设不成立,.....child + 1 <n是因为可能会超出数组边界,导致访问越界错误。
{
child++;
}
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
assert(hp);
//插入之前先判断空间是否充足
if (hp->size == hp->capacity)
{
int newcapacity = hp->capacity == 0 ? 4 : 2 * hp->capacity;
HPDataType* new = (HPDataType*)realloc(hp->a, newcapacity * sizeof(HPDataType));
hp->a = new;
hp->capacity = newcapacity;
}
hp->a[hp->size] = x;
hp->size++;
//插入完后继续执行向下调整,保持堆的结构
AdjustUp(hp->a,hp->size-1);
//AdjustDown(hp->a, hp->size, 0);
}
// 堆的删除
void HeapPop(Heap* hp)
{
assert(hp);
if (hp->size == 0)//此时堆内没有数据了
{
return;
}
Swap(&hp->a[0], &hp->a[hp->size - 1]);
hp->size--;
if (hp->size > 0)
{
AdjustDown(hp->a, hp->size, 0);
}
}
// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
assert(hp);
return hp->a[0];
}
// 堆的数据个数
int HeapSize(Heap* hp)
{
assert(hp);
return hp->size;
}
// 堆的判空
bool HeapEmpty(Heap* hp)
{
assert(hp);
return hp->size == 0;
}
三、二叉树链式结构的实现
3.1前提说明
在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二叉树结构掌握还不够深入,为了降低大家学习成本,此处手动快速创建一棵简单的二叉树, 快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。
typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
BTNode* CreatBinaryTree()
{
BTNode* node1 = BuyNode(1);
BTNode* node2 = BuyNode(2);
BTNode* node3 = BuyNode(3);
BTNode* node4 = BuyNode(4);
BTNode* node5 = BuyNode(5);
BTNode* node6 = BuyNode(6);
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
return node1;
}
注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式后序详解重点讲解。
如果对二叉树的基本内容有些遗忘的可以看二叉树(1)了解一下
3.2二叉树的遍历
学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的结点进行相应的操作,并且每个结点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。
一般二叉树的遍历分为前序遍历、中序遍历、后序遍历、层序遍历。
(1)前序遍历
前序遍历指的是一个二叉树遍历的顺序是先遍历根节点,然后再遍历左子树,最后再遍历右子树。
代码实现:
//前序遍历
void prevOrder(BTNode*root)
{
if (root == NULL)
{
return;
}
printf("%d ", root->data);
prevOrder(root->left);
prevOrder(root->right);
}
(2)中序遍历
中序遍历指的是一个二叉树遍历的顺序是先遍历左子树,然后遍历根节点,最后遍历右节点。
代码实现:
//中序遍历
void Inorder(BTNode*root)
{
if (root == NULL)
{
return;
}
Inorder(root->left);
printf("%d ", root->data);
Inorder(root->right);
}
(3)后序遍历
后序遍历指的是 一个二叉树遍历的顺序是先遍历左子树,然后遍历右子树,最后再遍历根节点。
代码实现:
//后序遍历
void endOrder(BTNode* root)
{
if (root ==NULL)
{
return;
}
endOrder(root->left);
endOrder(root->right);
printf("%d ", root->data);
}
(4)层序遍历
层序遍历指的是遍历一个二叉树的方式是一层一层的遍历的。
如下图所示:首先遍历的是A节点,然后在遍历B节点,接下来就依次一层一层的的遍历。
层序遍历代码实现思路:这里二叉树的层序遍历我们要使用到队列的知识,对该部分知识有遗忘的可以看(栈-队列)内容回顾一下。这里我们总体的思路就是一层带一层,具体可以看下面 的流程图。
这里我们先将节点(也就是上图的1节点)入队列,然后再将节点出队列,出完后将该节点的左右子树节点进行入队列(上图的1节点出队列后将它的左右子树节点2和3入队列)。然后再出队列中队头的数据,然后继续将它的左右子树节点入队列(上图的2节点出完队列后将它的左右子树节点4和5出队列)。最后不断的入队列和出队列,直到队列为空时,代表层序遍历结束。
代码实现:
3.3二叉树的扩展问题
扩展一
// 二叉树结点个数
int BinaryTreeSize(BTNode* root);
代码实现:
// 二叉树结点个数
int BinaryTreeSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}
这里我们运用递归的思路,如果节点为空,就return 0,如果不为空,那么节点的个数就是左子树的节点+右子树的节点+自己本身这个节点。
扩展二
// 二叉树叶子结点个数
int BinaryTreeLeafSize(BTNode* root);
代码实现:
// 二叉树叶子结点个数
int BinaryTreeLeafSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
if (root->left == NULL && root->right == NULL)//判断是否为叶子节点
{
return 1;
}
return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}
这里一样也是用了递归的思路,如果节点为空,就return 0,如果不为空就判断该节点是否为叶子节点,如果是就return 1,然后总的叶子节点个数就是左子树的叶子节点+右子树的叶子节点。
扩展三
// 二叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
代码实现:
// 二叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
if (root == NULL)
{
return 0;
}
if (k == 1)
{
return 1;
}
return BinaryTreeLevelKSize(root->left,k-1) + BinaryTreeLevelKSize(root->right,k-1);
}
扩展三
// 二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
代码实现:
// 二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
{
return 0;
}
if (root->data == x)
{
return root;
}
BTNode* ret1=BinaryTreeFind(root->left,x);
if (ret1)
{
return ret1;
}
BTNode* ret2 = BinaryTreeFind(root->right, x);
if (ret2)
{
return ret2;
}
}
3.4二叉树相关代码
#include<stdio.h>
#include<stdlib.h>
typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
BTNode* BuyNode(int x)
{
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
node->data = x;
node->left = NULL;
node->right = NULL;
return node;
}
BTNode* CreatBinaryTree()
{
BTNode* node1 = BuyNode(1);
BTNode* node2 = BuyNode(2);
BTNode* node3 = BuyNode(3);
BTNode* node4 = BuyNode(4);
BTNode* node5 = BuyNode(5);
BTNode* node6 = BuyNode(6);
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
return node1;
}
//前序遍历
void prevOrder(BTNode*root)
{
if (root == NULL)
{
return;
}
printf("%d ", root->data);
prevOrder(root->left);
prevOrder(root->right);
}
//中序遍历
void Inorder(BTNode*root)
{
if (root == NULL)
{
return;
}
Inorder(root->left);
printf("%d ", root->data);
Inorder(root->right);
}
//后序遍历
void endOrder(BTNode* root)
{
if (root ==NULL)
{
return;
}
endOrder(root->left);
endOrder(root->right);
printf("%d ", root->data);
}
// 二叉树结点个数
int BinaryTreeSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}
// 二叉树叶子结点个数
int BinaryTreeLeafSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
if (root->left == NULL && root->right == NULL)//判断是否为叶子节点
{
return 1;
}
return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}
// 二叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
if (root == NULL)
{
return 0;
}
if (k == 1)
{
return 1;
}
return BinaryTreeLevelKSize(root->left,k-1) + BinaryTreeLevelKSize(root->right,k-1);
}
// 二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
{
return 0;
}
if (root->data == x)
{
return root;
}
BTNode* ret1=BinaryTreeFind(root->left,x);
if (ret1)
{
return ret1;
}
BTNode* ret2 = BinaryTreeFind(root->right, x);
if (ret2)
{
return ret2;
}
}