数据结构与算法09:二叉树

news2024/11/26 6:06:00

目录

【树】

【二叉树】 

二叉树的遍历

Go代码实现

二叉树的复杂度分析

【二叉搜索树】

Go代码实现

【每日一练:移除元素】


【树】

什么是树?这个不用解释了吧,马路两边种的都是树。数据结构里面的“树”和现实生活中的树类似,像一颗反过来的树,最上面是树根,下面是树的各个分枝,如下图所示:

树里面的每个元素叫作“节点”,最顶端的是“根节点”(图中A是根节点),上下层级的是“父子节点”(A是B、C、D的父节点),同一层级的是“兄弟节点”(B、C、D之间是兄弟节点),最后一层是“叶子结点”(K、L、F、G、M、I、J是叶子结点)。

关于树的高度、深度、层数:

  • 节点的高度:节点到叶子节点的最长路径(边数)
  • 节点的深度:根节点到这个节点所经历的边的个数
  • 节点的层数:节点的深度 + 1
  • 树的高度:根节点的高度

【二叉树】 

在各种各样的树中,最常用的是二叉树,每个节点最多有两个子节点,分别是左子节点和右子节点,二叉树并不要求每个节点都有两个子节点,有的只有左子节点,有的节点只有右子节点。

同理,如果每个节点有四个节点的树就是“四叉树”,有八个节点的树就是“八叉树”。

要存储一棵二叉树,可以选择基于指针的二叉链式存储法,也可以选择基于数组的顺序存储法。

链式存储的二叉树:每个节点有三个字段,其中一个存储数据,另外两个是指向左右子节点的指针,只要拎住根节点就可以通过左右子节点的指针把整棵树都串起来。大部分二叉树代码都是通过链式结构来实现的。

数组存储的顺序二叉树:把根节点存储在 i 的位置,左子节点就存储在 i * 2 的位置,右子节点就存储在 i * 2 + 1 的位置;反过来,下标为 i/2 的位置存储的就是对应的父节点。通过这种方式,只要知道根节点存储的位置(为了方便计算子节点,根节点会存储在下标为 1 的位置),就可以通过下标计算把整棵树都串起来。

 

二叉树又分为:普通二叉树、满二叉树、完全二叉树、非完全二叉树等。

满二叉树:叶子节点全都在最底层,除了叶子节点之外,每个节点都有左右两个子节点。

完全二叉树:叶子节点都在最底下两层,最后一层的叶子节点都靠左排列,并且除了最后一层,其他层的节点个数都要达到最大。最后一层叶子结点靠左排列,是为了使用数组存储的时候不浪费太多空间。比如把上面的顺序二叉树改为非完全二叉树,就会出现浪费数组空间的情况。

二叉树的遍历

遍历二叉树的三种方法:前序遍历、中序遍历、后序遍历,这里的“序”指的是根节点的遍历顺序。

  • 前序遍历:先遍历根节点,然后遍历左子树,最后遍历右子树(根->左->右)。print(r) => foreach(r->left) => foreach(r->right)

  • 中序遍历:先遍历左子树,然后遍历根节点,最后遍历右子树(左->根->右)。foreach(r->left) => print(r) => foreach(r->right);

  • 后序遍历:先遍历左子树,然后遍历右子树,最后遍历根节点(左->右->根)。foreach(r->left) => foreach(r->right) => print(r);

  

二叉树的前中后序遍历

//二叉树的前、中、后序 遍历的伪代码
//前序遍历
void preOrder(Node* root) {
  if (root == null) return;
  print root //打印root节点
  preOrder(root->left); //打印左子树
  preOrder(root->right); //打印右子树
}
//中序遍历
void inOrder(Node* root) {
  if (root == null) return;
  inOrder(root->left); //打印左子树
  print root //打印root节点
  inOrder(root->right); //打印右子树
}
//后序遍历
void postOrder(Node* root) {
  if (root == null) return;
  postOrder(root->left); //打印左子树
  postOrder(root->right); //打印右子树
  print root //打印root节点
}

另外还可以对二叉树层序遍历:先遍历根节点,然后对于子节点中同一级的兄弟节点依次遍历,可以使用FIFO队列。 

Go代码实现

下面是Go语言实现二叉树遍历的代码:

package main

import "fmt"

type BinaryTree struct {
	Value     int
	LeftNode  *BinaryTree
	RightNode *BinaryTree
}

