### Linked Lists: Introduction, Properties, Applications, and Coding Problems

A comprehensive guide to understanding linked lists, including their properties, variations, and real-world applications in implementing data structures, large-number arithmetic, file allocation, and memory management. Includes coding problems and FAQs.

### Introduction

In software development, selecting the right tool to manage data is important. Data structures are essential to all programming languages and include objects such as hashes, objects, variables, and arrays. Linked lists may seem complex at first, but they become more interesting as you learn about them. Once you understand the logic behind when and how to use them, they are easy to comprehend.

### Linked Lists: A Brief Overview

A linked list is a linear data structure that consists of continuous nodes, each containing “data” and “next” pointers indicating the subsequent node in a circular and singly linked list. In a doubly linked list, there is also a “previous” pointer indicating the preceding node. Linked lists can perform operations like insertion, deletion, and append without reorganization of the whole list, making them more efficient than arrays when handling dynamic sizing. Moreover, linked lists are heavily utilized in real-life applications and offer numerous benefits to memory management.

Linked lists are comprised of links with data elements, each containing a pointer to the next link, as well as a linked list that starts with a pointer to the first link. Pointers store the memory address of an element and can indicate nothing, whereas references are similar but cannot indicate anything. Regardless of size, linked lists are a sequence of nodes, and each node includes two fields: data and pointers to other nodes.

The start pointer holds the address of the first node in the list, the null pointer indicates the end of the list, and every other node consists of data and pointers.

### Properties of Linked List

Code:

“`

// Node structure of linked list

struct Node {

int data; // stores data

struct Node* next; // stores the address of the next node

};

// Head of Linked List

struct Node* head;

/* Properties:

– It consists of a sequence of nodes where each node stores the address of the next node.

– Unlike arrays, linked list elements are not stored at adjacent memory locations.

– Linked Lists are dynamic and can change size during program execution.

– The reference of the head node of the linked list will be given in each linked list question.

– The final node of the linked list implies NULL(None) which indicates that it is the last node.

*/

“`

The linked list is a dynamic data structure that stores elements in nodes. The node structure consists of two parts, where one part stores the data and the other part stores the address of the next node. Unlike arrays, linked lists manage the problem of fixed capacity, and change size during runtime allowing more flexibility. The head of the linked list is the first node, and through the head, we can perform distinct operations on the linked list. In each linked list question, the reference of the head node of the linked list will be given. The final node of the linked list points to NULL, which suggests it’s the end of the list.

Time and Space Complexity of Linked Lists in Data Structures

### Time and Space Complexity

Code:

// Linked Lists are efficient for inserting and deleting nodes

// The time complexity for inserting and deleting at any point on the list is O(1), unlike dynamic arrays

// However, accessing data in nodes requires linear time due to the use of pointers to traverse the list

// Optimizing search in linked lists is not possible, unlike sorted arrays that can be used with binary search

// Linked list nodes consist of a value and a pointer, causing the amount of data stored to increase linearly with the number of nodes

// Therefore, the space complexity of a linked list is linear.

// Overall, linked lists are ideal for frequently changing data sets that require constant addition and removal of elements.

Plain text:

Linked lists are efficient for inserting and deleting nodes in constant time. However, accessing data requires linear time and optimizing search is not possible. Linked list nodes contain a value and a pointer, causing the amount of stored data to increase linearly. Therefore, the space complexity of a linked list is linear. Linked lists are ideal for frequently changing data sets that require constant addition and removal of elements.

### Types of Linked Lists

// Implementation of three types of Linked Lists: Singly, Doubly and Circular

- In a singly linked list, the nodes are traversed in only one direction from the head to the tail.

The head points to the first node and the tail points to NULL indicating the end of the list. - In doubly linked lists, each node has a next and a previous pointer to traverse

the list in both forward and backward directions, mostly used in caching web pages. - Circular linked list is a singly linked list with its tail pointing to the head of the list,

creating a loop throughout the list.

// Implementation of traversing, insertion and deletion in Linked Lists

Some applications of linked lists are timesharing, maintaining the history of web page visits

and implementing LRU cache.

### Applications of Linked Lists Data Structure

Code:

