二叉树与递归(一)

二叉树与递归

一:二叉树的重要性

我对于快速排序和并归排序的理解:快速排序就是一个二叉树的前序遍历,并归排序就是个二叉树的后序遍历。

  1. 快速排序的逻辑是,若要对nums[lo..hi]进行排序,我们下找到一个分界点p,通过交换元素使得nums[lo..p-1]都小于等于nums[p],并且nums[p+1..hi]都大于nums[p],然后递归的去nums[lo..p-1]nums[p+1..hi]中寻找新的分界点,最后整个数组就被排序了。

快速排序的代码如下:

void sort(int* arr,int l,int r){
	if(l>r)return;
    int x = l;
    int y = r;
    int z = arr[l];
	while(x<y){
		while(arr[y]>z)y--;
        if(x<=y)arr[x++] = arr[y];
        while(arr[x]>z)x++;
        if(x<=y)arr[y--]=arr[x];
    }
    arr[x] = z;
	sort(arr,l,x-1);
    sort(arr,x+1,r);
    return;
}

这是升级版,手贱写了一下。

void sort(int* arr,int l,int r){
	while(l<r){
		int x = l;
    	int y = r;
    	int z = arr[(x+y)>>1];
        do{
			while(arr[x]<z){
				x++;
            }
            while(arr[y]<z){
				y--
            }
        	if(x<y){
				swap(arr[x],arr[y]);
                x++;
                y--;
            }
        }while(x <= y);
        sort(arr,l,y);
        l = x;
    }
}

先构造这一层的分界点,再到左右数组中去构造分界点,这就是二次叉树的前序遍历

  1. 并归排序
    若要对nums[lo..hi]进行排序我们先对nums[lo,..,mid]进行排序,再对nums[mid+1,..hi]进行排序,最后将这两个有序数组进行合并,这个数组就合并好了。

    代码框架如下:

void merge_sort(int* arr,int l,int r){
if(l == r)return ;
int mid = (l+r)>>1;
merge_sort(arr,l,mid);
merge_sort(arr,mid+1,r);
int* tmp = (int)malloc(sizeof(int)(r-l+1));
int p1 = l;
int p2 = mid+1;
int k = 0;
while(p1<=mid || p2 <= r){
if(p2 > r || (p1 <= mid && arr[p1] < arr[p2])){
tmp[k++] = arr[p1++];
}else{
tmp[k++] = arr[p2++];
}
}
memcpy(arr+l,tmp,sizeof(int)*(r-l+1));
free(tmp);
return;
}




先对左右子数组排序,然后合并,这就是二叉树的==后序遍历框架==



## 二,写递归算法的秘诀

***学递归算法的关键是要明确函数的``定义``是什么,然后相信这个定义,利用这个定义推导出最终结果,不要掉入递归细节***

举个例子,比如让你计算一颗二叉树共有多少个结点:

~~~cpp
int count(TreeNode root){
 if(!root)return 0;
 
 return 1+count(root->left)+count(root->right);
}

root本身就是一个结点,加上左右子树的节点数就是root为根的树的节点个数。

左右子树的节点数怎么算?其实就是计算根为root.leftroot.right两棵树的节点个数。所以按照定义(函数的定义是计算出根结点个数),递归的调用count函数即可。

写树的相关算法,就要搞清楚当前root结点【该做什么】以及【什么时候做】,然后根据函数定义递归调用子节点,递归调用会让孩子节点做相同的事情。

  • 【该做什么】就是让你想清楚写什么让的代码能够实现题目想要的效果。
  • 【什么时候做】,就是让你思考这段代码应该写在前序(从上往下),中序还是后续(从下往上)遍历的代码位置上。

算法实践

1.翻转二叉树翻转一棵二叉树。

示例:

输入

4
/
2 7
/ \ /
1 3 6 9
输出:

4
/
7 2
/ \ /
9 6 3 1
备注:
这个问题是受到 Max Howell 的 原问题 启发的 :

谷歌:我们90%的工程师使用您编写的软件(Homebrew),但是您却无法在面试时在白板上写出翻转二叉树这道题,这太糟糕了。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/invert-binary-tree
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

两种思路:前序遍历和后序遍历

  1. 前序遍历是从上到下一层一层的往下交换的,所以交换的具体情况如下:

​ 4

/ \

2 7 先换这一层的结点

/ \ / \

