Sept 21, 2022
class SinglyLinkedList[T] extends Seq[T]
{
var head: Option[SinglyLinkedListNode[T]] = None
/* ... */
}
class SinglyLinkedListNode(
var value: T,
var next: Option[SinglyLinkedListNode[T]] = None
)
Implementing length.
def length: Int =
{
var i = 0
var current = head
while(current.isDefined){ i += 1; curr = curr.get.next }
return i
}
Complexity: $O(n)$
Idea: Keep track of the length
class SinglyLinkedList[T] extends Seq[T]
{
var head: Option[SinglyLinkedListNode[T]] = None
var length = 0
/* ... */
}
Complexity: $O(1)$
apply(2).
Implementing apply.
def apply(idx: Int): T =
{
var current = head
for(i <- 0 until idx){
if(current.isEmpty) { throw IndexOutOfBoundsException(idx) }
current = current.get.next
}
if(current.isEmpty) { throw IndexOutOfBoundsException(idx) }
return current
}
Complexity: $O(n)$ (or $\Theta(\texttt{idx})$)
insert(1, "D").
Implementing insert.
def insert(idx: Int, value: T): Unit =
{
if(idx == 0){
head = Some( new SinglyLinkedListNode(value, head) )
} else {
var current = head
for(i <- 0 until idx){
if(curr.isEmpty) { throw IndexOutOfBoundsException(idx) }
curr = curr.get.next
}
curr.next = Some( new SinglyLinkedListNode(value, curr.next) )
}
length += 1
}
Complexity: $O(n)$ (or $\Theta(\texttt{idx})$)
Let's use apply()
def sum(list: List[Int]): Unit =
{
val total: Int = 0
for(i <- 0 until list.length){ total += list(i) }
return total
}
What is the complexity?
$$=\sum_{i = 0}^{n-1} \frac{(n-1)(n-1+1)}{2} = \frac{n^2 - n}{2} = \Theta(n^2)$$
Can we do better?
def sum(list: List[Int]): Unit =
{
val total: Int = 0
val current = list.head
while(current.isDefined){
total += current.get.value
current = current.get.next
}
return total
}
What is the complexity? $\sum_{i = 0}^{n-1} \Theta(1) = (n-1+1)\cdot \Theta(1) = \Theta(n)$
Why does this work?
What is the expensive part of apply?
Index → Value: $\Theta(idx)$
(access by index)
SinglyLinkedListNode → Value: $\Theta(1)$
(access by reference)
An iterator is:
1: For some definition of next.
class ListIterator[T](
var current: Option[SinglyLinkedListNode[T]]
)
{
def hasNext: Boolean = current.isDefined
def next: T =
{
val ret = current.get.value
current = current.get.next
return ret
}
}
... back to LinkedLists
How about positional operations:
insertAfter(pos: SinglyLinkedListNode[T], value: T)
insertAfter(pos, "D")
Implementing a positional insertAfter
def insertAfter(pos: SinglyLinkedListNode[T], value: T) =
{
pos.next = Some(
new SinglyLinkedListNode(value, pos.next)
)
length += 1
}
What is the complexity? $\Theta(1)$
How would you implement a positional remove?
def remove(pos: SinglyLinkedListNode[T]): T =
{
val prev = ??? /* Problem: Need element **before** pos. */
prev.next = pos.next
length -= 1
return pos.get.value
}
Idea: Add a "backwards" pointer.
class DoublyLinkedList[T] extends Seq[T]
{
var head: Option[DoublyLinkedListNode[T]] = None
var last: Option[DoublyLinkedListNode[T]] = None
var length = 0
/* ... */
}
class DoublyLinkedListNode[T](
var value: T,
var next: Option[DoublyLinkedListNode[T]] = None
var prev: Option[DoublyLinkedListNode[T]] = None
)
How would you implement a positional insertAfter?
def insertAfter(pos: DoublyLinkedListNode[T], value: T) =
{
val newNode = new DoublyLinkedListNode(value, prev = Some(pos))
if(pos.next.isDefined){ pos.next.prev = Some(newNode)
newNode.next = pos.next }
else { last = newNode
newNode.next = None }
pos.next = Some(newNode)
length += 1
}
How would you implement a positional remove?
def remove(pos: DoublyLinkedListNode[T]): T =
{
if(pos.prev.isDefined){ pos.prev.next = pos.next }
else { head = pos.next }
if(pos.next.isDefined){ pos.next.prev = pos.prev }
else { tail = pos.prev }
length -= 1
return pos.get.value
}
Operation | Array[T] | ArrayBuffer[T] | List[T] (Index) | List[T] (Ref) |
---|---|---|---|---|
apply(i) | $\Theta(1)$ | $\Theta(1)$ | $\Theta(i)$, $O(n)$ | $\Theta(1)$ |
update(i, value) | $\Theta(1)$ | $\Theta(1)$ | $\Theta(i)$, $O(n)$ | $\Theta(1)$ |
insert(i, value) | $\Theta(n)$ | $O(n)$, ... | $\Theta(i)$, $O(n)$ | $\Theta(1)$ |
remove(i, value) | $\Theta(n)$ | $\Theta(n-i)$, $O(n)$ | $\Theta(i)$, $O(n)$ | $\Theta(1)$ |
append(i) | $\Theta(n)$ | $O(n)$, Amortized $\Theta(1)$ | $\Theta(i)$, $O(n)$ | $\Theta(1)$ |