// Define a linked list node

class Node {

int data;

Node next;

}

class LinkedList {

Node head;

// Add a node at the beginning of the linked list

public void insertAtBeginning(int newData) {

Node newNode = new Node();

newNode.data = newData;

newNode.next = head;

head = newNode;

}

// Add a node after a given node

public void insertAfterNode(Node prevNode, int newData) {

if (prevNode == null) {

System.out.println(“Given previous node cannot be null”);

return;

}

Node newNode = new Node();

newNode.data = newData;

newNode.next = prevNode.next;

prevNode.next = newNode;

}

// Delete a node from the linked list

public void deleteNode(int key) {

Node temp = head, prev = null;

if (temp != null && temp.data == key) {

head = temp.next;

return;

}

while (temp != null && temp.data != key) {

prev = temp;

temp = temp.next;

}

if (temp == null) {

return;

}

prev.next = temp.next;

}

// Print the linked list

public void printList() {

Node currentNode = head;

while (currentNode != null) {

System.out.print(currentNode.data + ” “);

currentNode = currentNode.next;

}

}

}

Plain Text:

A linked list is a dynamic data structure that consists of a sequence of nodes, each containing some data and a reference (link) to the next node in the sequence. Linked lists have several applications, including but not limited to implementing abstract data types such as lists, stacks, and queues.

Some common operations performed on linked lists include inserting a node at the beginning of the list, inserting a node after a given node, deleting a node from the list, and printing the list in order. These operations can be implemented using different algorithms, but the basic idea is to follow the links between nodes to access or modify the data stored in them.

Linked lists have several advantages over other data structures such as arrays, including efficient insertion and deletion of nodes, no need for a fixed amount of memory, and flexibility in storing data of different types and sizes. However, linked lists also have some drawbacks, including slower access times and complexity in traversing the list from the middle or end.

### Linked List Implementation of Stacks

Code:

“`

class Node:

def __init__(self, data):

self.data = data

self.next = None

class Stack:

def __init__(self):

self.top = None

def is_empty(self):

return self.top is None

def push(self, data):

new_node = Node(data)

new_node.next = self.top

self.top = new_node

def pop(self):

if self.is_empty():

return None

popped = self.top

self.top = self.top.next

return popped.data

“`

Explanation:

– To implement a stack, a linked list is used.

– The stack implemented using a linked list can accommodate an indefinite number of values. Therefore, the size of data does not need to be specified at the beginning of the implementation.

– Each new element in the implementation is added as a ‘top’ element, which means that every new element is pointed to by ‘top’.

– To remove an element from the stack, simply remove the node pointed to by ‘top’ by moving ‘top’ to its prior node in the list.

– The first element’s next field should always be NULL.

– The code above demonstrates the implementation of a stack using a linked list data structure in Python, including the use of Node and Stack classes.

### Implementing Queues with Linked Lists

Queues can be implemented using linked lists, which allows for an infinite number of values and a variable size of data without needing to specify the size at the start. The last node inserted is pointed to by ‘rear’ and the first node inserted is indicated by ‘front’. In this example, ‘rear’ points to the node with the value of 50 and ‘front’ points to the node with the value of 10, with elements 10, 15, 22, and 50 inserted in that order.

Code:

“`python

class Queue:

def __init__(self):

self.items = []

def is_empty(self):

return self.items == []

def enqueue(self, item):

self.items.append(item)

def dequeue(self):

return self.items.pop(0)

def size(self):

return len(self.items)

“`

This is an implementation of a Queue using a Python list. The “__init__“ method initializes the Queue as an empty list. The “is_empty“ method checks if the Queue is empty. The “enqueue“ method adds an item to the end of the Queue, and the “dequeue“ method removes and returns the item at the beginning of the Queue. The “size“ method returns the number of items in the Queue.

### Graph Implementation Using Adjacency Lists in Java

Code:

“`

public class Graph {

private LinkedList

private int vertices;

// Constructor

public Graph(int vertices) {

this.vertices = vertices;

adjLists = new LinkedList[vertices];

for (int i = 0; i < vertices; i++) {
adjLists[i] = new LinkedList<>();

}

}

// Adds an edge to the graph

public void addEdge(int src, int dest) {

adjLists[src].add(dest);

}

}

“`