// 前序遍历: 根节点 ---> 左子树 ---> 右子树
func preOrder(node *BinaryTree) {
	if node == nil {
		return
	}
	fmt.Printf("%v ", node.Value)
	preOrder(node.LeftNode)
	preOrder(node.RightNode)
}

// 中序遍历: 左子树---> 根节点 ---> 右子树
func inOrder(node *BinaryTree) {
	if node == nil {
		return
	}
	inOrder(node.LeftNode)
	fmt.Printf("%v ", node.Value)
	inOrder(node.RightNode)
}

// 后序遍历: 左子树 ---> 右子树 ---> 根节点
func tailOrder(node *BinaryTree) {
	if node == nil {
		return
	}
	tailOrder(node.LeftNode)
	tailOrder(node.RightNode)
	fmt.Printf("%v ", node.Value)
}

// 层序遍历,从root节点一层一层的遍历
func levelOrder(rootNode *BinaryTree) {
	if rootNode == nil {
		return
	}
	var nodeSlice []*BinaryTree // 封装一个slice
	nodeSlice = append(nodeSlice, rootNode)
	recursion(nodeSlice) //递归遍历
}

// 递归遍历核心
func recursion(nodeSlice []*BinaryTree) {
	if len(nodeSlice) == 0 { // 如果当前层级slice为空,则结束遍历
		return
	}
	var nextSlice []*BinaryTree           // 创建新的节点slice,存储下一层需要遍历的node
	for i := 0; i < len(nodeSlice); i++ { //遍历当前nodeSlice
		node := nodeSlice[i]          //取出要遍历的node
		fmt.Printf("%v ", node.Value) //输出当前node的值
		if node.LeftNode != nil {     //当前node左子节点append到下一层nodeSlice中
			nextSlice = append(nextSlice, node.LeftNode)
		}
		if node.RightNode != nil { //当前node右子节点append到下一层nodeSlice中
			nextSlice = append(nextSlice, node.RightNode)
		}
	}
	recursion(nextSlice) //递归遍历下一层的nodeSlice
}

func main() {
	//测试
	/*
	       1
	      / \
	     2   3
	    / \   \
	   4   5   6
	*/
	var rootNode = &BinaryTree{
		Value: 1,
		LeftNode: &BinaryTree{
			Value: 2,
			LeftNode: &BinaryTree{
				Value: 4,
			},
			RightNode: &BinaryTree{
				Value: 5,
			},
		},
		RightNode: &BinaryTree{
			Value: 3,
			RightNode: &BinaryTree{
				Value: 6,
			},
		},
	}

	//前序遍历
	preOrder(rootNode) //1 2 4 5 3 6

	//中序遍历
	inOrder(rootNode) //4 2 5 1 3 6

	//后序遍历
	tailOrder(rootNode) //4 5 2 6 3 1

	//层序遍历
	levelOrder(rootNode) //1 2 3 4 5 6
}

二叉树的复杂度分析

  • 二叉树遍历过程中,函数调用栈的深度和树的高度有关,所以空间复杂度是O(H),H为树的高度。
  • 二叉树遍历过程中,每个结点都被访问了一次,所以二叉树遍历的时间复杂度是O(n)
  • 二叉树中添加和删除数据时,只需要通过指针建立连接关系就可以了,所以二叉树添加和删除操作的时间复杂度是O(1)

【二叉搜索树】

二叉搜索树又叫做 二叉查找树:在树中的任意一个节点,它的左子树中每个节点的值都要小于这个节点的值,右子树中每个节点的值都大于这个节点的值。也就是说以树的任意一个节点为分界线,左子树->当前节点->右子树 的数据由小到大。对二叉搜索树中序遍历后,可以输出一个从小到大的有序数据队列,时间复杂度是 O(n),非常高效。

二叉搜索树的查找操作: 

  • 首先判断根结点是否等于要查找的数据,如果是就返回;
  • 如果根结点大于要查找的数据,就在左子树中递归执行查找动作,直到查找到叶子结点;
  • 如果根结点小于要查找的数据,就在右子树中递归执行查找动作,直到查找到叶子结点;

 

