用OC和Swift一起说说二叉树
什么是二叉树
二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。二叉树不是树的一种特殊情形,尽管其与树有许多相似之处,但树和二叉树有两个主要差别:
1、树中结点的最大度数没有限制,而二叉树结点的最大度数为2。
2、树的结点无左、右之分,而二叉树的结点有左、右之分。
二叉树的遍历
二叉树的遍历先总结下面几种方式:
1、前序遍历
2、中序遍历
3、后序遍历
4、层次遍历
下面这张图很好的解释了前三种遍历的正确顺序,其实在理解 前、中、后序遍历的时候我们只需要记住前中后说的是根节点的顺序,不是子节点的顺序,比如下面的 A、B、C三个节点中,前中后我们说的是 A 是前(先),中间、还是最后遍历。理解了这一点我们就比较容易理解正确的前中后序遍历的顺序.
二叉树的创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | /** 创建二叉树 @param Values 传入数组 @return return value description */ +(ZXTThreeObject * )CreatTreesWithValues:( NSArray * )Values{ __block ZXTThreeObject * rootNode = nil ; /** 这里顺便提一下这个循环遍历,可能不部分都用的是for循环或者for..in..来循环的 **/ /** 大家了解一下 NSEnumerator 遍历和Block遍历还有GCD中的dispatch_apply **/ [Values enumerateObjectsUsingBlock:^( id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { int value = [obj intValue]; rootNode = [ self AddTreeNode:rootNode andValue:value]; }]; return rootNode; } /** 递归的思想 这里简单说一下,局部变量是存储在栈中的,全局变量是存储在静态存储区的!每存储一个局部变量,编译器就会开辟一块栈区域来保存 方法第一次传递的node这个变量,编译器就开辟了栈区域保存了它的值,后面要是有嵌套调用了这个方法 编译器就又开辟新的栈区域保存它们的返回值,但不会影响第一次保存的值,你要调用多次的话,第一个保存的值也就是最后一个返回的**/ +(ZXTThreeObject * )AddTreeNode:(ZXTThreeObject *)node andValue:( int ) value{ if (!node) { node = [[ZXTThreeObject alloc]init]; node.Value = value; } else if (value <= node.Value){ /**值小于节点的值,插入到左节点**/ node.leftTreeNode = [ self AddTreeNode:node.leftTreeNode andValue:value]; } else { /**值大于节点的值,插入到右节点**/ node.rightTreeNode = [ self AddTreeNode:node.rightTreeNode andValue:value]; } return node; } |
正确的结果展示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | NSArray * array = @[ @2 , @3 , @7 , @5 , @9 , @4 , @6 , @1 , @8 ]; /** 上面的数组创建的二叉树应该是这样的 * 代表没有值 2 1 3 * 7 5 9 4 6 8 * **/ [ZXTThreeObject CreatTreesWithValues:array]; |
二叉树Swift创建代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | class ZXTreeObject : NSObject { var leftNode : ZXTreeObject ? var rightNode : ZXTreeObject ? var nodevalue : Int ? func CreatTreesWithValues ( Values :[ Int ]) - > ZXTreeObject { var rootNode : ZXTreeObject ? for value in Values { rootNode = self . AddTreeNode ( node : & rootNode , value ) } return rootNode ! } /**注意在Swift3中:函数签名中的下划线的意思是 告诉编译器,我们在调用函数时第一个参数不需要外带标签 这样,我们可以按照 Swift 2 中的方式去调用函数,还有这个下划线和参数名之间要有空格!必须要有! **/ func AddTreeNode ( node : inout ZXTreeObject ?, _ value : Int ) - > ZXTreeObject { if ( node == nil ) { node = ZXTreeObject () node ?. nodevalue = value } else if ( value < ( node ?. nodevalue )!) { //print("----- to left ") node . leftNode = AddTreeNode ( node : & node !. leftNode , value ) } else { //print("----- to right ") node . rightNode = AddTreeNode ( node : & node !. rightNode , value ) } // print("节点:",node!.nodevalue ?? 0) return node ! } } |
调用
1 2 3 | // 定义一个数组 let sortArray :[ Int ] = [ 2 , 3 , 7 , 5 , 9 , 4 , 6 , 1 , 8 ] _ = ZXTreeObject (). CreatTreesWithValues ( Values : sortArray ) |
这个没有使用到返回值的警告有下面两种消除的方式:
1 2 3 4 5 | /* 一:就像上面的加 _ = 在调用的函数前面 二:在函数声明的前面加上 @discardableResult 如: @discardableResult func AddTreeNode(node: inout ZXTreeObject?, _ value:Int) -> ZXTreeObject { }*/ |
计算二叉树的深度
二叉树的深度 OC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /** 树的深度 三种情况: 一:根节点要是不存在就返回0 二:左节点和右节点都不存在,到这里的前提是根节点存在,返回1 三:都存在,再递归获取深度,返回最大值! @param node 节点 @return return value description */ // 2017-03-03 14:06:15.248 算法OC版本[22755:262170] 数的深度==5 +( NSInteger )deepWithThree:(ZXTThreeObject *)node{ if (!node) { return 0; } else if (!node.leftTreeNode && !node.rightTreeNode){ return 1; } else { NSInteger leftDeep = [ self deepWithThree:node.leftTreeNode]; NSInteger rightDeep = [ self deepWithThree:node.rightTreeNode]; return MAX(leftDeep, rightDeep) + 1; } } |
二叉树的深度 Swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // Swift 求二叉树的深度 func deepWithThree ( rootNode : ZXTreeObject ?) - > Int { if (( rootNode ) == nil ){ return 0 } else if (( rootNode ?. leftNode ) == nil & & ( rootNode ?. rightNode ) == nil ){ return 1 } else { let deepLeft = self . deepWithThree ( rootNode : rootNode ?. leftNode ) let rightLeft = self . deepWithThree ( rootNode : rootNode ?. rightNode ) return max ( deepLeft , rightLeft ) + 1 } } // 调用 // 打印 ====== 5 let deep = node . deepWithThree ( rootNode : node ) print ( "======" , deep ) |
二叉树的宽度
二叉树的宽度OC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | /* 二叉树宽度的定义:各层节点数的最大值! */ +( NSInteger )WidthOfTree:(ZXTThreeObject * )RootNode{ // 根节点不存在,就返回 0 if (!RootNode) { return 0; } NSMutableArray * queeArray = [ NSMutableArray array]; NSInteger currentWidth = 0; NSInteger maxWidth = 1; // 前面 0 的不存在就 肯定有,至少是 1 [queeArray addObject:RootNode]; // 添加根节点 while (queeArray.count > 0) { // 这就是当前的节点的数目,第一层就只有根节点 宽度是1 // 下一层,按照下面逻辑添加了左右节点就变成了2 currentWidth = queeArray.count; // 遍历该层,删除原始数组中遍历过的节点,添加它下一层的节点。再循环遍历 for ( NSInteger i=0; i<currentWidth; i++) { ZXTThreeObject * treenode = [queeArray firstObject]; [queeArray removeObjectAtIndex:0]; if (treenode.leftTreeNode) { [queeArray addObject:treenode.leftTreeNode]; } if (treenode.rightTreeNode) { [queeArray addObject:treenode.rightTreeNode]; } } // 一层一层的遍历的时候去最大值,返回 maxWidth = MAX(maxWidth, queeArray.count); } return maxWidth; } |
二叉树的宽度Swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | func widthWithThree ( rootNode : ZXTreeObject ?) - > Int { if (( rootNode ) == nil ){ return 0 } else { var widthDataArray = Array < ZXTreeObject > () widthDataArray . append ( rootNode !) var maxWidth = 1 var currentWidth = 0 ; while ( widthDataArray . count > 0 ) { currentWidth = widthDataArray . count for _ in 0 .. < currentWidth { let node = widthDataArray . first widthDataArray . remove ( at : 0 ) if (( node ?. leftNode ) != nil ) { widthDataArray . append (( node ?. leftNode )!) } if (( node ?. rightNode ) != nil ){ widthDataArray . append (( node ?. rightNode )!) } } maxWidth = max ( currentWidth , widthDataArray . count ) } return maxWidth } } |
二叉树的先、中、后序遍历
OC方法遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | // 调用代码 NSMutableArray * dataArray = [ NSMutableArray array]; [ZXTThreeObject preorderTraversal:rootNode andHandle:^(ZXTThreeObject *threeNode) { NSString * value = [ NSString stringWithFormat:@ "%ld" ,threeNode.Value]; [dataArray addObject:value]; }]; NSLog (@ "=======%@" ,dataArray); // 方法实现代码 /** 这里所谓的先序,中序,或者后序说的都是根节点的顺序,这个可以看着上面的那张度娘的图片去好好体会一下。 handle就是一个回调,node就是根节点,这样就很容易理解记住了。其他的后序和中序就不写代码了,交换顺序就行了。 **/ +( void )preorderTraversal:(ZXTThreeObject *)node andHandle:( void (^)(ZXTThreeObject * threeNode))handle{ if (node) { // 根 if (handle) { handle(node); } //左 [ self preorderTraversal:node.leftTreeNode andHandle:handle]; //右 [ self preorderTraversal:node.rightTreeNode andHandle:handle]; } } |
Swift方法遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // 定义一个随机数组 let sortArray :[ Int ] = [ 2 , 3 , 7 , 5 , 9 , 4 , 6 , 1 , 8 ] var dataArray = Array < Int > () let node = ZXTreeObject (). CreatTreesWithValues ( Values : sortArray ) _ = node . preorderTraversal ( rootNode : node , handle : {( treeNode ) in dataArray . append (( treeNode ?. nodevalue )!) }) print ( dataArray ) /** 先序遍历 中序和后序就不写了,而上面OC的道理是一样的 **/ // 这是定义的闭包,注意它的位置和我们OC定义Block类似 typealias Handle = ( _ root : ZXTreeObject ?) - > Void ; func preorderTraversal ( rootNode : ZXTreeObject ?, handle :@ escaping Handle ) - > Void { if (( rootNode ) != nil ) { handle ( rootNode ); self . preorderTraversal ( rootNode : rootNode ?. leftNode , handle : handle ) self . preorderTraversal ( rootNode : rootNode ?. rightNode , handle : handle ) } } |
二叉树的层次遍历 (这个遍历方式可以参考我们获取树宽度时候的思路)
层次遍历OC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | /* 层次遍历 层次遍历,就是一层一层的遍历,下面的方法用到了队列的想法。 */ +( void )CengCiBLTreeNode:(ZXTThreeObject * ) RootNode handle:( void (^)(ZXTThreeObject * treenode))handle{ if (RootNode) { NSMutableArray * queeArray=[ NSMutableArray array]; // 添加了根节点进去 [queeArray addObject:RootNode]; while (queeArray.count>0) { // 取出数组中的第一个元素 ZXTThreeObject * rootnode = [queeArray firstObject]; if (handle) { // 添加这个元素的值到数组中去 handle(rootnode); } // 添加完这个元素的值就把这个元素删除了 [queeArray removeObjectAtIndex:0]; // 这个节点要有左节点的话,就添加左节点到数组中去,有右节点的话就添加右节点到数组中去。 // 建议一步一步研究这个添加顺序,就可以清晰的看到这个是一层一层的遍历到的。 if (rootnode.leftTreeNode) { [queeArray addObject:rootnode.leftTreeNode]; } if (rootnode.rightTreeNode) { [queeArray addObject:rootnode.rightTreeNode]; } } } } |
层次遍历Swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | /******* 调用/ var array = Array < Int > () _ = node . preorderTraversal ( rootNode : node , handle : {( treeNode ) in array . append (( treeNode ?. nodevalue )!) }) //array ====== [2, 1, 3, 7, 5, 4, 6, 9, 8] print ( "array ======" , array ) /******* 层次遍历/ func CengCiBLTreeNode ( rootNode : ZXTreeObject ?, handle :@ escaping Handle ) - > Void { if (( rootNode ) != nil ) { var dataArray = Array < ZXTreeObject > () dataArray . append ( rootNode !) while dataArray . count > 0 { handle ( rootNode ); dataArray . remove ( at : 0 ) if (( rootNode ?. leftNode ) != nil ) { dataArray . append (( rootNode ?. leftNode )!) } if (( rootNode ?. rightNode ) != nil ) { dataArray . append (( rootNode ?. rightNode )!) } } } } |
## 努力做一个合格的程序员。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话