算法基础:二叉树与常见玩法

二叉树

目录

二叉树

概念

思路

代码

算法考题


概念

这里聊的二叉树就是链式存储的二叉树,如果是使用数组存储就是所谓堆结构,二叉树,顾名思义就是两个分叉的树,是一种分治思想下的数据结构产物。

有如下特性:

  1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1) 个结点。
  2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是2^h- 1。
  3. 任何一棵二叉树, 如果度为0其叶结点个数为 n0, 度为2的分支结点个数为 n2,则有n0=n2+1
  4. 若规定根节点的层数为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;
}
 
 

算法考题

二叉树的非递归遍历(前序)

思路

  1. 优先判断树是否为空,空树不遍历。
  2. 准备辅助栈,首先记录根节点。
  3. 每次从栈中弹出一个元素,进行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
  }
 }
}

二叉树的非递归遍历(中序)

思路

  1. 从头节点开始一直向左next,然后路上所有的node push到stack
  2. 左next为NULL时,stack pop 一个node,visit 这个node
  3. 判断node 是否有right child ,如果有当前节点设置为右孩子,继续重复1,2
  4. 如果没有右孩子,再从栈中pop 一个重复3
  5. 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中的数据
	}
}

二叉树的层序遍历

思路

  1. 使用队列,从头节点开始,加入队列,加入队列时visit
  2. 弹出队头,遍历左右孩子,并visit,然后把左右孩子加入队列
  3. 重复2.3,遇到NULL孩子不加入队列,不打印
  4. 直到队列为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;
}

二叉树的序列化与反序列化(前序方式)

思路

序列化和反序列化出了中序存在歧义,其他的方式都可以转(后序可以转为变体前序)。

反序列化的过程和序列化的过程也非常相似,核心就是占位符#。

非递归的前序反序列化主要是:

  1. 创建根节点,并入栈
  2. 不断反序列化,设置为node的左孩子,并入栈,直到遇到#
  3. 然后出栈一个node,序列化字符串的下一个元素若不为#,就设置为node的右孩子,继续2
  4. 若为#,继续出栈node重复3
  5. 直到序列化字符串结束

代码

//序列化函数
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孩子,序列化为#

反序列化的时候,也是遍历的过程:

  1. 先把根创建出来,然后加入一个队列中
  2. 出队一个节点,然后从序列化字符中取两个值(可能有#)
  3. 如果是#就不管,如果不是#号,第一个值为左孩子,第二个值为右孩子,创建出来并入队
  4. 重复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));

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ym影子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值