[DS] 01 Array List vs Linked List

4 minute read

Published:

In this post, we compare array list and linked list.

Python List

In python, a list is implemented underneath with an array. This leads to the following inefficiencies:

  • When inserting an element, all elements from the insertion point to the end must be shifted one position to the right.
  • When deleting an element, all elements from the deletion point to the end must be shifted one position to the left.
  • Arrays allocate space in advance, leading to space wastage.

Python’s built-in list automatically handles situations like this (e.g. when attempting to insert an element when the space is full, it allocates a new array, copies the contents of the existing array, and remaps the list to the new array). However, longer lists can lead to wastage of space and performance degradation.

Linked List

Linked lists follow a dynamic allocation approach where space is allocated every time an element is added, avoiding wastage of space.

Below is a Python code implementing only the insertion process of a linked list, distinguishing between inserting at index 0 and other indices:

class ListNode:
    def __init__(self, newItem, nextNode:'ListNode'):
        self.item = newItem
        self.next = nextNode
from ListNode import ListNode
# not using dummy Node
class LinkedListBasic:
    def __init__(self):
        self.__head = None
        self.__numItems = 0
    
    def insert(self, i, newItem):
        if i>0 :
            prev = self.get(i-1)
            newNode = ListNode(newItem, prev.next)
            prev.next = newNode
            self.__numItems +=1 

        elif i==0 :
            newNode = ListNode(newItem, self.__head)
            self.__head = newNode
            self.__numItems +=1

However, by using a dummy node so that the head points to it, there will always be a previous node before the node to be inserted, eliminating the need to consider cases where the node to be inserted is at the beginning.

from ListNode import ListNode
# using dummy Node
class LinkedListBasic:
    def __init__(self):
        self.__head = ListNode('dummy', None)
        self.__numItems = 0
    
    def insert(self, i:int, newItem):
        if i >= 0 and i <= self.__numItems:
            prev = self.__getNode(i-1)
            newNode = ListNode(newItem, prev.next)
            prev.next = newNode
            self.__numItems +=1 
        else:
            print("index out of range")

    def append(self, newItem):
        prev = self.__getNode(self.__numItems-1)
        newNode = ListNode(newItem, prev.next)
        prev.next = newNode
        self.__numItems += 1

    def pop(self, i:int):
        if i >= 0 and i <= self.__numItems-1:
            prev = self.__getNode(i-1)
            curr = prev.next
            prev.next = curr.next
            retItem = curr.item
            self.__numItems -= 1
            return retItem
        else :
            return None

    def remove(self, x):
        (prev, curr) = self.__findNode(x)
        if curr != None:
            prev.next = curr.next
            self.__numItems -= 1

    def __getNode(self, i:int):
        curr = self.__head
        for index in range(i+1):
            curr = curr.next
        return curr

    def __findNode(self, x) -> (ListNode, ListNode):
        prev = self.__head
        curr = prev.next
        while curr != None:
            if curr.item == x:
                return (prev, curr)
            else:
                prev, curr = curr, curr.next
        return (None, None)

    def get(self, i:int):
        if self.isEmpty():
            return None
        elif i >= 0 and i <= self.__numItems - 1:
            return self.__getNode(i).item
        else:
            return None

    def index(self, x) -> int:
        curr = self.__head.next
        for index in range(self.__numItems):
            if curr.item == x:
                return index
            else:
                curr = curr.next
        return -12345
    
    def count(self, x) -> int:
        cnt = 0
        curr = self.__head.next
        while curr != None:
            if curr.item == x:
                cnt += 1
            curr = curr.next
        return cnt
    
    def extend(self, a):
        for index in range(a.size()):
            self.append(a.get(index))

    def copy(self):
        a = LinkedListBasic()
        for index in range(self.__numItems):
            a.append(self.get(index))
        return a

    def reverse(self):
        a = LinkedListBasic()
        for index in range(self.__numItems):
            a.insert(0, self.get(index))
        self.clear()
        for index in range(a.size()):
            self.append(a.get(index))

    def sort(self) -> None:
        a = []
        for index in range(self.__numItems):
            a.append(self.get(index))
        a.sort
        self.clear()
        for index in range(len(a)):
            self.append(a[index])

    def isEmpty(self) -> bool:
        return self.__numItems == 0

    def size(self) -> int:
        return self.__numItems

    def clear(self):
        self.__head = ListNode("dummy", None)
        self.__numItems = 0

    def printList(self):
        curr = self.__head.next
        while curr != None:
            print(curr.item, end = ' ')
            curr = curr.next
        print()

The above implementation includes several inefficiencies:

  • In the append() method, a for loop is used to access the last element of the list. Similarly, extend(), copy(), and reverse() also involve looping. If these operations are used repeatedly, it incurs significant time overhead.
  • The reverse() method also adds the burden of creating a new list of the same size as the list being copied.

Array List vs Linked List

Let’s compare Array List and Linked List from the perspective of time complexity (worst case):

Accessing the i-th element:

  • Array List: O(1)
  • Linked List: O(n)

Searching for a specific element:

  • Sorted Array List: O(nlogn) (using binary search algorithm)
  • Unsorted Array List: O(n)
  • Sorted Linked List: O(n)
  • Unsorted Linked List: O(n)

Adding/removing an element:

  • Array List: O(n) (when inserting/deleting data at the beginning of the array, shifting n existing data)
  • Linked List: O(n) (the main process of inserting/deleting by modifying the links of surrounding nodes is O(1), but finding the node to insert/delete is O(n))

From a memory perspective, as mentioned earlier, Linked List offers superior memory efficiency.

Leave a Comment