二叉树
目录
概念
这里聊的二叉树就是链式存储的二叉树,如果是使用数组存储就是所谓堆结构,二叉树,顾名思义就是两个分叉的树,是一种分治思想下的数据结构产物。
有如下特性:
- 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1) 个结点。
- 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是2^h- 1。
- 任何一棵二叉树, 如果度为0其叶结点个数为 n0, 度为2的分支结点个数为 n2,则有n0=n2+1
- 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h=Log2(n+1)
思路
插入节点
将插入结点e,与结点root结点进行比较,若小于则去到左子树,否则去右子树进行比较,重复以上操作直到找到一个空位置放置该结点
删除节点
1.删除叶节点,直接删除
2.删除结点存在左子树,不存在右子树,直接把左子节点替代删除结点
3.删除结点存在有子节点,不存在左子节点,直接把右子节点代替删除结点
4.删除结点存在左右子节点,则取左子树最大结点或右子树最小系欸DNA替换删除结点
代码
#include<iostream> #include <stdlib.h> using namespace std; typedef struct _BNode { int data; struct _BNode *lchild, *rchild; }Bnode,* Btree; //二叉树插入结点 /*将插入结点e,与结点root结点进行比较,若小于则去到左子树,否则 去右子树进行比较,重复以上操作直到找到一个空位置放置该结点 */ bool InsertBtree(Btree* root, Bnode* node) { Bnode* temp = NULL; Bnode* parent = NULL; if (!node) { //如果插入结点为空,返回false return false; }else { //清空插入结点的左右子树 node->lchild = NULL; node->rchild = NULL; } if (!(*root)) { //如果根节点不存在,将插入结点作为根节点 *root = node; return true; }else { temp = *root; } while (temp != NULL) { parent = temp; if (temp->data>node->data) { //小于,继续向左 temp = temp->lchild; } else { //大于,继续向右(不可以有相同的值) temp=temp->rchild; } //while循环结束,parent所处位置即为要插入结点的父结点 } if (node->data < parent->data) { parent->lchild = node; } else { parent->rchild = node; } return true; } //二叉搜索树删除结点 //1.删除叶节点,直接删除 //2.删除结点存在左子树,不存在右子树,直接把左子节点替代删除结点 //3.删除结点存在有子节点,不存在左子节点,直接把右子节点代替删除结点 //4.删除结点存在左右子节点,则取左子树最大结点或右子树最小系欸DNA替换删除 //结点 int findLeftMax(Btree* root) { /*采用递归方式查找 * if (root->rchild == NULL) return root->data; return findMax(root->rchild); */ //采用循环查找 Btree indexNode = *root; while (indexNode->rchild) indexNode = indexNode->rchild; return indexNode->data; } //采用递归的方式删除结点 /* 这种递归方式,是将要修改的结点的一层一层的返回 */ Btree deleteNode(Btree* root, int value) { Btree compareNode = *root; //节点为空(递归找不到value/根节点为空),直接返回 if (!compareNode)return compareNode; //大于 if (compareNode->data > value) { //左子树重新被赋值 compareNode->lchild = deleteNode(&(compareNode->lchild), value); return compareNode; } //小于 else if (compareNode->data < value) { //右子树重新被赋值 compareNode->rchild = deleteNode(&(compareNode->rchild), value); return compareNode; } else {//等于 Btree temp = NULL; //无左右子节点,直接返回NULL if (compareNode->lchild == NULL && compareNode->rchild == NULL) { delete compareNode; } //有左子树,返回左子树 else if (compareNode->lchild && compareNode->rchild == NULL) { temp = compareNode->lchild; delete compareNode; } //有右子树,返回右子树 else if (compareNode->rchild && compareNode->lchild == NULL) { temp = compareNode->rchild; delete compareNode; } else { //这里采用左子树最大值替换 int leftMax = findLeftMax(&(compareNode->lchild)); //最大值替换删除结点的值 compareNode->data = leftMax; //将最大值从树中删除 compareNode->lchild = deleteNode(&(compareNode->lchild), leftMax); temp= compareNode; } return temp; } } // 查找结点 Bnode* queryByLoop(Btree root, int value) { while (root != NULL && root->data!=value) { if (root->data>value) { root = root->lchild; } else { root = root->rchild; } } return root; } void PreOrderRec(Btree* root) { Btree indexNode = *root; if (indexNode == NULL)return; printf("—%d", indexNode->data); PreOrderRec(&indexNode->lchild); PreOrderRec(&indexNode->rchild); } int main() { //_CrtSetBreakAlloc(86); int test[] = { 19,7,25,5,11,15,21,61 ,4}; Btree root = NULL; Btree node = NULL; node = new Bnode; node->data = test[0]; InsertBtree(&root, node); for (int i = 1; i < sizeof(test) / sizeof(int); i++) { node = new Bnode; node->data = test[i]; InsertBtree(&root, node); } printf("前序遍历\n"); PreOrderRec(&root); root= deleteNode(&root, 19); printf("\n"); printf("删除19后的前序遍历\n"); PreOrderRec(&root); printf("\n"); Btree temp; printf("查找数据为61\n"); temp = queryByLoop(root, 61); cout << temp->data << endl; system("pause"); return 0; }
算法考题
二叉树的非递归遍历(前序)
思路
- 优先判断树是否为空,空树不遍历。
- 准备辅助栈,首先记录根节点。
- 每次从栈中弹出一个元素,进行visit访问,然后验证该节点的左右子节点是否存在,存的话的加入栈中,优先加入右节点。
代码
void preorderNoRecursion(btNode* p) { if (p != NULL) { int top = -1; btNode* stack[50]; btNode* temp; stack[++top] = p; while (top != -1) { temp = stack[top--]; std::cout << temp->data << ' '; //viist if (temp->rc != NULL) stack[++top] = temp->rc;//push right child if (temp->lc != NULL) stack[++top] = temp->lc;//push left child } } }
二叉树的非递归遍历(中序)
思路
- 从头节点开始一直向左next,然后路上所有的node push到stack
- 左next为NULL时,stack pop 一个node,visit 这个node
- 判断node 是否有right child ,如果有当前节点设置为右孩子,继续重复1,2
- 如果没有右孩子,再从栈中pop 一个重复3
- stack 为空时结束
代码
void Inorder_I(BiTree T)//中序的非递归遍历 { stack<BiTNode*>s; BiTree p=T; while(p!=NULL||!s.empty())//栈不空或P不空时循环 { if(p) //一路向左 { s.push(p); //当前节点入栈 p=p->lchild; //左孩子不空,一直往左走 } else //出栈,并转向出栈节点的右子树 { p=s.top(); cout<<p->data; s.pop(); //栈顶元素出栈,访问出栈节点 p=p->rchild; //返回while循环继续进入if-else语句 } } }
二叉树的非递归遍历(后序)
思路
先序遍历顺序的根-左-右改为根-右-左
用这个栈把结果存起来,然后输出,就是后序遍历
代码
void postorderNoRecursion(btNode* p) { if (p != NULL) { btNode* stack1[50];//栈1为辅助栈 btNode* stack2[50];//栈2为访问栈 int top1 = -1, top2 = -1; btNode* temp; stack1[++top1] = p; while (top1 != -1)//修改先序非递归遍历进行交换 { temp = stack1[top1--]; stack2[++top2] = temp; if (temp->lc != NULL) stack1[++top1] = temp->lc;//先入左孩子结点 if (temp->rc != NULL) stack1[++top1] = temp->rc;//后入右孩子结点 } while (top2 != -1)//再逆序 std::cout << stack2[top2--]->data << ' ';//访问栈2中的数据 } }
二叉树的层序遍历
思路
- 使用队列,从头节点开始,加入队列,加入队列时visit
- 弹出队头,遍历左右孩子,并visit,然后把左右孩子加入队列
- 重复2.3,遇到NULL孩子不加入队列,不打印
- 直到队列为NULL
代码
class Solution { //二叉树的层次遍历 public: vector<vector<int>> levelOrder(TreeNode* root) { vector<vector<int>> result; queue<TreeNode*> que; if (root != nullptr){ que.push(root); } while (!que.empty()) { vector<int> temp; int length = que.size(); for (int i = 0; i < length; ++i) { TreeNode* tempNode = que.front(); que.pop(); temp.push_back(tempNode->val); if (tempNode->left) { que.push(tempNode->left); } if (tempNode->right) { que.push(tempNode->right); } } result.push_back(temp); } return result; } };
求二叉树的最大宽度
思路
层序遍历的变体,用一个map做记录,把每一个node 的层数作为key,value就是遍历一个+1
遍历完树就可以拿到所有层的宽度
也可以不使用map,多使用一个队列,一层使用一个队列,当一个队列为NULL时,另外一个队列达到最大值,记录一下这个层的number,一样可以做到上面的效果。
代码
int function(Node* head) { if (head == NULL) return 0; unordered_map<Node*, int> m;//装每个结点以及对应的第几层 queue<Node*> q;//宽度优先遍历 q.push(head);//先将头装进去 int max_w = 0;//记录最大宽度 int h = 0;//第几层 int floor[100] = { 0 };//每一层的结点个数 m.insert(make_pair(head, h)); while (!q.empty()) { //取出元素 Node* cur = q.front();//头拿出来 q.pop();//弹出去 //给压入的元素标号 h = m[cur]; if (cur->left != NULL) { q.push(cur->left); m.insert(make_pair(cur->left, h + 1)); } if (cur->right != NULL) { q.push(cur->right); m.insert(make_pair(cur->right, h + 1)); } floor[h]++; } //寻找每一层的最大结点数 for (int i = 0;i <= h-1;i++) max_w = max_w < floor[i] ? floor[i] : max_w; return max_w; }
二叉树的序列化与反序列化(前序方式)
思路
序列化和反序列化出了中序存在歧义,其他的方式都可以转(后序可以转为变体前序)。
反序列化的过程和序列化的过程也非常相似,核心就是占位符#。
非递归的前序反序列化主要是:
- 创建根节点,并入栈
- 不断反序列化,设置为node的左孩子,并入栈,直到遇到#
- 然后出栈一个node,序列化字符串的下一个元素若不为#,就设置为node的右孩子,继续2
- 若为#,继续出栈node重复3
- 直到序列化字符串结束
代码
//序列化函数 void serialization(btree* head, queue<int>* que) { //前序遍历入队--(递归法) if (!head) { //节点为空则返回 NULL 并 return que->push(NULL); return; } que->push(head->data); //入队 serialization(head->left, que); //左子树入队 serialization(head->right, que); //右子树入队 } //反序列化函数 - 递归版本 btree* bulidTree(queue<int>* que) { //按前序遍历入队的顺序依次出队, 建树 int num = que->front(); //获取队头元素 que->pop(); //出队 if (!num) { //若为空则返回 NULL return NULL; } btree* head = new btree; //非空则建立节点 head->data = num; //对节点赋值 head->left = bulidTree(que); //建立节点的左子树 head->right = bulidTree(que); //建立节点的右子树 return head; //返回节点 }
二叉树的序列化与反序列化(层序方式)
思路
层序方式的序列化和反序列化的核心在于标志空节点的占位符,比如#
序列化的时候需要NULL孩子,序列化为#
反序列化的时候,也是遍历的过程:
- 先把根创建出来,然后加入一个队列中
- 出队一个节点,然后从序列化字符中取两个值(可能有#)
- 如果是#就不管,如果不是#号,第一个值为左孩子,第二个值为右孩子,创建出来并入队
- 重复2,3,直到所有序列化字符
代码
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class Codec { public: /* 解题思路: 1.序列比:将所有节点的val(包括空节点)按照层次遍历存储到string,空节点存入null,用逗号','隔开 2.反序列化:将string每个元素分解成stringArr存储每个元素,然后遍历 */ // Encodes a tree to a single string. string serialize(TreeNode* root) { if (root == nullptr) return "#"; string result; std::queue<TreeNode*> nodeQueue; TreeNode* cur = nullptr; int isValid = false; int size = 0; nodeQueue.push(root); while (!nodeQueue.empty()) // 正常的层次遍历 { size = nodeQueue.size(); for (int i = 0; i < size; ++i) { cur = nodeQueue.front(); nodeQueue.pop(); if (cur == nullptr) // 这里有区别,如果是空节点则存储为'#' { result.append("#,"); continue; } result.append(std::to_string(cur->val) + ","); // 存储节点信息 nodeQueue.push(cur->left); //这里有区别,不管是否空节点都压入栈 nodeQueue.push(cur->right); //这里有区别,不管是否空节点都压入栈 } } return result; } void toStrArr(const string& data, vector<string>& valsStr) { string valStr; valStr.reserve(10); // 优化,预分配10个byte for (const char& ch : data) { if (ch == ',') { valsStr.push_back(valStr); // 将每个节点的值存入数组 valStr.clear(); // 初始化 } else { valStr.push_back(ch); // 存储每个节点的值 } } } // Decodes your encoded data to tree. TreeNode* deserialize(string data) { if (data.compare("#") == 0) // 如果整个字符串为空,则直接返回nullptr return nullptr; vector<string> valsStr; // 数组字符串存储每个节点信息 toStrArr(data, valsStr); // 将string转为数组字符串 int size = valsStr.size(); TreeNode* root = new TreeNode(std::stod(valsStr[0])); TreeNode* parent = nullptr; std::queue<TreeNode*> nodeQueue; nodeQueue.push(root); // 将根节点压入栈 for (int i = 1; i < size; ++i) { parent = nodeQueue.front(); nodeQueue.pop(); if (valsStr[i].compare("#")) { parent->left = new TreeNode(std::stod(valsStr[i])); // 创建该节点左子节点 nodeQueue.push(parent->left); // 非空则压入栈,顺序访问每个节点 } if (valsStr[++i].compare("#")) { parent->right = new TreeNode(std::stod(valsStr[i])); // 创建该节点右子节点 nodeQueue.push(parent->right); // 非空则压入栈,顺序访问每个节点 } } return root; // 返回原始二叉树 } }; // Your Codec object will be instantiated and called as such: // Codec ser, deser; // TreeNode* ans = deser.deserialize(ser.serialize(root));