数据结构——树(中篇)

今日名言:

人生碌碌,竞短论长,却不道枯荣有数,得失难量


上次我们讲了树的相关知识,接下来就进一步了解二叉树吧。本文为个人学习笔记,如有侵权,请

联系删除,如有错误,欢迎批评指正!!!

上次讲到了建堆的做法,这次我们来讲讲有关堆的应用吧

堆排序

堆排序就是利用堆的思想来排序,总共分为两个步骤


1.建堆

  • 升序:建大堆
  • 降序:建小堆

这里可能有人有疑问,为什么升序是建大堆,而降序是建小堆呢?以小堆为例,我们建完小堆后,第一个元素就是最小的,那么为了得到第二小的元素我们就得继续建小堆,这样时间你复杂度就比较高;而如果我们利用堆删除的方法来排序,将第一个元素和最后一个元素交换,取最后一个元素,然后采用向下调整的方法调整,这样依次进行,就可以得到一个降序序列。

2.利用堆删除的方法进行排序

代码实现

复制代码
for (int i = (n - 2) / 2;i >= 0;i--) {
	AdjustDown(a, n, i);
	int end = n - 1;//找到最后一个元素
	while (end > 0) {
		swap(&a[0], &a[end]);//交换第一个与最后一个
		AdjustDown(a, n, i);//向下调整
		end--;//排除最后一个继续调整
	}
}

图解:

Top-K问题

什么是TOP-K问题呢?

即是求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大,其实这类问题在我们的日常生活中十分常见,例如专业前十名,世界五百强等等。

对于这类问题,我们一般的方法就是排序,但是数据量大,排序可能就不适合了。


如果用堆来解决,我们有两种方法

  • 方法一:在大堆中pop k次就能找到最大的前K个,在小队中pop k次就能找到最小的前K个。但是这种方法会有弊端,数据量大的时候就需要非常多的空间,这样内存可能不够用
  • 方法二:如果是查找前K个最大的元素,我们就用前K个元素建小堆, 再用剩余的元素与堆顶元素比较,如果比堆顶元素大,就如替换堆顶元素;如果是查找前K个最小的元素,我们就用前K个元素见大堆,再用剩余的元素与堆顶元素比较,如果比堆顶元素小,则替换堆顶元素。

下面我们通过一道具体的题来体会一下

题目来源:面试题 17.14. 最小K个数 - 力扣(LeetCode)

题目描述:

代码解答:

复制代码
/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
void AdjustDown(int*arr,int n,int root){
    int parent=root;
    int child=2*root+1;
    while(child<n){
        if(child+1<n&&arr[child]<arr[child+1]){//防止越界
            child++;//如果左孩子小于右孩子,那么就交换二者的值
        }
    if(arr[parent]<arr[child]){
        int t=arr[parent];
        arr[parent]=arr[child];
        arr[child]=t;//交换二者的值
    }
    parent=child;
    child=2*parent+1;//更新二者的值
    }
 }
int* smallestK(int* arr, int arrSize, int k, int* returnSize) {
    if(arrSize==0||k<=0){//如果数组大小为0或者前k个数小于等于0,那么返回的是空数组,且大小为0
        return NULL;
        *returnSize=0;
    }
    int *returnarr=(int*)malloc(sizeof(int)*k);
    int i=0;
    for(i=0;i<k;i++){
        returnarr[i]=arr[i];//将前K个数存入返回数组中
    }
    for(i=(k-2);i>=0;i--){
        AdjustDown(returnarr,k,i);//建大堆
    }
    for(i=k;i<arrSize;i++){//如果数组中剩余的值比堆顶元素小,则替换堆顶元素
        if(arr[i]<returnarr[0]){
            returnarr[0]=arr[i];
            AdjustDown(returnarr,k,0);
        }
    }
    *returnSize=k;
    return returnarr;

}

图解:

二叉树的遍历

学习二叉树结构,最简单的方式就是遍历,所谓二叉树的遍历就是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。


按照规则,二叉树的遍历有:前序,中序,后序的递归结构遍历(这三种遍历是根据访问根的先后顺序进行划分的)

  • 前序遍历(也叫做先序遍历)------访问根节点的操作发生在遍历其左右子树之前(根---左子树---右子树)
  • 中序遍历------访问根节点的操作发生在遍历其左右子树之中(左子树---根---右子树)
  • 后序遍历------访问根节点的操作发生在遍历其左右子树之后(左子树---右子树---根)

下面我们来看一个具体的例子,当然,在写他们的遍历时,可以省去NULL

前序遍历的代码实现

复制代码
void preorderTraversal(TreeNode* root) {
    if (root == NULL) {
        printf("NULL ");
    }
    if (root != NULL) {
        printf("%d ", root->val);
        preorderTraversal(root->left);
        preorderTraversal(root->right);
    }
}

后序遍历的代码实现

复制代码
void postorderTraversal(TreeNode* root) {
    if (root == NULL) {
        printf("NULL ");
    }
    if (root != NULL) {
        postorderTraversal(root->left);
        postorderTraversal(root->right);
        printf("%d ", root->val);
    }
}