二叉搜索树的插入操作(需要先执行上面的查找操作,找到对应位置后再插入元素):

  • 新插入的数据一般都是在叶子节点上,因此只需要从根节点开始依次比较要插入的数据和节点的大小关系。
  • 如果要插入的数据比节点数值大,并且节点的右子树为空,就将新数据插入到右子节点的位置;如果不为空,就再递归遍历右子树,查找插入位置。
  • 如果要插入的数据比节点数值小,并且节点的左子树为空,就将新数据插入到左子节点的位置;如果不为空,就再递归遍历左子树,查找插入位置。

二叉搜索树的删除操作:

  • 如果要删除的节点没有子节点,那么直接将父节点中指向要删除节点的指针置为 null。
  • 如果要删除的节点只有一个子节点(只有左子节点或者右子节点),那么需要更新父节点中指向要删除节点的指针,让它指向要删除节点的子节点就可以了。
  • 如果要删除的节点有两个子节点,需要找到这个节点的右子树中的最小节点,把它替换到要删除的节点上,然后再删除掉这个最小节点,因为最小节点肯定没有左子节点(如果有左子结点,那就不是最小节点了),此时可以应用上面两条规则来删除这个最小节点。(也可以将要删除的节点标记为“已删除”,但并不真正从树中将这个节点去掉,会比较浪费内存空间)

Go代码实现

下面是Go语言实现二叉搜索树的代码:

package main

import (
	"fmt"
)

// 二叉查找树根节点
type BinarySearchTree struct {
	Root *BinarySearchTreeNode // 树根节点
}

// 二叉查找树节点
type BinarySearchTreeNode struct {
	Value int64                 // 值
	Times int64                 // 值出现的次数
	Left  *BinarySearchTreeNode // 左子树
	Right *BinarySearchTreeNode // 右字树
}

// 初始化一个二叉查找树
func NewBinarySearchTree() *BinarySearchTree {
	return new(BinarySearchTree)
}

// 添加元素
func (tree *BinarySearchTree) Add(value int64) {
	// 如果没有树根,证明是颗空树,添加树根后返回
	if tree.Root == nil {
		tree.Root = &BinarySearchTreeNode{Value: value}
		return
	}
	// 将值添加进去
	tree.Root.Add(value)
}

func (node *BinarySearchTreeNode) Add(value int64) {
	if value < node.Value { // 如果插入的值比节点的值小,那么要插入到该节点的左子树中
		if node.Left == nil { // 如果左子树为空,直接添加
			node.Left = &BinarySearchTreeNode{Value: value}
		} else { // 否则递归
			node.Left.Add(value)
		}
	} else if value > node.Value { // 如果插入的值比节点的值大,那么要插入到该节点的右子树中
		if node.Right == nil { // 如果右子树为空,直接添加
			node.Right = &BinarySearchTreeNode{Value: value}
		} else { // 否则递归
			node.Right.Add(value)
		}
	} else { // 值相同,不需要添加,值出现的次数加1即可
		node.Times = node.Times + 1
	}
}

// 查找指定节点
func (tree *BinarySearchTree) Find(value int64) *BinarySearchTreeNode {
	if tree.Root == nil {
		// 如果是空树,返回空
		return nil
	}

	return tree.Root.Find(value)
}

func (node *BinarySearchTreeNode) Find(value int64) *BinarySearchTreeNode {
	if value == node.Value {
		// 如果该节点刚刚等于该值,那么返回该节点
		return node
	} else if value < node.Value {
		// 如果查找的值小于节点值,从节点的左子树开始找
		if node.Left == nil {
			// 左子树为空,表示找不到该值了,返回nil
			return nil
		}
		return node.Left.Find(value)
	} else {
		// 如果查找的值大于节点值,从节点的右子树开始找
		if node.Right == nil {
			// 右子树为空,表示找不到该值了,返回nil
			return nil
		}
		return node.Right.Find(value)
	}
}

