7.1 Introduction to Data Structures, Lists, Linked Lists, and Trees

Data structures are essential concepts in computer science that organize and store data efficiently to allow operations like searching, insertion, deletion, and traversal. Understanding different types of data structures and their applications is fundamental to solving complex computational problems.


1. Data Types, Data Structures, and Abstract Data Types (ADT)

  • Data Types: Refers to the type of value a variable can hold, such as integers, floats, and strings.

  • Data Structures: These are ways of organizing and storing data, such as arrays, lists, trees, and graphs.

  • Abstract Data Types (ADT): These are high-level descriptions of data structures, abstracting the implementation details. Examples include Stack, Queue, List, and Map. ADTs define operations but not their concrete implementation.


2. Time and Space Complexity Analysis of Algorithms

  • Time Complexity: Describes the amount of time an algorithm takes relative to the size of the input. It is often expressed using Big-O notation.

  • Space Complexity: Describes the amount of memory an algorithm uses relative to the input size.

  • Big-O (O): Describes the upper bound (worst-case) behavior of an algorithm.

  • Omega (Ω): Describes the lower bound (best-case) behavior.

  • Theta (Θ): Describes the average-case behavior.


3. Linear Data Structures

A linear data structure organizes elements in a sequential order, where each element has a unique predecessor and successor (except the first and last elements).

Examples:

  • Arrays: A collection of elements stored in contiguous memory locations.

  • Lists: A dynamic sequence of elements with flexible size.

  • Stacks: A LIFO (Last In, First Out) structure where elements are added and removed from the top.

  • Queues: A FIFO (First In, First Out) structure where elements are added at the rear and removed from the front.


4. Stack and Queue Implementation

  • Stack: A Last-In-First-Out (LIFO) data structure where elements are added and removed from the same end, called the "top."

    • Operations: Push (add), Pop (remove), Peek (view top element)

  • Queue: A First-In-First-Out (FIFO) data structure where elements are added at the "rear" and removed from the "front."

    • Operations: Enqueue (add), Dequeue (remove), Front (view front element)


5. Stack Applications

Infix to Postfix Conversion:

Infix notation requires parentheses to dictate precedence, while postfix (Reverse Polish Notation) eliminates the need for parentheses. A stack is used to help convert infix expressions into postfix form.

Infix to Postfix Conversion with Precedence

Expression: A + B * C - D / E

Here, operator precedence is considered:

  1. * and / (higher precedence)

  2. + and - (lower precedence)

Precedence Rules:

  • Operators with higher precedence are applied first.

  • Operators of equal precedence are evaluated left-to-right.


Steps Using a Stack

Step

Action

Stack

Postfix Result

1

Add A to the result

A

2

Push +

+

A

3

Add B to the result

+

A B

4

Push * (higher precedence)

+, *

A B

5

Add C to the result

+, *

A B C

6

Encounter -: Pop * first (higher precedence), then + (equal precedence). Push -.

-

A B C * +

7

Add D to the result

-

A B C * + D

8

Push / (higher precedence)

-, /

A B C * + D

9

Add E to the result

-, /

A B C * + D E

10

Pop /

-

A B C * + D E /

11

Pop -

A B C * + D E / -


Final Postfix Expression:

A B C * + D E / -


Explanation of Precedence Handling:

  1. When encountering an operator, the stack is popped until the operator at the top of the stack has lower precedence, or the stack is empty.

  2. Higher-precedence operators (* and /) are evaluated before lower-precedence operators (+ and -).

  3. Operators of equal precedence are popped in a left-to-right manner (e.g., + before -).

This ensures that the final postfix expression respects the precedence rules while maintaining the correct order of operations.


Postfix to Infix conversion:

Postfix expressions can be evaluated easily using a stack. Operands are pushed onto the stack, and when an operator is encountered, the necessary operands are popped, the operation is performed, and the result is pushed back onto the stack.

Postfix Expression:

A B + C D - * E /


Step-by-Step Conversion Using a Stack

Step

Action

Stack

1

Push A

A

2

Push B

A, B

3

Encounter + → Pop B, A, combine as (A + B), push back

(A + B)

4

Push C

(A + B), C

5

Push D

(A + B), C, D

6

Encounter - → Pop D, C, combine as (C - D), push back

(A + B), (C - D)

7

Encounter * → Pop (C - D), (A + B), combine as ((A + B) * (C - D)), push back

((A + B) * (C - D))

8

Push E

((A + B) * (C - D)), E

