把优秀当成一种习惯。
本文将带大家了解二叉树的知识点:堆排序 建堆 topK问题
先来看下概念:
通过概念我们知道,二叉树的度最大为2,那什么叫做度呢?
看完下面的这一张图我相信你很容易就能够理解,这里就不过多赘述。
所以下面这些也都成为树。
二叉树又分为两种特殊的结构:完全二叉树(最后一层可以满可以不满) 满二叉树(每一层都是满的)
另外满二叉树又称为:堆
堆又分为两种:大堆(父亲大于等于孩子) 小堆(父亲小于等于孩子)
以下为计算父亲节点的下标以及左孩子 右孩子下标的方法
好了,上述的概念跟知识点已经基本讲完了。接下来就该对代码进行一个实现。本章的代码为 :堆排序 topK问题
先来实现堆排序的代码:
如果有一题的题目为:让你把该数组的数据排列成小堆你会怎么完成呢?
首先这个数组我们认为他是物理结构结构,我们把它转化为逻辑结构来看一下,你会发现这个数组的数据很特殊,左边都为小堆,右边也是小堆。满足这种特殊情况的我们一般采用向下调整法。
做题思路:
接下来直接上代码吧
void swap(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
//只能用在特殊情况
void AdJustDown(int* a, int n, int parent)
{
//作用:调整为小堆/大堆
//前提:特殊情况下的堆排序(左子树跟右子树都是小堆或者大堆才能用这个方法)
//这个方法成为:向下调整法
int child = (parent * 2) + 1;//只定义一个孩子默认为左孩子,右孩子只需要++以下就行
while (child < n)
{
if (a[child] > a[child+1] && child+1 < n)//选出左右孩子里最小的一个;保证不越界
{
child++;
}
if (a[child] < a[parent])
{
swap(&a[child], &a[parent]);//小于就交换位置,继续比较
parent = child;
child = (parent * 2) + 1;
}
else
{
//大于就不需要继续了,说明调整完成
break;
}
}
}
int main()
{
int arr[] = { 27,15,19,18,28,34,65,49,25,34 };
int len = sizeof(arr) / sizeof(arr[0]);
AdJustDown(arr, len, 0);
for (int i = 0; i < len; i++)
{
printf("%d ", arr[i]);
}
}
那么问题来了,如果题目给我们的数据不符合上述的前提,都是大堆或者小堆该怎么办呢?
接下来就是本章节的重点了:建堆
再来个题目:
直接上代码
#include <stdio.h>
//leftchild = parent*2+1; rightchild = parent*2+2;
//parent = (child-1)/2
void swap(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
//只能用在特殊情况
void AdJustDown(int* a, int n, int parent)
{
//作用:调整为小堆/大堆
//前提:特殊情况下的堆排序(左子树跟右子树都是小堆或者大堆才能用这个方法)
//这个方法成为:向下调整法
int child = (parent * 2) + 1;//只定义一个孩子默认为左孩子,右孩子只需要++以下就行
while (child < n)
{
if (a[child] < a[child+1] && child+1 < n)//选出左右孩子里最小的一个;保证不越界
{
child++;
}
if (a[child] > a[parent])
{
swap(&a[child], &a[parent]);//小于就交换位置,继续比较
parent = child;
child = (parent * 2) + 1;
}
else
{
//大于就不需要继续了,说明调整完成
break;
}
}
}
//可以用在任何情况
void HeapSort(int* a,int n)
{
//作用:给左子树跟右子树都不是小堆或者大堆的进行排序:按照升序或者降序来排
//方法:建堆-->时间复杂度 O(N) 直接排序是O(N^2)
//思路:找出最后一个节点的父亲,然后自下而上,把每个节点都变为小堆或者大堆,然后就办成了我们前面所学的第一种特殊情况的样子了
//parent = (child-1)/2 通过儿子找父亲
//第一步:建堆 如果排序是升序:要建大堆而不是小堆
for (int parent = (n - 1 - 1) / 2; parent >= 0; parent--)//最后一个孩子为n-1 父亲节点都是相邻的,所以通过--就可以找到
{
AdJustDown(a, n, parent);
}
//第二步:排序
int end = n - 1;
while (end>0)
{
swap(&a[0], &a[end]);//因为建堆已经把最大的升到了最前面一个,所以思路是把第一个跟最后一个交换,然后在让0~n-1个进行建堆,重复循环
end--;
AdJustDown(a, end, 0);
}
}
int main()
{
//int arr[] = { 27,15,19,18,28,34,65,49,25,34 };
//int len = sizeof(arr) / sizeof(arr[0]);
/*AdJustDown(arr, len, 0);
for (int i = 0; i < len; i++)
{
printf("%d ", arr[i]);
}*/
int a[] = { 15, 18, 28, 34, 65, 19, 49, 25, 37, 27 };
int len = sizeof(a) / sizeof(a[0]);
HeapSort(a, len);
for (int i = 0; i < len; i++)
{
printf("%d ", a[i]);
}
}
TOPK问题
直接上代码
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
//思路:为前k个数开辟空间创建大堆 k后面的数依次跟大堆对顶上的数比较,小的就替换
//比顶上的数小就说明比堆里的其中一个数小,循环替换掉就可以得到N个数中前K个最小的数
//好处:如果数有10亿个,我们只需要开辟前K个大小的空间,而且空间 时间复杂度(O(N*logN*k))都是最优
void Swap(int* p1,int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
//建大堆
void AdJustDown(int* p,int n,int parent)
{
int child = (parent*2)+1;
while(child<n)
{
if(p[child+1]>p[child] && child+1 < n)
{
child++;
}
if(p[child]>p[parent])
{
Swap(&p[child],&p[parent]);
parent = child;
child = (parent*2)+1;
}
else
{
break;
}
}
}
int* smallestK(int* arr, int arrSize, int k, int* returnSize){
if(k == 0)
{
*returnSize = 0;
return NULL;
}
int* arrt = (int*)malloc(sizeof(int*)*k);
for(int i = 0; i < k; i++)
{
arrt[i] = arr[i];//将数组前K个数导数arrt中
}
//建堆:自下到上
for(int parent = (k-1-1)/2; parent >=0 ; parent--)
{
AdJustDown(arrt,k,parent);
}
//往K的堆插入k往后的数据,跟实现二叉树的插入数据相同
for(int i = k; i < arrSize; i++)
{
if(arr[i]<arrt[0])
{
arrt[0]=arr[i];
AdJustDown(arrt,k,0);
}
}
*returnSize = k;
return arrt;
}
二叉树的遍历:
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef char BTDataType;
typedef struct BinaryTreeNode
{
BTDataType Data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
//普通二叉树的zhengshan查改没有意义,搜索树的才有意义
//遍历前序:根 左子树 右子树
void PrevOrder(BTNode* root);
//遍历中序:左子树 根 右子树
void InOrder(BTNode* root);
//遍历后续:左子树 右子树 根
void PostOrder(BTNode* root);
//统计叶子节点的个数:度为0
BTNode* CreateTreeNode(BTDataType x);
//统计节点个数
int TreeSize(BTNode* root);
//统计叶子节点个数
int TreeLeafSize(BTNode* root);
//统计第K层的节点个数
int TreeKLevelSize(BTNode* root, int k);
//查找树里面值为X的那个节点
BTNode* TreeFind(BTNode* root, BTDataType x);
#include "BTtest.h"
//遍历前序:根 左子树 右子树
void PrevOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%c ", root->Data);
PrevOrder(root->left);
PrevOrder(root->right);
}
//遍历中序:左子树 根 右子树
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->left);
printf("%c ", root->Data);
InOrder(root->right);
}
//遍历后续:左子树 右子树 根
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%c ", root->Data);
}
//创建结构体类型节点,这样才能包含数据域跟两个指针域
BTNode* CreateTreeNode(BTDataType x)
{
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
if (node == NULL)
{
printf("malloc fail...");
exit(-1);
}
node->Data = x;
node->left = NULL;
node->right = NULL;
return node;
}
//统计节点个数
int TreeSize(BTNode* root)
{
//递归 左子树的节点加上右子树的节点 在加上根节点就是总的节点
//+1是因为算上根节点A
return root == NULL ? 0: TreeSize(root->left)+ TreeSize(root->right) + 1;
}
//统计叶子节点的个数:度为0
int TreeLeafSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
if (root->left == NULL && root->right == NULL)
{
return 1;
}
//不是空 也不是叶子节点 说明往下还有节点,也是用到递归
return TreeLeafSize(root->left) + TreeLeafSize( root->right);
}
//统计第K层的节点个数:k-1层的节点个数
int TreeKLevelSize(BTNode* root, int k)
{
if (root == NULL)
{
return 0;
}
if (k == 1)
{
return 1;
}
return TreeKLevelSize(root->left, k - 1) + TreeKLevelSize(root->right, k - 1);
}
//查找树里面值为X的那个节点
BTNode* TreeFind(BTNode* root, BTDataType x)
{
//空
if (root == NULL)
{
return NULL;
}
//我自己不是
if (root->Data == x)
{
return root;
}
//分别到左右子树去找,知道递归到最后一个子问题,最后一个子问题一定会返回一个节点
//要是找到了 就会返回root给lret 然后 lret在返回到上一层递归给他的函数
//在left找到就不需要在right再找了
//如果左右都没有找到 那就是不存在 返回NULL
//这部分比较绕 建议画图理解
BTNode* lret = TreeFind(root->left,x);
if (lret)
{
return lret;
}
BTNode* rret = TreeFind(root->right,x);
if (rret)
{
return rret;
}
return NULL;
}
int main()
{
//malloc一个结构体大小的空间拿来存数据域跟指针域 ,接受返回值的类型也要相同,所以也是结构体指针
BTNode* A = CreateTreeNode('A');
BTNode* B = CreateTreeNode('B');
BTNode* C = CreateTreeNode('C');
BTNode* D = CreateTreeNode('D');
BTNode* E = CreateTreeNode('E');
BTNode* F = CreateTreeNode('F');
BTNode* G = CreateTreeNode('G');
//链接起来 形成一个二叉树
A->left = B;
A->right = C;
B->left = D;
B->right = E;
C->left = F;
C->right = G;
PrevOrder(A);
printf("\n");
InOrder(A);
printf("\n");
PostOrder(A);
printf("\n");
printf("节点个数为:%d\n", TreeSize(A));
printf("叶子节点个数为:%d\n", TreeLeafSize(A));
printf("第K层的节点个数:%d\n", TreeKLevelSize(A, 3));
printf("TreeFind:%p\n", TreeFind(A, 'B'));
}