// 删除指定的元素
func (tree *BinarySearchTree) Delete(value int64) {
	if tree.Root == nil {
		// 如果是空树,直接返回
		return
	}

	// 查找该值是否存在
	node := tree.Root.Find(value)
	if node == nil {
		// 不存在该值,直接返回
		return
	}

	// 查找该值的父亲节点
	parent := tree.Root.FindParent(value)

	// 第一种情况,删除的是根节点,且根节点没有儿子
	if parent == nil && node.Left == nil && node.Right == nil {
		// 置空后直接返回
		tree.Root = nil
		return
	} else if node.Left == nil && node.Right == nil {
		//  第二种情况,删除的节点有父亲节点,但没有子树

		// 如果删除的是节点是父亲的左儿子,直接将该值删除即可
		if parent.Left != nil && value == parent.Left.Value {
			parent.Left = nil
		} else {
			// 删除的原来是父亲的右儿子,直接将该值删除即可
			parent.Right = nil
		}
		return
	} else if node.Left != nil && node.Right != nil {
		// 第三种情况,删除的节点下有两个子树,因为右子树的值都比左子树大,那么用右子树中的最小元素来替换删除的节点,这时二叉查找树的性质又满足了。

		// 找右子树中最小的值,一直往右子树的左边找
		minNode := node.Right
		for minNode.Left != nil {
			minNode = minNode.Left
		}
		// 把最小的节点删掉
		tree.Delete(minNode.Value)

		// 最小值的节点替换被删除节点
		node.Value = minNode.Value
		node.Times = minNode.Times
	} else {
		// 第四种情况,只有一个子树,那么该子树直接替换被删除的节点即可

		// 父亲为空,表示删除的是根节点,替换树根
		if parent == nil {
			if node.Left != nil {
				tree.Root = node.Left
			} else {
				tree.Root = node.Right
			}
			return
		}
		// 左子树不为空
		if node.Left != nil {
			// 如果删除的是节点是父亲的左儿子,让删除的节点的左子树接班
			if parent.Left != nil && value == parent.Left.Value {
				parent.Left = node.Left
			} else {
				parent.Right = node.Left
			}
		} else {
			// 如果删除的是节点是父亲的左儿子,让删除的节点的右子树接班
			if parent.Left != nil && value == parent.Left.Value {
				parent.Left = node.Right
			} else {
				parent.Right = node.Right
			}
		}
	}
}

// 中序遍历
func (tree *BinarySearchTree) MidOrder() {
	tree.Root.MidOrder()
}

func (node *BinarySearchTreeNode) MidOrder() {
	if node == nil {
		return
	}

	// 先打印左子树
	node.Left.MidOrder()

	// 按照次数打印根节点
	for i := 0; i <= int(node.Times); i++ {
		fmt.Print(node.Value, " ")
	}

	// 打印右子树
	node.Right.MidOrder()
}

func main() {
	values := []int64{3, 6, 8, 20, 9, 2, 6, 8, 9, 3, 5, 40, 7, 9, 13, 6, 8}

	// 初始化二叉查找树并添加元素
	tree := NewBinarySearchTree()
	for _, v := range values {
		tree.Add(v)
	}
	fmt.Println(tree.Root.Value) //3

	// 查找存在的9
	node = tree.Find(9) //true

	// 删除存在的9后,再查找9
	tree.Delete(9)
	node = tree.Find(9) //false
	
	// 中序遍历,实现排序
	tree.MidOrder() //2 3 3 5 6 6 6 7 8 8 8 13 20 40 
}

Golang二叉树源代码:https://gitee.com/rxbook/go-algo-demo/tree/master/tree

关于PHP中对二叉树的遍历,参考:PHP二叉树1  和 PHP二叉树2

【每日一练:移除元素】

力扣27. 移除元素(源代码:leetcode/RemoveElement.go · 浮尘/go-algo-demo - Gitee.com)

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。

示例 1:输入:nums = [3,2,2,3], val = 3,输出:2, nums = [2,2]

示例 2:输入:nums = [0,1,2,2,3,0,4,2], val = 2,输出:5, nums = [0,1,3,0,4]

思路:要求原地修改数组,就不能开辟新的空间,分析如下:

  • 循环遍历一次当前数组(切片),如果当前元素的值不等于 val ,就把当前元素的值重新赋值给当前数组从零开始的下标;
  • 全部遍历一遍之后,所有等于val 的元素全都排到了数组的后面,然后直接返回已重新赋值的切片长度的子切片即可;
  • 数组中的每个元素最多需要校验一次就可以了,因此时间复杂度是 O(N),空间复杂度: O(1)。
package main

import "fmt"

func removeElement(nums []int, val int) (int, []int) {
	ret := 0
	//把nums[0:ret]当做新数组,把nums数组中不等于val的元素覆盖的插入到里面
	for i := 0; i < len(nums); i++ {
		if nums[i] != val {
			nums[ret] = nums[i]
			ret++
		}
	}

	// 返回新的数组中已经重新赋值过的长度,后面的都是传入的val
	return ret, nums[0:ret]
}

