#Swift 递归枚举 VS Struct 实现自引用 数据结构 (链表 二叉树)
Enumeration 和 Struct 是 value type 实现 自引用 (self retain) 的数据类型,可以有效避免引用计数管理的问题(Class 是 reference type),递归枚举 因为在自引用类型的使用上和处理上不需要考虑 Struct 实现的空节点 Optional 类型的处理,更readable一些。
在 Swift 中,递归枚举(Recursive Enumerations) 是指一种枚举类型,其中一个或多个枚举 case
可能包含该枚举类型本身作为关联值。这允许你构建数据结构,如树或链表等,递归枚举是处理这些自引用结构的一种重要工具。
递归枚举的定义
由于 Swift 编译器需要知道枚举的内存布局,而递归枚举可能导致编译器无法直接推断它的大小,因此你需要使用 indirect
关键字来声明递归枚举。
indirect
可以用在整个枚举前,也可以只标记递归 case
。
递归枚举的常见用法场景
- 表达式求值:在编程语言中,表达式的结构通常是递归的,比如算术表达式,可以嵌套其他表达式。递归枚举可以用来表示和计算这样的嵌套表达式。
- 树结构:许多算法和数据结构,如二叉树或文件系统,都是递归定义的结构。每个节点可能有多个子节点,而子节点本身又是同样的类型。
- 链表:链表是一种递归的数据结构,每个节点指向下一个节点,直到链表结束。
让我们详细看一下递归枚举在这些场景中的应用。
1. 表达式求值的递归枚举
递归枚举非常适合用来表示像算术表达式这样的递归结构。比如表达式 (5 + 4) * 2
,你可以使用递归枚举表示加法和乘法之间的嵌套关系:
indirect enum ArithmeticExpression {case number(Int)case addition(ArithmeticExpression, ArithmeticExpression)case multiplication(ArithmeticExpression, ArithmeticExpression)
}func evaluate(_ expression: ArithmeticExpression) -> Int {switch expression {case .number(let value):return valuecase .addition(let left, let right):return evaluate(left) + evaluate(right)case .multiplication(let left, let right):return evaluate(left) * evaluate(right)}
}// 构造表达式 (5 + 4) * 2
let expression = ArithmeticExpression.multiplication(.addition(.number(5), .number(4)),.number(2)
)// 计算表达式的值
print(evaluate(expression)) // 输出 18
这里递归枚举的好处
- 枚举中的每个
case
可以递归地包含其他表达式。 - 你可以用很简单的代码构建嵌套的表达式结构,并用递归函数
evaluate
来处理每个case
。 - 递归定义非常直观,表达式中的嵌套逻辑由递归结构自然表达。
2. 树结构的递归枚举
递归枚举非常适合表示树状结构,比如文件系统或组织架构。树状结构每个节点可能有多个子节点,而每个子节点本身也是一个子树。
indirect enum BinaryTree {case emptycase node(Int, BinaryTree, BinaryTree)
}// 创建一个简单的二叉树
let leftChild = BinaryTree.node(2, .empty, .empty)
let rightChild = BinaryTree.node(3, .empty, .empty)
let root = BinaryTree.node(1, leftChild, rightChild)// 定义一个递归函数来遍历树
func traverseInOrder(_ tree: BinaryTree) {switch tree {case .empty:returncase .node(let value, let left, let right):traverseInOrder(left)print(value)traverseInOrder(right)}
}traverseInOrder(root)
// 输出:
// 2
// 1
// 3
递归枚举的好处
- 二叉树的每个节点有两个子节点,递归定义树的结构非常自然。
- 通过递归函数
traverseInOrder
,你可以很方便地遍历每个节点,进行各种操作,比如打印、求和、查找等。
3. 链表的递归枚举
链表是一种简单的递归数据结构。每个链表节点包含数据,并指向下一个节点。
indirect enum LinkedList {case emptycase node(Int, LinkedList)
}// 创建一个链表:1 -> 2 -> 3 -> 空
let list = LinkedList.node(1, .node(2, .node(3, .empty)))// 定义一个递归函数来遍历链表
func printList(_ list: LinkedList) {switch list {case .empty:returncase .node(let value, let next):print(value)printList(next)}
}printList(list)
// 输出:
// 1
// 2
// 3
递归枚举的好处
- 链表本质上就是递归的结构,节点指向下一个节点,直到
empty
。 - 使用递归枚举可以直观地表示链表结构,并通过递归函数轻松遍历链表。
总结
递归枚举非常适合处理以下场景:
- 表达式求值:可以用递归枚举构建表达式树,简化处理。
- 树结构:用递归枚举可以方便地表示二叉树或多叉树的结构,并通过递归遍历和操作树节点。
- 链表结构:链表的递归本质和递归枚举完美匹配,链表的操作变得非常自然。
使用 struct
实现链表
链表是一种递归结构,其中每个节点包含一个值和指向下一个节点的引用。我们可以用 struct
来实现链表的递归结构。通常情况下,链表会有一个表示空节点的 nil
,我们可以通过 Optional
类型来表示这一点。
链表的定义
struct LinkedList<T> {var value: Tvar next: LinkedList<T>?init(value: T, next: LinkedList<T>? = nil) {self.value = valueself.next = next}
}
在这个链表实现中:
LinkedList<T>
是一个泛型结构体,可以存储任何类型的数据。- 每个节点包含一个
value
,表示当前节点的值。 next
是一个可选类型(LinkedList<T>?
),表示下一个节点,如果链表结束,则next
为nil
。
创建一个链表
我们可以用这个结构体创建一个链表,比如 1 -> 2 -> 3 -> nil
:
let node3 = LinkedList(value: 3)
let node2 = LinkedList(value: 2, next: node3)
let node1 = LinkedList(value: 1, next: node2)
这个链表是:
1 -> 2 -> 3 -> nil
遍历链表
我们可以写一个函数来递归遍历链表:
func printList(_ list: LinkedList<Int>?) {guard let list = list else { return }print(list.value)printList(list.next)
}printList(node1)
// 输出:
// 1
// 2
// 3
在这里,printList
函数通过递归遍历链表的每个节点,并打印出每个节点的值。
使用 struct
实现二叉树
二叉树是一种递归数据结构,其中每个节点最多有两个子节点:左子节点和右子节点。我们也可以用 struct
来实现二叉树的递归结构。
二叉树的定义
struct BinaryTree<T> {var value: Tvar left: BinaryTree<T>?var right: BinaryTree<T>?init(value: T, left: BinaryTree<T>? = nil, right: BinaryTree<T>? = nil) {self.value = valueself.left = leftself.right = right}
}
在这个二叉树实现中:
BinaryTree<T>
是一个泛型结构体,可以存储任何类型的数据。- 每个节点包含一个
value
,表示当前节点的值。 left
和right
是可选类型,表示左子树和右子树。如果子树不存在,则为nil
。
创建一个二叉树
我们可以创建一个简单的二叉树,例如:
1/ \2 3
let leftChild = BinaryTree(value: 2)
let rightChild = BinaryTree(value: 3)
let root = BinaryTree(value: 1, left: leftChild, right: rightChild)
遍历二叉树
我们可以写一个递归函数来进行二叉树的遍历。例如,中序遍历(先左子树,后根,再右子树):
func inOrderTraversal<T>(_ tree: BinaryTree<T>?) {guard let tree = tree else { return }inOrderTraversal(tree.left)print(tree.value)inOrderTraversal(tree.right)
}inOrderTraversal(root)
// 输出:
// 2
// 1
// 3
在这个递归函数中:
- 递归遍历左子树。
- 打印当前节点的值。
- 递归遍历右子树。
总结
- 链表:通过递归的
next
指针,每个节点指向下一个节点,直到链表结束。 - 二叉树:通过递归的
left
和right
指针,每个节点最多有两个子节点,分别指向左子树和右子树。
对比分析
-
struct
在实现递归结构时更像是传统的数据结构定义,适合简单的递归情况,尤其是在不需要表示多种状态时,struct
提供了更直观的语法。 -
enum
更适合表达有明确状态区分的递归结构,如树结构中的叶子节点、分支节点、空节点等。通过enum
的case
,你可以更清晰地表达这些状态,并使用模式匹配来处理递归逻辑。
选择 struct
还是 enum
取决于你的数据结构的复杂性和状态的需求:
- 如果只是简单地描述一组数据,
struct
更简洁。 - 如果数据结构有多个状态,且不同状态需要不同的处理逻辑,
enum
是更好的选择。
这两者在递归结构上的区别主要在于状态的表达方式和处理方式上的差异。