二叉树-->详解篇

把优秀当成一种习惯。

本文将带大家了解二叉树的知识点:堆排序 建堆 topK问题

先来看下概念:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6Ium55qE6Ium6KGM5YOn,size_20,color_FFFFFF,t_70,g_se,x_16 通过概念我们知道,二叉树的度最大为2,那什么叫做度呢?

看完下面的这一张图我相信你很容易就能够理解,这里就不过多赘述。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6Ium55qE6Ium6KGM5YOn,size_20,color_FFFFFF,t_70,g_se,x_16

所以下面这些也都成为树。

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6Ium55qE6Ium6KGM5YOn,size_20,color_FFFFFF,t_70,g_se,x_16watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6Ium55qE6Ium6KGM5YOn,size_11,color_FFFFFF,t_70,g_se,x_16

 二叉树又分为两种特殊的结构:完全二叉树(最后一层可以满可以不满) 满二叉树(每一层都是满的)

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6Ium55qE6Ium6KGM5YOn,size_20,color_FFFFFF,t_70,g_se,x_16


另外满二叉树又称为:堆 

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6Ium55qE6Ium6KGM5YOn,size_14,color_FFFFFF,t_70,g_se,x_16

堆又分为两种:大堆(父亲大于等于孩子)  小堆(父亲小于等于孩子)

以下为计算父亲节点的下标以及左孩子 右孩子下标的方法

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6Ium55qE6Ium6KGM5YOn,size_16,color_FFFFFF,t_70,g_se,x_16


好了,上述的概念跟知识点已经基本讲完了。接下来就该对代码进行一个实现。本章的代码为 ​​:堆排序  topK问题

先来实现堆排序的代码:

如果有一题的题目为:让你把该数组的数据排列成小堆你会怎么完成呢?watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6Ium55qE6Ium6KGM5YOn,size_14,color_FFFFFF,t_70,g_se,x_16

 首先这个数组我们认为他是物理结构结构,我们把它转化为逻辑结构来看一下,你会发现这个数组的数据很特殊左边都为小堆,右边也是小堆满足这种特殊情况的我们一般采用向下调整法。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6Ium55qE6Ium6KGM5YOn,size_14,color_FFFFFF,t_70,g_se,x_16

做题思路:watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6Ium55qE6Ium6KGM5YOn,size_11,color_FFFFFF,t_70,g_se,x_16

 接下来直接上代码吧

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]);
	}
}

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6Ium55qE6Ium6KGM5YOn,size_9,color_FFFFFF,t_70,g_se,x_16

那么问题来了,如果题目给我们的数据不符合上述的前提,都是大堆或者小堆该怎么办呢?

接下来就是本章节的重点了:建堆

再来个题目:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6Ium55qE6Ium6KGM5YOn,size_20,color_FFFFFF,t_70,g_se,x_16

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6Ium55qE6Ium6KGM5YOn,size_20,color_FFFFFF,t_70,g_se,x_16

 直接上代码

#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问题

题目:面试题 17.14. 最小K个数

直接上代码

/**
 * 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'));
}

 

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值