func main() {
	fmt.Println(removeElement([]int{3, 2, 2, 3}, 3))             //2 [2 2]
	fmt.Println(removeElement([]int{0, 1, 2, 2, 3, 0, 4, 2}, 2)) //5 [0 1 3 0 4]
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/595630.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

chatgpt赋能python:Python会动的图形:如何让你的网站活力四射

Python会动的图形&#xff1a;如何让你的网站活力四射 如果你想让你的网站更具生命力、吸引力和互动性&#xff0c;一种非常有效的方式是使用动态图形。而Python有许多强大的库可以帮助你实现这一目标。在本篇文章中&#xff0c;我们将讨论Python会动的图形的好处、如何实现和…

过滤器JavaWeb:Filter与拦截器Spring:Intercepter

过滤器与拦截器若同时存在&#xff0c;先执行过滤器的放行前&#xff0c;再执行整个拦截器&#xff0c;最后再执行过滤器的放行后 过滤器会拦截所有资源&#xff08;包括静态资源&#xff09;&#xff0c;拦截器只会拦截Spring环境的资源 Filter的使用 1、创建一个类implement…

深蓝学院C++基础笔记 第 1 章 C++初探

第 1 章 C初探 1&#xff0e;从Hello World 谈起 Hello World: #include <iostream> int mian() { std::cout << "Hello World!" << std::endl; }函数: 一段能被反复调用的代码&#xff0c;可以接收输入&#xff0c;进行处理并(或)产生输出-返回…

Postgres vs MySQL

主要区别及示例 简而言之&#xff0c;Postgres 和 MySQL 之间的主要区别实际上归结为主索引和辅助索引的实现方式以及数据的存储和更新方式。 让我们进一步探讨这个问题。 但首先... 基础知识 索引是一种数据结构&#xff08;主要是 B 树&#xff09;&#xff0c;允许通过…

DAY01_MySQL基础数据类型navicat使用DDL\DML\DQL语句练习

目录 1 数据库相关概念1.1 数据库1.2 数据库管理系统1.3 常见的数据库管理系统1.4 SQL 2 MySQL2.1 MySQL安装2.1.1 安装步骤 2.2 MySQL配置2.2.1 添加环境变量2.2.2 MySQL登录2.2.3 退出MySQL 2.3 MySQL数据模型2.4 MySQL目录结构2.5 MySQL一些命令2.5.1 修改默认账户密码2.5.2…

Linux 计划任务(at与crontab)

一次性计划任务 at Linux 中的【 at 】 命令是用来创建一次性计划任务的&#xff0c; at 命令有一个服务 atd 以后台的模式运行&#xff0c;通过检查当前的时间来决定是 否运行 " 计划 " &#xff0c;默认情况下&#xff0c; atd 服务每 60 秒检 查一次&#x…

【Web服务应用】Nginx服务

Nginx服务 一、Nginx概述1.1Nginx特点1.2Nginx作用1.3Nginx与Apache的差异 二、Nginx进程模型三、编译安装Nginx3.1Nginx服务的检查、启动、停止&#xff0c;重载3.2平滑升级3.3把nginx进程加入到系统服务当中 四、Nginx服务的主配置文件nginx.conf4.1补充什么是IO多路复用4.2根…

R语言:移动平均计算及绘图

问题描述 现在有一个分日期记录DAU的数据&#xff0c;现在需要绘制其360,180,90,30,7日移动平均值&#xff0c;来观测消除了波动干扰的DAU趋势 (实际移动在股价趋势图上非常常见) 原始数据格式如下&#xff1a; day &#xff08;character&#xff09; dau &#xff08;int…

Docker+Jenkins+Gitee自动化部署maven单模块项目

1.简介 各位看官老爷&#xff0c;本文为Jenkins实战&#xff0c;注重实际过程&#xff0c;阅读完会有以下收获&#xff1a; 了解如何使用Docker安装Jenkins了解如何使用Jenkins部署maven项目了解如何使用JenkinsGitee实现自动化部署 2.Jenkins介绍 相信&#xff0c;正在读这…

2023年上半年软件设计师上午真题及答案解析

1.计算机中&#xff0c;系统总线用于( )连接 A.接口和外设 B.运算器&#xff0c;控制器和寄存器 C.主存、外设部件 D.DMA控制器和中断控制器 2.在由高速缓存、主存和硬盘构成的三级存储体系中&#xff0c;CPU执行指令时需要读取数据&#xff0c;那么DMA控制…

深入理解Linux虚拟内存管理(一)

系列文章目录 Linux 内核设计与实现 深入理解 Linux 内核&#xff08;一&#xff09; 深入理解 Linux 内核&#xff08;二&#xff09; Linux 设备驱动程序&#xff08;一&#xff09; Linux 设备驱动程序&#xff08;二&#xff09; Linux 设备驱动程序&#xff08;三&#xf…

ubantu换配置源

文章目录 1.配置镜像源位置2.进入终端&#xff0c;切换到/home/user/etc/apt/3.默认这个文件是只读的&#xff0c;我们修改一下权限4.修改之前&#xff0c;我们先备份一下系统原来配置的源5.开始修改&#xff0c;打开/etc/apt/sources.list文件&#xff0c;将原来的内容删除&am…

chatgpt赋能python:Python代码怎么打包-全面介绍

Python 代码怎么打包 - 全面介绍 Python 是一种高效、易学易用、灵活多变的编程语言。对于 Python 开发者来说&#xff0c;如何将其编写的程序打包是一个必须掌握的技能。本文将着重介绍 Python 代码打包的方法及其优势&#xff0c;并提供一些实用的工具和技巧。 什么是打包?…

六级备考17天|2017年12月三套真题|翻译与写作|20:45~21:00

目录 第一套 翻译&#xff1a;太湖 中文 英文 词汇 作文 谚语题&#xff1a;respect others, and you will be respected 第二套 翻译&#xff1a;青海湖 中文 英文 词汇 第一套 翻译&#xff1a;太湖 中文 英文 词汇 太湖 Lake Tai 淡水湖 fre…

python+vue学生选课学习成绩分析及可视化分析系统

但目前国内的学习成绩分析及可视化分析信息仍然都使用人工管理&#xff0c;随着学校规模越来越大&#xff0c;同时课程信息量也越来越庞大&#xff0c;人工管理显然已无法应对时代的变化&#xff0c;而学习成绩分析及可视化分析能很好地解决这一问题&#xff0c;轻松应对学习成…

2020第十一届蓝桥杯Python组国赛【真题+解析+代码】

&#x1f381;2020第十一届蓝桥杯python组国赛真题 &#x1f680; 真题练习&#xff0c;冲刺国赛 &#x1f680; 2020年第十一届蓝桥python组国赛真题解析代码 博观而约取&#xff0c;厚积而薄发 &#x1f3c6;国赛真题目录 文章目录 &#x1f381;2020第十一届蓝桥杯python组国…

本地部署Jellyfin影音服务器【公网远程影音库】

文章目录 1. 前言2. Jellyfin服务网站搭建2.1. Jellyfin下载和安装2.2. Jellyfin网页测试 3.本地网页发布3.1 cpolar的安装和注册3.2 Cpolar云端设置3.3 Cpolar本地设置 4.公网访问测试5. 结语 转载自cpolar极点云的文章&#xff1a;零基础搭建私人影音媒体平台【远程访问Jelly…

华为OD机试真题 Java 实现【数组的中心位置】【2023Q1 100分】

一、题目描述 给你一个整数数组nums,请计算数组的中心位置,数组的中心位置是数组的一个下标,其左侧所有元素相乘的积等于右侧所有元素相乘的积。数组第一个元素的左侧积为1,最后一个元素的右侧积为1。如果数组有多个中心位置,应该返回最靠近左边的那一个,如果数组不存在…

chatgpt赋能python:Python代码转为C语言——提高效率的必经之路

Python代码转为C语言——提高效率的必经之路 Python是一种高级编程语言&#xff0c;具有易学易用的优点&#xff0c;因此越来越多的程序员选择使用Python来开发应用程序和脚本。但是&#xff0c;在开发高性能应用程序时&#xff0c;Python的效率问题会成为拦路虎。因此&#x…

每日一题——逆波兰表达式求值(前缀、中缀、后缀表达式的说明,库函数atoi()的解析)

文章目录 每日一题逆波兰表达式求值中缀&#xff0c;前缀&#xff08;波兰&#xff09;&#xff0c;后缀&#xff08;逆波兰&#xff09;表达式的基本概念逆波兰表达式的优点和计算方法优点计算方法 思路函数原型如何将数字入栈库函数atoi() 实现代码 每日一题 逆波兰表达式求…