Explanation:

This code provides an implementation of a graph using adjacency lists, which is more space-efficient than an adjacency matrix for large graphs. The `Graph` class has an array of linked lists to store the adjacent vertices for each vertex. The `addEdge` method adds an edge between two vertices by adding the destination vertex to the linked list for the source vertex. The `vertices` variable stores the number of vertices in the graph.

Using this implementation, we can create a new graph and add edges to it like so:

“`

Graph graph = new Graph(5);

graph.addEdge(0, 1);

graph.addEdge(1, 2);

graph.addEdge(2, 3);

graph.addEdge(3, 4);

“`

This creates a graph with 5 vertices and the following edges: 0-1, 1-2, 2-3, 3-4.

### Hash Tables in Java

Hash Tables in Java map a collection of keys to values based on specific relations, but collisions can occur when multiple keys map to the same position, which can be resolved through Hash Table Chaining. This approach uses a linked list to store keys that map to the same slot. Java allows for both Singly and Doubly Linked Lists, but the latter permits two-way traversal, making it more efficient for deletion and insertion at a specific position. To insert a key, hash it to gain its position in the table and insert it at the head of the corresponding linked list. To delete, search the list using the hash function and remove the key using linked list procedures.

### Polynomial Representation using Linked List

To represent a polynomial, a linked list can be used, where each node holds two fields – coefficient and power. Each node represents a term of the polynomial. The linked list can be sorted in O(n log n) time, where n is the total number of nodes.

Adding two polynomials involves adding coefficients of like terms and generating a new linked list for the resulting polynomial. Two linked lists can be used to represent polynomials, which can be combined using a two-pointer technique, as both linked lists are ordered by power.

### Large Number Arithmetic with Doubly Linked List

This program employs a dynamic physical structure, consisting of a doubly linked list, to perform arithmetic operations on large integers, such as 70-digit prime numbers, which can be negative, positive, or zero.

The program includes a `LargeInt` class, which provides the following:

* A default constructor

* Overloaded operator functions for +, -, *, /, and %

* Overloaded operator functions for == and =

* An overloaded operator function for >

Code:

“`

class LargeInt {

private:

struct Node {

int data;

Node* prev;

Node* next;

Node(int d = 0, Node* p = nullptr, Node* n = nullptr)

: data(d), prev(p), next(n) {}

};

Node* head;

Node* tail;

int size;

};

“`

### Linked File Allocation

Storing a large file in one disk location can be tough. A linked list approach is effective in allocating files. Each file block has a pointer to its corresponding text block, allowing blocks to be spread throughout the disk space without fragmentation. This approach is more efficient and minimizes space wastage. Directory load is also reduced, as it only needs to include the starting and ending pointers of the file.

### Memory Management using Linked List

One way to manage memory is to keep track of free and allocated memory using a linked list, where a segment can be a process or a hole. When sorted by address, the list can be used to allocate memory to a new process or a process being swapped in from the disk. Some allocation algorithms include:

– First Fit: Searches the list for the first hole big enough and splits it into process and unused memory.

– Next Fit: Similar to First Fit, but starts searching from the last suitable hole found.

– Best Fit: Looks for the smallest hole that is adequate, instead of splitting a large hole.

– Worst Fit: Always takes the largest available hole, so that it can be split into bigger segments later.

– Quick Fit: Keeps separate lists for commonly requested sizes.

Code: ` should be here, but since there's no code to optimize, I'll put plain text in a <p> tag:`

Memory management with linked lists is a common technique used by operating systems to allocate and deallocate memory. It involves keeping track of free and allocated memory segments in a linked list, and using various algorithms to allocate memory to new or existing processes. The choice of algorithm can impact the overall performance of the system.

```
```### Real-life Applications of Linked Lists

Linked lists have many real-life applications:

```
```Image viewers
Web browsers
Music players
Mailing lists and email applications

For example, linked lists are used in image viewers to create a next and previous button that links to the next or previous image. Similarly, web browsers use linked lists to allow users to navigate to the previous or next websites visited.