9

Encounter / → Pop E, ((A + B) * (C - D)), combine as (((A + B) * (C - D)) / E)

(((A + B) * (C - D)) / E)


Final Infix Expression:

(((A + B) * (C - D)) / E)


Key Points:

  1. No Precedence Rules Needed:

    • Postfix inherently ensures the operators are processed in the correct order.

    • Conversion back to infix only requires grouping operands with their operators.

  2. Parentheses Are Added Explicitly:

    • To preserve the postfix order during the conversion, parentheses are added to show precedence in the resulting infix expression.

Thus, postfix inherently removes precedence concerns, simplifying the reverse conversion!


6. Array Implementation of Lists

An array is a collection of elements identified by index or key. Lists can be implemented using arrays, where each element is stored in contiguous memory locations. However, arrays have fixed sizes, making dynamic resizing more challenging.

  • For example, an array arr with size 5 can hold up to 5 integers.

    arr = [0, 0, 0, 0, 0]  # A fixed-size array with 5 elements
    arr[0] = 5
    arr[1] = 10
    arr[2] = 15  # Now the array is: [5, 10, 15, 0, 0]
    print(arr[1])  # Output: 10 (access the element at index 1)

Limitations:

  • Fixed Size: If the list grows beyond the predefined size, a new array must be created, and the elements must be copied over to the new array.

  • Insertion/Deletion Complexity: Inserting or deleting elements in the middle of the array requires shifting elements, which takes O(n) time.


Stack and Queues as Lists

Both stacks and queues can be implemented using arrays or linked lists.

  • Stack as a List: A stack can be implemented using a linked list or array, where elements are inserted and removed at the top of the list.

  • Queue as a List: A queue can be implemented using a linked list, where elements are inserted at the tail and removed from the head.


Static List Structures, Static vs Dynamic List Structures

  • Static Lists: A list structure with a fixed size, typically implemented using arrays.

  • Dynamic Lists: A list structure with a flexible size, typically implemented using linked lists, where nodes are allocated as needed.


7. Dynamic Implementation of Linked List

A linked list is a linear data structure where each element, called a node, contains two parts:

  1. Data: The actual information the node holds (could be any data type).

  2. Reference (or Link): A pointer to the next node in the sequence (in the case of a singly linked list).

In a dynamic implementation, the linked list is created and managed in memory dynamically, which means that the size of the list is not fixed and can grow or shrink as needed.

Example of Linked List Operations

Let's see an example of how a linked list works with basic operations.

Singly Linked List Example in C

#include <stdio.h>
#include <stdlib.h>

// Define the structure for a node
struct Node {
    int data;            // Data to store
    struct Node* next;   // Pointer to the next node
};

// Function to insert a new node at the beginning of the list
void insertAtBeginning(struct Node** head, int newData) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));  // Allocate memory for new node
    newNode->data = newData;  // Set the data
    newNode->next = *head;     // Point new node to the current head
    *head = newNode;           // Move head to point to the new node
}

// Function to print the linked list
void printList(struct Node* head) {
    struct Node* temp = head;   // Temporary pointer for traversal
    while (temp != NULL) {
        printf("%d -> ", temp->data);
        temp = temp->next;
    }
    printf("NULL\n");
}

// Main function to demonstrate linked list operations
int main() {
    struct Node* head = NULL;   // Start with an empty list
    
    // Insert some nodes
    insertAtBeginning(&head, 10);
    insertAtBeginning(&head, 20);
    insertAtBeginning(&head, 30);
    
    // Print the list
    printList(head);
    
    return 0;
}

Output:

30 -> 20 -> 10 -> NULL

Advantages of Linked Lists (Dynamic Implementation):

  1. Dynamic Size: Linked lists do not require a predefined size, which makes them more flexible when the number of elements is unknown or changes frequently.

  2. Efficient Insertions/Deletions: Inserting and deleting elements is fast, as it only involves changing pointers. No shifting of elements is needed, unlike arrays.

  3. No Wasted Space: Since nodes are created dynamically as needed, there’s no wasted memory for unused space, unlike arrays that may allocate extra space.


Disadvantages of Linked Lists:

  1. Memory Overhead: Each node requires extra memory to store the reference (pointer) to the next node. This adds overhead compared to arrays.

  2. Sequential Access: Linked lists must be traversed sequentially from the head to access a specific node, which makes access slower than array indexing (which is O(1)).

  3. Complexity in Implementation: Operations like insertion and deletion can be more complex to implement compared to arrays, especially when dealing with edge cases like inserting at the head or deleting the last node.


