上篇说的线性表的线性存储结构,有一个明显的确定,那就是每次插入和删除元素都需要移动大量的元素,能否想个办法解决它。我们可以使用本文提到的链表解决
线性表的链式存储结构每一个结点都拥有一个数据域和一个指针域(指向下一个节点),因此其不需要将所有结点存在一块连续的地址空间中
先贴上kotlin代码
/**
* 使用单链表实现,区别于java类库中的LinkedList(双向链表)
*/
class LinkedList<T> {
/**
* 头结点
*/
private val mHeader: Node = Node(null, null)
var size = 0
private set
/**
* 取出对应索引的数据
*/
fun get(index: Int): T? = node(index)?.data
/**
* 设置指定索引的数据
*/
fun set(index: Int, data: T?) {
node(index)?.data = data
}
/**
* 在指定位置添加若干个数据
* 如果链表的数据是Int,那么这个方法有问题
*/
@JvmOverloads
fun add(index: Int = size, vararg data: T) {
checkIndex(index, size)
var preNode: Node? = null
for (i in data) {
val node = Node(i, null)
if (index == 0) {
node.next = mHeader.next
mHeader.next = node
} else {
if (preNode == null) {
preNode = node(index-1)
}
node.next = preNode?.next
preNode?.next = node
}
size++
}
}
/**
* 移除指定位置的数据
*/
fun remove(index: Int) {
checkIndex(index)
if (index == 0) {
mHeader.next = node(1)
} else {
val preNode = node(index-1)
preNode?.next = preNode?.next?.next
}
size--
}
/**
* 找到对应索引位置的结点
*/
private fun node(index: Int): Node? {
checkIndex(index)
var tempNode: Node? = mHeader
for (i in 0..index) {
tempNode = tempNode?.next
}
return tempNode
}
/**
* 检查索引
*/
private fun checkIndex(index: Int, length: Int = size - 1) {
if (index !in 0..length) {
throw RuntimeException("out of range totalLength is $size index is $index")
}
}
override fun toString(): String {
val sb = StringBuilder("[")
var tempNode: Node? = mHeader
for (i in 0..(size-1)) {
tempNode = tempNode?.next
if (i != 0) {
sb.append(", ")
}
sb.append(tempNode?.data)
}
sb.append("]")
return sb.toString()
}
private inner class Node(var data: T?, var next: Node?)
}
- 链表获取指定位置元素的流程 时间复杂度O(n)
- 判断获取的位置是否小于列表大小,不正确就抛出异常
- 一个接一个往下查找直到找到对应位置
- 返回该位置结点的数据域
- 链表设置指定位置元素的流程 时间复杂度O(n)
- 判断获取的位置是否小于列表大小,不正确就抛出异常
- 一个接一个往下查找直到找到对应位置
- 设置该位置结点的数据域
- 链表在指定位置插入一个数据 时间复杂度O(n)
- 判断插入的位置是否小于列表大小,不正确就抛出异常
- 一个接一个往下查找直到找到插入位置的前一个结点P
- 新建一个节点N,并且设置N.next = P.next,P.next = N
- 链表删除指定位置的一个数据 时间复杂度O(n)
- 判断插入的位置是否小于列表大小,不正确就抛出异常
- 一个接一个往下查找直到找到插入位置的前一个结点P
- 设置P.next = P.next.next
- 4个方法时间复杂度都是O(n)还多了一个引用域那么相对于线性结构有什么优点呢?
其实链表元素的插入和删除主要时间花在了找到插入/删除前的一个结点,如果我们已经知道了该结点,那么插入和删除的时间复杂度将是O(1),因此当我们需要在同一个位置连续插入多个元素其优点就显现出来了