// Linked Lists - Singly & Doubly Linked - Greg Hogg DSA Course Materials Lecture 3
#include <iostream>
#include <string>
class SinglyNode {
public:
int val;
SinglyNode* next;
SinglyNode(int val, SinglyNode* next = nullptr) : val(val), next(next) {}
std::string toString() const {
return std::to_string(val);
}
};
class DoublyNode {
public:
int val;
DoublyNode* next;
DoublyNode* prev;
DoublyNode(int val) : val(val), next(nullptr), prev(nullptr) {}
std::string toString() const {
return std::to_string(val);
}
};
void display(SinglyNode* head) {
SinglyNode* curr = head;
std::cout << "Singly linked list: ";
while (curr) {
std::cout << curr->val << " -> ";
curr = curr->next;
}
std::cout << "null" << std::endl;
}
bool search(SinglyNode* head, int val) {
SinglyNode* curr = head;
while (curr) {
if (curr->val == val) return true;
curr = curr->next;
}
return false;
}
void display(DoublyNode* head) {
DoublyNode* curr = head;
std::cout << "Doubly linked list: ";
while (curr) {
std::cout << curr->val << " <-> ";
curr = curr->next;
}
std::cout << "null" << std::endl;
}
DoublyNode* insertAtBeginning(DoublyNode* head, DoublyNode*& tail, int val) {
DoublyNode* newNode = new DoublyNode(val);
newNode->next = head;
if (head) head->prev = newNode;
else tail = newNode;
return newNode;
}
DoublyNode* insertAtEnd(DoublyNode* head, DoublyNode*& tail, int val) {
DoublyNode* newNode = new DoublyNode(val);
if (tail) {
tail->next = newNode;
newNode->prev = tail;
} else {
head = newNode;
}
tail = newNode;
return head;
}
int main() {
// Singly Linked List
SinglyNode* head = new SinglyNode(1);
SinglyNode* A = new SinglyNode(3);
SinglyNode* B = new SinglyNode(4);
SinglyNode* C = new SinglyNode(7);
head->next = A;
A->next = B;
B->next = C;
std::cout << "Head of the list: " << head->toString() << std::endl;
std::cout << "Traversing the singly linked list: ";
display(head);
int searchValue = 7;
bool found = search(head, searchValue);
std::cout << "Search for value " << searchValue << " in list: " << (found ? "true" : "false") << std::endl;
// Doubly Linked List
DoublyNode* headD = new DoublyNode(1);
DoublyNode* tailD = headD;
std::cout << "Head of the doubly linked list: " << headD->toString() << std::endl;
display(headD);
headD = insertAtBeginning(headD, tailD, 3);
display(headD);
headD = insertAtEnd(headD, tailD, 7);
display(headD);
return 0;
}
// Linked Lists - Singly & Doubly Linked - Greg Hogg DSA Course Materials Lecture 3
// Singly Linked Lists
class SinglyNode {
int val;
SinglyNode next;
SinglyNode(int val, SinglyNode next) {
this.val = val;
this.next = next;
}
SinglyNode(int val) {
this(val, null);
}
public String toString() {
return Integer.toString(val);
}
}
public class Main {
public static void main(String[] args) {
SinglyNode head = new SinglyNode(1);
SinglyNode A = new SinglyNode(3);
SinglyNode B = new SinglyNode(4);
SinglyNode C = new SinglyNode(7);
head.next = A;
A.next = B;
B.next = C;
System.out.println("Head of the list: " + head); // Output: 1
// Traverse the list - O(n)
SinglyNode curr = head;
System.out.println("Traversing the singly linked list:");
while (curr != null) {
System.out.print(curr.val + " ");
curr = curr.next;
}
System.out.println();
// Display linked list - O(n)
display(head);
// Search for node value - O(n)
int searchValue = 7;
boolean found = search(head, searchValue);
System.out.println("Search for value " + searchValue + " in list: " + found);
// Doubly Linked Lists
DoublyNode headD = new DoublyNode(1);
DoublyNode tailD = headD;
System.out.println("Head of the doubly linked list: " + tailD); // Output: 1
// Display - O(n)
display(headD);
// Insert at beginning - O(1)
headD = insertAtBeginning(headD, tailD, 3);
display(headD);
// Insert at end - O(1)
tailD = insertAtEnd(headD, tailD, 7);
display(headD);
}
// Display singly linked list - O(n)
static void display(SinglyNode head) {
SinglyNode curr = head;
StringBuilder elements = new StringBuilder();
while (curr != null) {
elements.append(curr.val).append(" -> ");
curr = curr.next;
}
elements.append("null");
System.out.println("Singly linked list: " + elements);
}
// Search for node value in singly linked list - O(n)
static boolean search(SinglyNode head, int val) {
SinglyNode curr = head;
while (curr != null) {
if (curr.val == val) {
return true;
}
curr = curr.next;
}
return false;
}
// Doubly Linked Lists
static class DoublyNode {
int val;
DoublyNode next, prev;
DoublyNode(int val) {
this.val = val;
}
public String toString() {
return Integer.toString(val);
}
}
// Display doubly linked list - O(n)
static void display(DoublyNode head) {
DoublyNode curr = head;
StringBuilder elements = new StringBuilder();
while (curr != null) {
elements.append(curr.val).append(" <-> ");
curr = curr.next;
}
elements.append("null");
System.out.println("Doubly linked list: " + elements);
}
// Insert at beginning of doubly linked list - O(1)
static DoublyNode insertAtBeginning(DoublyNode head, DoublyNode tail, int val) {
DoublyNode newNode = new DoublyNode(val);
newNode.next = head;
if (head != null) {
head.prev = newNode;
}
return newNode;
}
// Insert at end of doubly linked list - O(1)
static DoublyNode insertAtEnd(DoublyNode head, DoublyNode tail, int val) {
DoublyNode newNode = new DoublyNode(val);
newNode.prev = tail;
if (tail != null) {
tail.next = newNode;
}
return newNode;
}
}
Linked lists are dynamic data structures made of nodes that store values and pointers. Singly linked lists link nodes in one direction, while doubly linked lists have forward and backward links. They are ideal for applications where elements are frequently added or removed from the front or back without the overhead of resizing or shifting elements like in arrays.
A linked list is a linear data structure where each element (called a node) contains a value and a reference to the next node in the sequence. This differs from arrays, which store elements in contiguous memory. Linked lists allow for efficient insertions and deletions, especially at the beginning or end.
In a singly linked list, each node points only to the next node, enabling forward traversal. A doubly linked list extends this by allowing each node to also reference its previous node, enabling bidirectional traversal.
O(1)
O(n)
O(n)
Linked lists are useful in implementing stacks, queues, and adjacency lists in graphs. They're also common in scenarios where memory reallocation is costly or predictable indexing isn’t required.
Mastering linked lists teaches you how memory is handled under the hood and prepares you for more advanced pointer-based structures like trees and graphs. They're also a popular topic in coding interviews due to the logic-heavy operations they require.
Linked lists are a fundamental data structure in computer science, providing a dynamic alternative to arrays for storing data. Unlike arrays, linked lists consist of individual nodes connected via pointers, allowing for flexible memory usage and efficient insertions and deletions.
A singly linked list is composed of nodes, where each node contains:
The list starts with a special node called the head, and the end of the list is marked by a node whose next
pointer is null
or None
.
Nodes are stored at distinct memory locations (i.e., not contiguous like arrays).
next
pointer.A doubly linked list enhances the singly linked list by adding an additional pointer to each node. Each node contains:
The head node's prev
pointer is null
, and the tail node’s next
pointer is null
.
Doubly linked lists support bidirectional traversal, making them more flexible in certain applications.
prev
and next
are accessible.Both singly and doubly linked lists can be implemented using Python classes. Each Node class typically includes:
value
, next
value
, next
, prev
node.next = next_node
curr = head
Feature | Singly Linked List | Doubly Linked List |
---|---|---|
Direction | Forward only | Forward and backward |
Memory per Node | Lower (1 pointer) | Higher (2 pointers) |
Deletion with Reference | O(n) | O(1) |
Insert at Head/Tail | Head: O(1), Tail: O(n) | Head & Tail: O(1) |
Traversal Flexibility | One direction only | Two-way traversal |
Singly linked lists and doubly linked lists are core components in the study of data structures. They offer flexible alternatives to arrays, particularly for applications requiring frequent insertions or deletions. Understanding their structure, operations, and time complexity is essential for mastering algorithms in Python and other programming languages.