Types of Linked Lists

  • Singly Linked List: A linked list where each node points to the next node and the last node points to null.

  • Doubly Linked List: A linked list where each node has two pointers: one pointing to the next node and one pointing to the previous node.

  • Circular Linked List: A linked list where the last node points to the first node, forming a circular structure.


Basic Operations on Linked Lists

  • Creation: Initializing the head of the linked list to null or an empty value.

  • Insertion: Adding a new node at the beginning, end, or a specific position in the list.

  • Deletion: Removing a node from the list, either from the front, rear, or a specific position.

  • Traversal: Access each node in the list starting from the head and follow the links to each subsequent node until the end is reached (null pointer).

  • Search: Search for a specific element in the linked list. This is done by traversing the list node by node.


Doubly Linked Lists and Their Applications

Doubly linked lists allow traversal in both forward and backward directions due to their two pointers (next and previous). They are more versatile but require more memory than singly linked lists. Common applications include:

  • Implementing navigable lists, such as browser history.

  • Undo/redo functionality in software applications.


8. Tree

A tree is a hierarchical data structure consisting of nodes connected by edges. Each tree has a root node, and each node may have child nodes, which further have sub-nodes, forming a tree structure.

  • Height: The maximum level of any node in the tree.

  • Level: The level of a node is the number of edges from the root to the node.

  • Depth: The depth of a node is the number of edges from the root to the node.


Types of Trees:

  1. Binary Tree:

    • Each node has at most two children (left and right).

    • A binary tree can be used to represent hierarchical relationships efficiently.

    • Example:

          1
         / \
        2   3
       / \
      4   5
  2. Binary Search Tree (BST):

    • A type of binary tree where for each node:

      • The left subtree contains nodes with values less than the node.

      • The right subtree contains nodes with values greater than the node.

    • Operations:

      • Insertion: Insert new nodes in the correct position based on the node values.

      • Search: Efficiently search for a node by following left or right based on value comparison.

      • Deletion: Remove a node while maintaining the BST property.

  3. Balanced Trees:

    • Trees like AVL trees and Red-Black trees are self-balancing binary search trees that maintain their height balance to ensure operations (insertion, deletion, search) remain efficient (O(log n)).

    • AVL Tree

      An AVL tree is a self-balancing binary search tree (BST), where the difference in heights between the left and right subtrees of any node is at most 1. This balance factor ensures that the tree remains balanced, thus maintaining an efficient O(log n) time complexity for operations such as insertion, deletion, and search.

      Types of Rotations in AVL Trees:

      • Single Rotations:

        1. Right Rotation (RR): Used when the left subtree is taller than the right subtree, and the left child of the left subtree is unbalanced.

        2. Left Rotation (LL): Used when the right subtree is taller than the left subtree, and the right child of the right subtree is unbalanced.

      • Double Rotations:

        1. Left-Right Rotation (LR): A combination of a left rotation followed by a right rotation. Used when the left subtree is taller, and the right child of the left subtree is unbalanced.

        2. Right-Left Rotation (RL): A combination of a right rotation followed by a left rotation. Used when the right subtree is taller, and the left child of the right subtree is unbalanced.

      • Visual Representation of AVL Rotations:

        1. Right Rotation (RR): Used when the left child’s left subtree is taller.

          Before Rotation (Unbalanced):

              30
             /
            20
           /
          10

          After Right Rotation:

            20
           /  \
          10   30

        2. Left Rotation (LL): Used when the right child’s right subtree is taller.

          Before Rotation (Unbalanced):

           10
            \
            20
              \
              30

          After Left Rotation:

            20
           /  \
          10   30

        3. Left-Right Rotation (LR): A combination of a left rotation followed by a right rotation.

          Before Rotation (Unbalanced):

              30
             /
            10
              \
              20

          After Left Rotation on 10 (First Step):

              30
             /
            20
           /
          10

          After Right Rotation on 30 (Second Step):

            20
           /  \
          10   30

        4. Right-Left Rotation (RL): A combination of a right rotation followed by a left rotation.

          Before Rotation (Unbalanced):

          10
            \
            30
           /
          20

          After Right Rotation on 30 (First Step):

          10
            \
            20
              \
              30

          After Left Rotation on 10 (Second Step):

            20
           /  \
          10   30
  4. Heap:

    • A complete binary tree that satisfies the heap property:

      • Max-Heap: The value of each parent node is greater than or equal to its child nodes.

      • Min-Heap: The value of each parent node is less than or equal to its child nodes.

    • Heaps are used in priority queues.

  5. Trie (Prefix Tree):

    • A tree-like structure used for storing a set of strings, where each node represents a character in the string.

    • Efficient for string searches, auto-complete features, and dictionary implementations.

  6. General Tree:

    • A tree where each node can have any number of children. Unlike binary trees, there is no restriction on the number of child nodes a parent node can have.

  7. N-ary Tree:

    • A generalization of binary trees where each node can have up to N children.