9 6 3 1 第二次遍历是就是换这一层结点

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if(root == NULL)return NULL;
        TreeNode* temp = root->left;
        root->left = root->right;
        root->right = temp;
        invertTree(root->left);
        invertTree(root->right);
        return root;
    }
};
  1. 后序遍历是从下往上

​ 4

/ \

7 2 第二次遍历是就是换这一层结点

/ \ / \

6 9 1 3 先换这一层的结点

代码演示
class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if(root == NULL)return NULL;
        TreeNode* left = invertTree(root->left);
        TreeNode* right = invertTree(root->right);
        root->left = right;
        root->right = left;
        return root;
    }
};

2. 填充二叉树节点的右侧指针

题目描述

给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:

struct Node {
int val;
Node *left;
Node *right;
Node *next;
}

填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。

初始状态下,所有 next 指针都被设置为 NULL。

进阶:

你只能使用常量级额外空间。
使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。

示例:

输入:root = [1,2,3,4,5,6,7]
输出:[1,#,2,3,#,4,5,6,7,#]
解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。序列化的输出按层序遍历排列,同一层节点由 next 指针连接,'#' 标志着每一层的结束。

提示:

树中节点的数量少于 4096
-1000 <= node.val <= 1000

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

:这个问题的难点是,如何把题目的要求细化成每个节点需要做的事情,如果只依赖一个结点的话,肯定是没有办法链接跨父节点这两个相邻节点的。例如上面例题中的结点5和结点6。

所以正确的做法就是增加函数的参数,一个结点做不到,借给他安排两个结点。

代码演示

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* left;
    Node* right;
    Node* next;

    Node() : val(0), left(NULL), right(NULL), next(NULL) {}

    Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {}

    Node(int _val, Node* _left, Node* _right, Node* _next)
        : val(_val), left(_left), right(_right), next(_next) {}
};
*/

class Solution {
public:
    void func(Node* l,Node* r){
        if(l == NULL || r == NULL)return ;
        //将传入的两个结点链接
        l->next = r;
        //链接同父节点
        func(l->left,l->right);
        func(r->left,r->right);
        //链接跨父节点
        func(l->right,r->left);
        return ;
    }

    Node* connect(Node* root) {
        if(root == NULL)return NULL;
        func(root->left,root->right);
        return root;
    }
};

第三题:

给你二叉树的根结点 root ,请你将它展开为一个单链表:

展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
展开后的单链表应该与二叉树 先序遍历 顺序相同。

示例 1:

输入:root = [1,2,5,3,4,null,6]
输出:[1,null,2,null,3,null,4,null,5,null,6]
示例 2:

输入:root = []
输出:[]
示例 3:

输入:root = [0]
输出:[0]

提示:

树中结点数在范围 [0, 2000] 内
-100 <= Node.val <= 100

进阶:你可以使用原地算法(O(1) 额外空间)展开这棵树吗?

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/flatten-binary-tree-to-linked-list
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

定义函数

flatten函数输入一个结点root,那么以root为根的二叉树就会被拉平为一条链表。

我们再梳理一下,如何按照题目要求将一棵树拉平成一条链表?

  1. root的左子树和右子树拉平。
  2. root的右子树接到左子树下方,然后将整个左子树作为右子树。

这些通过递归通过递归可以实现,我们并不能所清楚每一个细节,但是只要知道flatten的定义是这样的,并且相信这个定义,让root做他该做的事,然后让flatten函数就会按照定义去工作。另外注意到递归框架是后序遍历,对于这后序遍历,怎么说呢?你想想,后序遍历是树的底部开始操作的,如果从顶部开始开始对节点操作,那么下面还没有呈直线的结点就会比较难操作,所以最好从树的底部开始进行成直线的操作,这样只要根节点重复子节点的操作就可以了。你看看上面的图示过程也是从底部开始的。

代码演示

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    void flatten(TreeNode* root) {
        if(!root)return;
        flatten(root->left);
        flatten(root->right);
        //cout<<root->val<<endl;
        if(root->left){
            TreeNode* temp = root->left;
            TreeNode* n = root->left;
            while(n->right)n = n->right;
            n->right = root->right;
            root->right = temp;
        }
        root->left = NULL;
        TreeNode* n = root;
        while(n){
            cout<<n->val;
            n = n->right;
        }
        cout<<endl;
    }
};
posted @ 2021-11-04 01:20  shawchakong  阅读(66)  评论(0)    收藏  举报