一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情。
什么是单链表
单链表是一种常见的链式数据结构,它是由每个节点和指向节点的指针构成的。与同是链式结构的数组不同,它不需要操作系统分配整块的内存来存储元素。
在它进行添加元素的时候,操作系统只需要开辟存储单个元素的空间即可。
以下是实现单链表需要编写的函数:
func count() -> Int // 链表的长度
func isEmpty() -> Bool // 链表是否为空
func contains(ele: Element) -> Bool // 是否包含某个元素
func add(ele: Element) // 尾部添加元素
func insert(index: Int, ele: Element) // 在 index 插入元素
func getElement(index: Int) -> Element? // 获取 index 的元素
func setElement(index: Int, ele: Element) -> Element? //设置 index 的元素
func indexOf(ele: Element) -> Int? // 获取 ele 的当前索引
func remove(index: Int) -> Element? // 移除 index 的元素
func removeAll() // 移除所有元素
千里之行始于足下,下面让我们一个一个的实现。
代码实现
预备工作
在开始实现之前,我们需要做一些准备工作。
因为存放的元素需要放在节点中,所以首先我们需要创建一个节点类:
class Node<Element: Equatable> {
var element: Element
var next: Node?
init(ele: Element, next: Node?) {
self.element = ele
self.next = next
}
}
上述代码的作用:
- 因为节点需要存放任意类型的元素,所以我们将它声明为泛型。且后面需要进行元素判断,所以 Element 需要遵守 Equatable。
- element 用来存储元素,next 用来指向下一个节点。
创建完节点类,我们还需要写一下 LinkedList 的基本内容:
class LinkedList<Element: Equatable> {
var head: Node<Element>?
var size = 0
private func rangeCheck(index: Int) {
if index < 0 || index >= size {
fatalError("invalid array index")
}
}
private func rangeCheckForInsert(index: Int) {
if index < 0 || index > size {
fatalError("invalid array index")
}
}
}
上述代码的作用:
- 声明 head 指针,用来指向链表的头结点。
- 声明 size 变量,用来存储链表的节点数。
- rangeCheck:用来判断除插入操作的其他操作,索引是否合法;rangeCheckForInsert:用来判断插入操作的索引是否合法。
实现简单的函数
func count() -> Int {
return size
}
func isEmpty() -> Bool {
return size == 0
}
func contains(ele: Element) -> Bool {
return indexOf(ele: ele) != nil
}
func add(ele: Element) {
insert(index: size, ele: ele) // 在尾部添加元素
}
func removeAll() { // 清空链表直接将 size 设为 0,head 置为 nil,即可无需遍历去除所有的节点再置为 nil。
size = 0
head = nil
}
因为上述函数比较简单,这里就不做文字解释了。
insert
func insert(index: Int, ele: Element) {
rangeCheckForInsert(index: index)
if index == 0 {
let newHead = Node(ele: ele, next: head)
head = newHead
} else {
let pre = getNode(of: index - 1)
let node = Node(ele: ele, next: pre?.next)
pre?.next = node
}
size += 1
}
func getNode(of index: Int) -> Node<Element>? { // 遍历获取当前 index 的前一个节点
rangeCheck(index: index)
var cur = head
for _ in 0..<index {
cur = cur?.next
}
return cur
}
代码解释:
- 首先需要检查索引是否合法:index >= 0 && index <= size。
- 若 index 为 0,则代表在头部插入:
- 创建新的节点 - newHead,将 newHead 的 next 指向 head。
- 将 head 再指向 newHead 即可。
- 在除头结点的其他位置插入:
- 获取当前位置的前一个节点 - pre。
- 创建新节点 - node,将 node 的 next 指向 pre 的 next。
- 再讲 pre 的 next 指向 node 即可。
- 最后,size + 1。
getElement
func getElement(index: Int) -> Element? {
rangeCheck(index: index)
if index == 0 {
return head?.element
} else {
let node = getNode(of: index)
return node?.element
}
}
代码解释:
- 首先检查索引是否合法:index >= 0 && index < size。
- 若 index 为 0,则代表第一个节点,返回 head 的 element 即可。
- 若 index 不为 0,则调用 getNode 获取当前节点 - node,返回 node 的 element 即可。
setElement
func setElement(index: Int, ele: Element) -> Element? {
rangeCheck(index: index)
var old = head?.element
if index == 0 {
head?.element = ele
} else {
let node = getNode(of: index)
old = node?.element
node?.element = ele
}
return old
}
代码解释:
- 首先检查索引是否合法:index >= 0 && index < size。
- 若 index 为 0,则代表第一个节点,设置 head 的 element。
- 若 index 不为 0,则调用 getNode 获取当前节点 - node,保存 node 的 element,最后,设置 node 的 element 。
- 返回设值之前的旧值 - old。
remove
func remove(index: Int) -> Element? {
rangeCheck(index: index)
var removed = head?.element
if index == 0 {
head = head?.next
} else {
let pre = getNode(of: index - 1)
removed = pre?.next?.element
pre?.next = pre?.next?.next
}
size -= 1
return removed
}
代码解释:
- 首先检查索引是否合法:index >= 0 && index < size。
- 若 index 为 0,直接将 head 指向 head 的 next 即可删除头结点。
- 若 index 不为 0:
- 则调用 getNode 获取当前节点的前一个节点 - pre。
- 保存 pre 的 next 的 element(即要删除的节点的 element)。
- 将 pre 的 next 指向 pre 的 next 的 next。
- size - 1。
- 返回设值之前的旧值 - removed。
indexOf
func indexOf(ele: Element) -> Int? {
var node = head
for i in 0..<size {
if ele == node?.element {
return i
}
node = node?.next
}
return nil
}
代码解释:
- 声明 node,指向当前链表的头结点。
- 遍历链表,若 ele 等于 node 的 element,即找到,直接返回 i。正是因为此处需要元素进行对比,所以 Element 需要遵守
Equatable。 - 若遍历完链表都未找到,则代表链表中没有 ele。
至此,整个单链表就已经实现完了。可以看到代码量还是比较小的。
对单链表感兴趣的同学可以自己来实现一遍,来体会一下。需要注意的就是对 next 指针的挪动,一定要搞对。