中序遍历的代码实现

复制代码
void inorderTraversal(TreeNode* root) {
    if (root == NULL) {
        printf("NULL ");
    }
    if (root != NULL) {
        inorderTraversal(root->left);
        printf("%d ", root->val);
        inorderTraversal(root->right);
    }
}

最终效果

层序排列:

除了以上三种遍历方法之外,还可以对二叉树进行层序遍历。假设二叉树根节点所在层数是1,那么层序遍历就是从根节点出发,首先访问第一层的节点,然后从左到右访问第二层上的节点,以此类推从上至下,从左往右逐层访问树的节点的过程就是层序遍历,也就是一层层遍历

当给定前序/后序+中序就能确定唯一的二叉树,而当给定前序+后序的话是不能确定唯一的二叉树的。

1.已知前序和后序

前序(根左右):EFHIGJK、中序(左右根):HFIEJKG

那么我们现在来判断二叉树

2.已知后序和中序

后序:bdeca,中序:badce;

有关二叉树的一些题目

1.求二叉树中得的节点个数

代码实现

复制代码
//方法一:与前中后序的遍历原理相同
int size = 0;//用全局变量来记录节点的个数
void BTreeSize1(BTNode* node) {
	if (node == NULL) {
		return;如果为空则直接返回
	}
	size++;
	BTreeSize1(node->left);//分别遍历左右子树,每次遍历加一就可以计算出节点的个数
	BTreeSize1(node->right);
}
//方法二:递归调用,返回自身的节点个数和左子树的所有节点以及右子树的所有节点
int BTreeSize2(BTNode* node) {
	if (node == NULL) {
		return 0;//如果为空就返回0
	}
	return 1 + BTreeSize2(node->left) + BTreeSize2(node->right);

}

下面分析一下递归的方法(在初学二叉树的时候,画图真的是一个很友好的方式啦)

2,计算二叉树的高度

思路分析

递归思路,每个节点记录自己左右子树返回来的值,判断左右孩子哪边返回的值最大,最终用自身的加上大的那一边

代码实现

复制代码
int BTreeHeight(BTreeNode* node) {
	if (node == NULL) {//如果为空,则返回0
		return 0;
	}
	int left = BTreeHeight(node->left);//计算左子树的高度
	int right = BTreeHeight(npde->right);//计算右子树的高度
	return left > right ? left + 1 : right + 1;//返回高度大的一遍,并且加上自身高度
}

3. 求二叉树中 第K层的节点个数

思路:

  • 当k=1时,表示我们已经到达目标层级

  • 当前非空节点就是我们要计数的节点,返回1

  • 如果k>1,我们需要向下一层探索

  • 分别计算左子树和右子树中第k-1层的节点数

  • 将左右子树的结果相加返回

代码描述

复制代码
int BTreeLevelKSize(BTNode* node,int k) {
	aseert(k > 0);//防止遇到不合法的k
	if (node == NULL) {
		return 0;
	}
	if (k == 1) {//第K层,节点数肯定为1
		return 1;
	}

	return BTreeLevelKSize(node->left, k - 1) + BTreeLevelKSize(node->right, k - 1);//加上左右子树的节点个数
}

4.查找节点中的值

思路:

  • 如果节点为空,则不满足条件
  • 分别在左右子树中查找
  • 如果节点中的值与所要查找的值相等,就返回该节点的位置

代码描述

复制代码
BTNode* BTreeFind(BTNode* root, BTDataType x) {
	if (root == NULL) {
		return NULL;
	}
	if (root->data == x) {
		return root;
	}
	BTNode* left = BTreeFind(root->left, x);
	BTNode* right = BTreeFind(root->right, x);
	if (left != NULL) {
		return left;
	}
	if (right != NULL) {
		return right;
	}
}

更多内容,敬请期待。

相关推荐
不吃香菜?1 小时前
贝叶斯算法实战:从原理到鸢尾花数据集分类
算法·分类·数据挖掘
不吃香菜?1 小时前
逻辑回归之参数选择:从理论到实践
算法·机器学习·逻辑回归
keep intensify2 小时前
【数据结构】- 栈
c语言·数据结构·算法·
小技与小术2 小时前
代码随想录算法训练营day12(二叉树)
数据结构·python·算法
Chrome深度玩家3 小时前
微博安卓版话题热度推荐算法与内容真实性分析
算法·机器学习·推荐算法
Demons_kirit3 小时前
LeetCode LCP40 心算挑战题解
java·数据结构·算法·leetcode·职场和发展
菜还不练就废了3 小时前
25.4.30数据结构|并查集 路径压缩
数据结构
每次的天空4 小时前
Android面试总结之GC算法篇
android·算法·面试
EanoJiang4 小时前
树与二叉树
算法
鑫—萍6 小时前
C++——入门基础(2)
java·开发语言·jvm·数据结构·c++·算法