Movies, songs, and other playlists in music players can also be thought of as linked lists.

Linked lists also have benefits in email applications. For instance, a mailer may create a linked list of email addresses before sending a message. This is easier than anticipating numerous lists.

### Understanding Linked Lists in Data Structures

A Linked List is a data structure that stores similar data items. It is simple to implement and helps with insertion and deletion operations. With various Linked List types such as singly and doubly linked lists, memory is used efficiently. As a programmer, understanding Linked Lists is crucial for interviews and advancing your career.

### Common Use Cases for Linked Lists

Linked lists are commonly used for their efficient insertion and deletion operations. They can be used to implement various data structures like stacks, queues, and other abstract data types.

// Example code of a linked list implementation:

```
```class Node {

int data;

Node next;

public Node(int data) {

this.data = data;

this.next = null;

}

}

class LinkedList {

Node head;

public void insert(int data) {

Node newNode = new Node(data);

if (head == null) {

head = newNode;

} else {

Node currentNode = head;

while (currentNode.next != null) {

currentNode = currentNode.next;

}

currentNode.next = newNode;

}

}

` public void delete(int data) {`

if (head == null) {

return;

}

if (head.data == data) {

head = head.next;

return;

}

Node prevNode = head;

Node currentNode = head.next;

while (currentNode != null && currentNode.data != data) {

prevNode = currentNode;

currentNode = currentNode.next;

}

if (currentNode == null) {

return;

}

prevNode.next = currentNode.next;

}

}

### Usage of Linked Lists in JavaScript

Linked lists are definitely useful in JavaScript.

The question of whether linked lists are useful in JavaScript can be answered with a resounding "yes"!

### Difference between Array and Linked List

An array is a collection of elements of the same data type that are stored in adjacent memory locations. On the other hand, a linked list is a collection of objects, called nodes, that consist of two parts: data and a reference to the next node.

Array elements can be accessed directly using their index, whereas linked list elements can only be accessed sequentially by traversing through the list.

One major difference between the two is that array size is fixed, meaning you cannot add or remove elements unless you create a new array. In contrast, a linked list can easily be modified by adding or removing nodes.

Another difference is that array elements are stored in contiguous memory locations, while linked list elements can be scattered throughout the memory.

### Linked List Coding Problems

Here are some common problems related to Linked Lists:

//Detects a loop in the given linked list

bool detectLoop(Node* head) {

Node* slow = head;

Node* fast = head;

```
``` while (fast != NULL && fast->next != NULL) {

slow = slow->next;

fast = fast->next->next;

if (slow == fast)

return true;

}

return false;

}

//Reverses the given linked list

Node* reverseList(Node* head) {

Node* prev = NULL;

Node* curr = head;

while (curr != NULL) {

Node* next = curr->next;

curr->next = prev;

prev = curr;

curr = next;

}

return prev;

}

//Deletes a node with given key from linked list

void deleteNode(Node*& head, int key) {

if (head == NULL)

return;

if (head->data == key) {

Node* temp = head;

head = head->next;

delete temp;

return;

}

Node* curr = head->next;

Node* prev = head;

while (curr != NULL && curr->data != key) {

prev = curr;

curr = curr->next;

}

if (curr == NULL)

return;

prev->next = curr->next;

delete curr;

}

//Finds the intersection point of two linked lists

Node* getIntersectionNode(Node* headA, Node* headB) {

Node* l1 = headA;

Node* l2 = headB;

while (l1 != l2) {

l1 = (l1 == NULL) ? headB : l1->next;

l2 = (l2 == NULL) ? headA : l2->next;

}

return l1;

}

//Adds two numbers represented by linked list

Node* addTwoLists(Node* first, Node* second) {

int carry = 0;

Node* res = NULL;

Node** temp = &res;

while (first != NULL || second != NULL || carry) {

int sum = (first ? first->data : 0) + (second ? second->data : 0) + carry;

carry = sum / 10;

*temp = new Node(sum % 10);

temp = &((*temp)->next);

if (first != NULL) first = first->next;

if (second != NULL) second = second->next;

}

` return res;`

}

### Additional Resources

Here are some helpful resources for learning about linked lists, data structures, and algorithms: