Android

A Guide to Common Data Structures: Arrays, Lists, Linked Lists, Sets, Stacks, Queues, Trees, Maps, Graphs, and Heaps

In computer science, a data structure is a way of organizing and storing data in a computer program. There are many different types of data structures, each with their own strengths and weaknesses. In this post, we’ll explore some of the most common data structures, and provide examples of how they can be used.

Arrays

Arrays are a fundamental data structure that store a collection of elements of the same type in contiguous memory. They offer constant-time access to elements by index, but have fixed size and are inefficient at inserting or deleting elements.


val numbers = intArrayOf(1, 2, 3, 4, 5)
val sum = numbers.sum()

Lists

Lists are similar to arrays, but can dynamically grow or shrink in size as elements are added or removed. They offer constant-time access to elements by index, and efficient insertion and deletion operations.

val numbers = mutableListOf(1, 2, 3, 4, 5)
numbers.add(6)
numbers.removeAt(0)

Linked Lists

Linked Lists are a dynamic data structure that store a collection of elements in nodes that are linked to each other. They offer efficient insertion and deletion operations, but have slower access times and require more memory than arrays or lists.

class Node<T>(var data: T, var next: Node<T>?)

class LinkedList<T> {
    var head: Node<T>? = null
    var tail: Node<T>? = null
    var size = 0
    
    fun add(element: T) {
        val node = Node(element, null)
        if (head == null) {
            head = node
            tail = node
        } else {
            tail?.next = node
            tail = node
        }
        size++
    }
}

Sets

Sets are a data structure that store a collection of unique elements in no particular order. They offer efficient membership tests and set operations, but have slower access times than arrays or lists.

val numbers = setOf(1, 2, 3, 4, 5)
val containsThree = numbers.contains(3)

Stacks

Stacks are a data structure that store a collection of elements in a last-in, first-out (LIFO) order. They offer efficient insertion and deletion operations, and are used in many applications, such as function call stacks and undo operations.

val stack = Stack<Int>()
stack.push(1)
stack.push(2)
stack.push(3)
val top = stack.pop()

Queues

Queues are a data structure that store a collection of elements in a first-in, first-out (FIFO) order. They offer efficient insertion and deletion operations, and are used in many applications, such as job scheduling and message queues.

val queue = LinkedList<Int>()
queue.add(1)
queue.add(2)
queue.add(3)
val first = queue.removeFirst()

Heaps

Heaps are a specialized tree-based data structure that store a collection of elements in a way that allows for efficient extraction of the smallest or largest element. They are used in many applications, such as priority queues and sorting algorithms.

class MinHeap {
    private val elements = mutableListOf<Int>()
    
    fun insert(element: Int) {
        elements.add(element)
        siftUp(elements.size - 1)
    }
    
    private fun siftUp(index: Int) {
        var i = index
        while (i > 0 && elements[i] < elements[parentIndex(i)]) {
            swap(i, parentIndex(i))
            i = parentIndex(i)
        }
    }
    
    fun extractMin(): Int? {
        if (elements.isEmpty()) {
            return null
        }
        val min = elements[0]
        elements[0] = elements.last()
        elements.removeLast()
        siftDown(0)
        return min
    }
    
    private fun siftDown(index: Int) {
        var i = index
        while (hasLeftChild(i)) {
            var smallerChildIndex = leftChildIndex(i)
            if (hasRightChild(i) && rightChild(i) < leftChild(i)) {
                smallerChildIndex = rightChildIndex(i)
            }
            if (elements[i] < elements[smallerChildIndex]) {
                break
            } else {
                swap(i, smallerChildIndex)
            }
            i = smallerChildIndex
        }
    }
    
    private fun parentIndex(index: Int) = (index - 1) / 2
    private fun leftChildIndex(index: Int) = 2 * index + 1
    private fun rightChildIndex(index: Int) = 2 * index + 2
    private fun hasLeftChild(index: Int) = leftChildIndex(index) < elements.size
    private fun hasRightChild(index: Int) = rightChildIndex(index) < elements.size
    private fun leftChild(index: Int) = elements[leftChildIndex(index)]
    private fun rightChild(index: Int) = elements[rightChildIndex(index)]
    private fun swap(i: Int, j: Int) {
        val temp = elements[i]
        elements[i] = elements[j]
        elements[j] = temp
    }
}

These are just a few of the many data structures that are available for use in computer programs. Choosing the right data structure for a particular problem can be critical to the performance and scalability of the program.

Conclusion

In conclusion, understanding data structures and algorithms is fundamental for any programmer who wants to write efficient and scalable code. A thorough knowledge of the different data structures available and the problems they can solve is essential for making the right choices when designing a program. This post has provided an overview of some of the most commonly used data structures, along with snippets of code to help illustrate their implementation.

It is important to note that while these data structures are powerful tools, they should be used with care. In some cases, a simple data structure like an array or a list might be more appropriate than a more complex data structure like a tree or a graph. The key is to choose the right data structure for the job, and to have a good understanding of its properties and performance characteristics.

By incorporating a deep understanding of data structures and algorithms into your programming practice, you can write more efficient, scalable, and elegant code, and become a better programmer overall.