102.二叉树的层序遍历
给一个二叉树,请你返回其按 层序遍历 得到的节点值。(即逐层,从左到右访问所有节点)。
动画如下:(记录每层的数量,弹出元素后把其孩子节点加入队列,因为有每层的数量就能知道元素是哪一层的,比如弹出4后,队列中有7、1、3,第二层size为2,弹出4后size剩一个)
int** levelOrder(struct TreeNode* root, int* returnSize, int** returnColumnSizes){
int** ans=(int**)malloc(sizeof(int*)*2000);
*returnSize=0;
if(!root) return NULL;//要在return前给出*returnSize的取值不然过不去;!!
int columnSizes[2000];//记录每一行的列数(每层结点数),因为要给出* returnColumnSizes
struct TreeNode* queue[2000];//模拟队列
int rear=0;int head=0;//记录队列头尾
queue[rear++]=root;//录入根结点
while(rear!=head){//队列不为空
ans[(*returnSize)]=(int*)malloc(sizeof(int)*(rear-head));
columnSizes[(*returnSize)]=rear-head;
int start=head;//记录遍历开始位置,即为本层的头
head=rear;//本层的尾部成为下次的头,因为所有的元素均弹出队列
//在这里下层的头head同时作为遍历结束的位置,因为在遍历中rear会不断改变,成为下层的尾
for(int i=start;i<head;i++){//弹出start到未变化的rear(即为head)之间的所有元素
ans[(*returnSize)][i-start]=queue[i]->val;
if(queue[i]->left) queue[rear++]=queue[i]->left;
if(queue[i]->right) queue[rear++]=queue[i]->right;//rear不断改变
}
(*returnSize)++;//一层遍历完*returnSize加一;
}
*returnColumnSizes=(int*)malloc(sizeof(int)*(*returnSize));//给出*returnColumnSizes
for(int i=0;i<*returnSize;i++) (*returnColumnSizes)[i]=columnSizes[i];
return ans;
}
java写法
// 102.二叉树的层序遍历
class Solution {
public List<List<Integer>> resList = new ArrayList<List<Integer>>();
public List<List<Integer>> levelOrder(TreeNode root) {
//checkFun01(root,0);
checkFun02(root);
return resList;
}
//DFS--递归方式
public void checkFun01(TreeNode node, Integer deep) {
if (node == null) return;
deep++;
if (resList.size() < deep) {
//当层级增加时,list的Item也增加,利用list的索引值进行层级界定
List<Integer> item = new ArrayList<Integer>();
resList.add(item);
}
resList.get(deep - 1).add(node.val);
checkFun01(node.left, deep);
checkFun01(node.right, deep);
}
//BFS--迭代方式--借助队列
public void checkFun02(TreeNode node) {
if (node == null) return;
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(node);
while (!que.isEmpty()) {
List<Integer> itemList = new ArrayList<Integer>();
int len = que.size();
while (len > 0) {
TreeNode tmpNode = que.poll();
itemList.add(tmpNode.val);
if (tmpNode.left != null) que.offer(tmpNode.left);
if (tmpNode.right != null) que.offer(tmpNode.right);
len--;
}
resList.add(itemList);
}
}
}
226.翻转二叉树
把每一个节点的左右孩子交换一下。
使用前序遍历和后序遍历都可以,唯独中序遍历不方便
(递归通过函数自调用实现循环;迭代通过循环语句实现循环。)
递归法
前序遍历翻转动画:
递归三部曲:
-
确定递归函数的参数和返回值
参数就是要传入节点的指针,不需要其他参数了,通常此时定下来主要参数,如果在写递归的逻辑中发现还需要其他参数的时候,随时补充。
返回值的话其实也不需要,但是题目中给出的要返回root节点的指针,可以直接使用题目定义好的函数,所以就函数的返回类型为
TreeNode*
。TreeNode* invertTree(TreeNode* root)
- 确定终止条件
当前节点为空的时候,就返回
if (root == NULL) return root;
- 确定单层递归的逻辑
因为是先前序遍历,所以先进行交换左右孩子节点,然后反转左子树,反转右子树。
// 手动交换左右子节点 swap(&(root->left), &(root->right)); // 递归地翻转左右子树 invertTree(root->left); invertTree(root->right);
完整代码如下(C语言中没有类和引用传递(通过指针实现),并且也没有swap
函数):
//首先,我们定义二叉树节点的结构体:
typedef struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode;
//实现翻转二叉树的函数
void swap(TreeNode** a, TreeNode** b) {
TreeNode* temp = *a;
*a = *b;
*b = temp;
}
TreeNode* invertTree(TreeNode* root) {
if (root == NULL)
return root;
// 手动交换左右子节点
swap(&(root->left), &(root->right));
// 递归地翻转左右子树
invertTree(root->left);
invertTree(root->right);
return root;
}
java写法
//首先,我们定义二叉树节点的类TreeNode:
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
//接下来,我们实现翻转二叉树的方法:
public 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;
}
}
迭代法
深度优先遍历
struct TreeNode* invertTree(struct TreeNode* root){
if(!root)
return NULL;
//存储结点的栈
struct TreeNode** stack = (struct TreeNode**)malloc(sizeof(struct TreeNode*) * 100);
int stackTop = 0;
//将根节点入栈
stack[stackTop++] = root;
//若栈中还有元素(进行循环)
while(stackTop) {
//取出栈顶元素
struct TreeNode* temp = stack[--stackTop];
//交换结点的左右孩子
struct TreeNode* tempNode = temp->right;
temp->right = temp->left;
temp->left = tempNode;
//若当前结点有左右孩子,将其入栈
if(temp->right)
stack[stackTop++] = temp->right;
if(temp->left)
stack[stackTop++] = temp->left;
}
return root;
}
广度优先遍历
struct TreeNode* invertTree(struct TreeNode* root) {
if (!root) return NULL;
// 存储节点的队列
struct TreeNode** queue = (struct TreeNode**)malloc(sizeof(struct TreeNode*) * 1000);
int front = 0, rear = 0; // 队列的头和尾
// 将根节点入队
queue[rear++] = root;
// 若队列中还有元素(进行循环)
while (front < rear) {
// 取出队头元素
struct TreeNode* node = queue[front++];
// 交换节点的左右孩子
struct TreeNode* tempNode = node->left;
node->left = node->right;
node->right = tempNode;
// 若当前节点有左右孩子,将其入队
if (node->left) queue[rear++] = node->left;
if (node->right) queue[rear++] = node->right;
}
free(queue); // 不要忘记释放分配的内存
return root;
}
java写法
//BFS广度优先
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null) {return null;}
ArrayDeque<TreeNode> deque = new ArrayDeque<>();
deque.offer(root);
while (!deque.isEmpty()) {
int size = deque.size();
while (size-- > 0) {
TreeNode node = deque.poll();
swap(node);
if (node.left != null) deque.offer(node.left);
if (node.right != null) deque.offer(node.right);
}
}
return root;
}
public void swap(TreeNode root) {
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
}
}
101. 对称二叉树
给定一个二叉树,检查它是否是镜像对称的。
只能是“后序遍历”,因为要通过递归函数的返回值来判断两个子树的内侧节点和外侧节点是否相等。
递归法
递归三部曲
1.确定递归函数的参数和返回值
因为我们要比较根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较两个树,参数就是左子树节点和右子树节点。返回值是bool类型。
bool compare(TreeNode* left, TreeNode* right)
2.确定终止条件
比较两个节点数值相不相同,首先要弄清楚两个节点为空的情况!否则后面比较数值的时候会操作空指针。
*节点为空的情况有:(注意我们比较的不是左孩子和右孩子,比较的是左节点和右节点)
- 左为空,右不为空,不对称,return 0
- 左不为空,右为空,不对称 return 0
- 左右都为空,对称,返回1
*左右节点不为空:
- 比较节点数值,不相同就return 0
if (left == NULL && right != NULL) {
return 0;
}
else if (left != NULL && right == NULL) {
return 0;
}
else if (left == NULL && right == NULL) {
return 1;
}
else if (left->val != right->val) {
return 0;
}
//c语言中通常非零值表示true,而零值表示false
//注意上面最后一种情况,没有使用else,而是else if, 因为把以上情况都排除之后,剩下的就是左右节点都不为空,且数值相同的情况。
3.确定单层递归的逻辑
此时才进入单层递归的逻辑,就是处理左右节点都不为空且数值相同的情况。
- 比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
- 比较内侧是否对称,传入左节点的右孩子,右节点的左孩子。
- 如果左右都对称就返回1,有一侧不对称就返回0。
int outside = compare(left->left, right->right); // 左子树左节点、 右子树右节点
int inside = compare(left->right, right->left); // 左子树右节点、 右子树左节点
int isSame = outside && inside; // 左子树中节点、右子树中节点(逻辑处理)
return isSame;
//如上代码中,使用的遍历方式为左子树左右中,右子树右左中,这个遍历顺序大概为“后序遍历”(不是严格的后序遍历)。
整体代码如下:
/*
#include <stdbool.h>
struct TreeNode {
int val;
struct TreeNode* left;
struct TreeNode* right;
};
*/
int compare(struct TreeNode* left, struct TreeNode* right) {
if (left == NULL && right != NULL) {
return 0;
}
else if (left != NULL && right == NULL) {
return 0;
}
else if (left == NULL && right == NULL) {
return 1;
}
else if (left->val != right->val) {
return 0;
}
int outside = compare(left->left, right->right);
int inside = compare(left->right, right->left);
int isSame = outside && inside;
return isSame;
}
int isSymmetric(struct TreeNode* root) {
if (root == NULL) {
return 1;
}
return compare(root->left, root->right);
}
迭代法
使用队列来比较两个树(根节点的左右子树)是否相互翻转。(这不是层序遍历)
使用队列
通过队列来判断根节点的左子树和右子树的内侧和外侧是否相等,如动画所示:
bool isSymmetric(struct TreeNode* root) {
/* 根节点为空,即对称 */
if (root == NULL) {
return true;
}
/* 创建队列,保存节点 */
struct TreeNode** queue = (struct TreeNode**)malloc(sizeof(struct TreeNode*) * 10000);
int head = 0, rear = 0;
queue[rear++] = root->left; // 将根节点的左子树头结点加入队列
queue[rear++] = root->right; // 将根节点的右子树头结点加入队列
/* 迭代遍历二叉树 */
while (head != rear) {
struct TreeNode* left = queue[head++]; // 弹出左子树节点
struct TreeNode* right = queue[head++]; // 弹出右子树节点
/* 若两者都为空,则当前节点的左右孩子匹配,继续 */
if (!left && !right) {
continue;
}
/* 有一个为空另一个不为空,或者两者都不为空但是节点值不相等 */
if (!left || !right || left->val != right->val) {
return false;
}
/* 到这里则表示当前两节点匹配,再添加当前节点的左右节点入队列 */
queue[rear++] = left->left; // 加入左节点左孩子
queue[rear++] = right->right; // 加入右节点右孩子
queue[rear++] = left->right; // 加入左节点右孩子
queue[rear++] = right->left; // 加入右节点左孩子
}
return true;
}
使用栈
将当前节点的左右节点对称的入栈,再出栈进行比较
bool isSymmetric(struct TreeNode* root) {
/* 根节点为空,即对称 */
if (root == NULL) {
return true;
}
/* 创建栈,保存节点 */
struct TreeNode** stack = (struct TreeNode**)malloc(sizeof(struct TreeNode*) * 10000);
int top = -1;
stack[++top] = root->left; // 将根节点的左子树头结点入栈
stack[++top] = root->right; // 将根节点的右子树头结点入栈
/* 迭代遍历二叉树 */
while (top != -1) {
struct TreeNode* right = stack[top--]; // 弹出栈顶的右子树节点
struct TreeNode* left = stack[top--]; // 弹出栈顶的左子树节点
/* 若两者都为空,则当前节点的左右孩子匹配,继续 */
if (!left && !right) {
continue;
}
/* 有一个为空另一个不为空,或者两者都不为空但是节点值不相等 */
if (!left || !right || left->val != right->val) {
return false;
}
/* 到这里则表示当前两节点匹配,再添加当前节点的左右节点入栈 */
stack[++top] = left->left; // 左节点的左孩子入栈
stack[++top] = right->right; // 右节点的右孩子入栈
stack[++top] = left->right; // 左节点的右孩子入栈
stack[++top] = right->left; // 右节点的左孩子入栈
}
return true;
}