Tree Traversal:

Traversal refers to the process of visiting all nodes in a tree in a specific order. The main types of tree traversal are:

  1. Pre-order Traversal (Root, Left, Right): Visit the root node first, then recursively traverse the left subtree, and then the right subtree.

    • Example (for the tree above): 1, 2, 4, 5, 3

  2. In-order Traversal (Left, Root, Right): Recursively traverse the left subtree, then visit the root node, and then recursively traverse the right subtree. This is used in binary search trees to retrieve values in sorted order.

    • Example: 4, 2, 5, 1, 3

  3. Post-order Traversal (Left, Right, Root): Recursively traverse the left subtree, then the right subtree, and finally visit the root node.

    • Example: 4, 5, 2, 3, 1

  4. Level-order Traversal (Breadth-First Search): Visit nodes level by level, starting from the root. This is usually implemented using a queue.

    • Example: 1, 2, 3, 4, 5

Applications of Trees:

  1. Hierarchical Data Representation:

    • Trees are often used to represent hierarchical data, such as file systems, organization charts, and parsing expressions.

  2. Searching and Sorting:

    • Binary Search Trees and AVL trees are used for fast searching, insertion, and deletion in ordered data.

  3. Expression Parsing:

    • Trees are used in compilers and interpreters to represent expressions and parse mathematical formulas (e.g., syntax trees).

  4. Routing Algorithms:

    • Trees are used in network routing algorithms like Spanning Trees in graph theory to find the shortest path or minimum cost path.

  5. Prefix Matching:

    • Tries are used for efficient prefix matching, often used in tasks like auto-completion and dictionary lookups.

  6. Heap-based Priority Queues:

    • Heaps are used to implement priority queues, which are used in algorithms like Dijkstra’s shortest path, and in scheduling tasks.

Advantages of Trees:

  1. Efficient Search and Retrieval: Especially in binary search trees, searching for an element can be done in O(log n) time, which is much faster than linear search in arrays (O(n)).

  2. Efficient Insertions and Deletions: Trees like binary search trees and heaps allow for efficient insertion and deletion of elements.

  3. Hierarchical Organization: Trees naturally represent hierarchical data, which is difficult to represent in arrays or linked lists.

  4. Dynamic Structure: Trees grow dynamically without a fixed size, unlike arrays, where the size is fixed at the time of creation.

Disadvantages of Trees:

  1. Memory Overhead: Each node in a tree requires additional memory for storing pointers or references (in addition to the data).

  2. Complex Operations: Operations like balancing or maintaining tree properties (in self-balancing trees) can be complex.

  3. Traversal Complexity: Traversing large trees (especially unbalanced trees) can be time-consuming, leading to poor performance in some cases.


Example of Binary Tree Implementation (in C):

#include <stdio.h>
#include <stdlib.h>

// Define the structure of a node
struct Node {
    int data;
    struct Node* left;
    struct Node* right;
};

// Function to create a new node
struct Node* newNode(int data) {
    struct Node* node = (struct Node*)malloc(sizeof(struct Node));
    node->data = data;
    node->left = node->right = NULL;
    return node;
}

// In-order traversal of the binary tree
void inorder(struct Node* root) {
    if (root != NULL) {
        inorder(root->left);       // Traverse left subtree
        printf("%d ", root->data); // Visit root
        inorder(root->right);      // Traverse right subtree
    }
}

int main() {
    // Creating a simple binary tree
    struct Node* root = newNode(1);
    root->left = newNode(2);
    root->right = newNode(3);
    root->left->left = newNode(4);
    root->left->right = newNode(5);

    printf("In-order traversal: ");
    inorder(root);

    return 0;
}

Output:

In-order traversal: 4 2 5 1 3

Conclusion

This outline provides an overview of key data structures like lists, linked lists, and trees, along with various operations and applications essential for problem-solving in computer science.

Last updated