数据结构与算法 - 双链表 - 大整数相加
工作中虽然主打 Objc,但是接触 Swift 后就很难回去,在用 Swift 写过一些 Demo 程序时,她的优雅和简练以及它所反映的编程思想深深地让人迷恋。本系列文章会用 Swift 实现一些常见算法,以期待能够在实战中持续学习 Swift 并对基础数据结构算法做整理和回顾。
样式
双链表是在单链表的基础上,扩展了一个前向指针,使得双链表可以在两个方向自由地移动。下面将定义一个存放单个数字的链表,用来展示大整数加法和 LRU 算法。
每一个节点除了包含数据空间,还必须包含前向和后向的指针。定义节点:
1
2
3
4
5
6
7
8
9
10class DigitNode {
var val: Int!
var next: DigitNode?
var previous: DigitNode?
init(_ val: Int) {
self.val = val
self.next = nil
self.previous = nil
}
}定义双链表,为了方便,新增加了存放节点个数信息的属性:
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
32class DigitList {
var digitList: DigitNode?
var headerDigit: DigitNode?
var tailDigit: DigitNode?
var nodesCnt: Int = 0
init(_ numStr:String) throws {
for s in numStr {
guard let digit = Int(String(s)) else {
throw BigNumberError.InvalidDigit
}
let digitNode = DigitNode(digit)
if self.headerDigit == nil {
self.headerDigit = digitNode
}
self.tailDigit?.next = digitNode
digitNode.previous = self.tailDigit
self.tailDigit = digitNode
self.nodesCnt += 1
}
}
}
enum BigNumberError: Error {
case InvalidDigit
}
组建链表时,时刻注意前向和后向指针的指向,不至于出现野指针、循环指向问题
非负大整数相加
“大整数”使用链表存放,整数相加时,使用竖式计算,从右往左逐位相加,封 10 进一。代码如下:
1 |
|
为了方便打印,添加如下扩展:
1 |
|
验证代码:
1 |
|
双链表常用操作
- 头部插入(如上)
尾部追加
1
2
3
4
5
6func appendTail(with node:DigitNode) {
node.previous = self.tailDigit
self.tailDigit?.next = node
self.tailDigit = node
node.next = nil
}中间插入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 在 node 节点后面插入新节点 newNode
func insert(after node:DigitNode?, with newNode:DigitNode)throws -> Bool {
guard node != nil else {
throw BigNumberError.NullParamOccur
}
var work = self.headerDigit
while work != nil {
if work === node {
// 找到了锚节点,准备插入
newNode.next = work?.next
work?.next?.previous = newNode
work?.next = newNode
newNode.previous = work
return true
}
work = work?.next
}
return false
}- 倒置
- 常规方法是使用一个工作指针,指向头部,然后顺次滑向尾部,逐个 node 操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20func reverse1() {
var workNode = self.headerDigit?.next
if workNode?.next == nil {
return
}
// 尾部指针直接指向新的头部
self.tailDigit = self.headerDigit
while workNode != nil {
let formerNext = workNode?.next
workNode?.next = self.headerDigit
self.headerDigit?.previous = workNode
self.headerDigit = workNode
workNode = formerNext
}
self.headerDigit?.previous = nil
self.tailDigit?.next = nil
} - 示例中的链表记录了元素个数,可以使用折半交换的方式倒置,效率比常规方法高
1
2
3
4
5
6
7
8
9
10
11
12
13
14func reverse2(){
var tempHeade = self.headerDigit
var tempTail = self.tailDigit
var tempCnt = 0
while tempCnt < self.nodesCnt / 2 {
let val = tempHeade?.val
tempHeade?.val = tempTail?.val
tempTail?.val = val
tempCnt += 1
tempHeade = tempHeade?.next
tempTail = tempTail?.previous
}
}
- 常规方法是使用一个工作指针,指向头部,然后顺次滑向尾部,逐个 node 操作:
LRU
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!