A linked list is a fundamental data structure in computer science and programming. It consists of a sequence of elements, called nodes, where each node contains data and a reference (or link) to the next node in the sequence. Linked lists are particularly useful when you need dynamic data storage that can easily grow or shrink in size.
#include <iostream>
#include <memory>
class Node {
public:
int data;
std::unique_ptr<Node> next;
Node(int val) : data(val), next(nullptr) {}
};
class LinkedList {
private:
std::unique_ptr<Node> head;
public:
void insert(int val) {
auto newNode = std::make_unique<Node>(val);
if (!head) {
head = std::move(newNode);
} else {
newNode->next = std::move(head);
head = std::move(newNode);
}
}
void display() {
Node* current = head.get();
while (current) {
std::cout << current->data << " -> ";
current = current->next.get();
}
std::cout << "nullptr" << std::endl;
}
};
int main() {
LinkedList list;
list.insert(3);
list.insert(2);
list.insert(1);
list.display();
return 0;
}
std::unique_ptr
is used for automatic memory management.Node
class represents each element in the list, containing data and a pointer to the next node.LinkedList
class provides methods to insert elements and display the list.insert
method adds new elements to the front of the list (constant time operation).display
method traverses the list and prints each element.#include <iostream>
#include <memory>
class Node {
public:
int data;
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev;
Node(int val) : data(val), next(nullptr) {}
};
class DoublyLinkedList {
private:
std::shared_ptr<Node> head;
std::shared_ptr<Node> tail;
public:
void insertBack(int val) {
auto newNode = std::make_shared<Node>(val);
if (!head) {
head = tail = newNode;
} else {
newNode->prev = tail;
tail->next = newNode;
tail = newNode;
}
}
void displayForward() {
auto current = head;
while (current) {
std::cout << current->data << " <-> ";
current = current->next;
}
std::cout << "nullptr" << std::endl;
}
void displayBackward() {
auto current = tail;
while (current) {
std::cout << current->data << " <-> ";
current = current->prev.lock();
}
std::cout << "nullptr" << std::endl;
}
};
int main() {
DoublyLinkedList list;
list.insertBack(1);
list.insertBack(2);
list.insertBack(3);
std::cout << "Forward: ";
list.displayForward();
std::cout << "Backward: ";
list.displayBackward();
return 0;
}
std::shared_ptr
is used for the next
pointer to allow shared ownership.std::weak_ptr
is used for the prev
pointer to avoid circular references.DoublyLinkedList
class maintains both head
and tail
pointers for efficient operations at both ends.insertBack
method adds elements to the end of the list in constant time.displayForward
and displayBackward
methods demonstrate traversal in both directions.#include <iostream>
#include <memory>
class Node {
public:
int data;
std::shared_ptr<Node> next;
Node(int val) : data(val), next(nullptr) {}
};
class CircularLinkedList {
private:
std::shared_ptr<Node> head;
public:
void insert(int val) {
auto newNode = std::make_shared<Node>(val);
if (!head) {
head = newNode;
head->next = head; // Point to itself to make it circular
} else {
newNode->next = head->next;
head->next = newNode;
std::swap(head->data, newNode->data);
}
}
void display() {
if (!head) {
std::cout << "Empty list" << std::endl;
return;
}
auto current = head;
do {
std::cout << current->data << " -> ";
current = current->next;
} while (current != head);
std::cout << "... (circular)" << std::endl;
}
};
int main() {
CircularLinkedList list;
list.insert(1);
list.insert(2);
list.insert(3);
list.display();
return 0;
}
insert
method adds new elements at the beginning of the list, updating the head.display
method uses a do-while loop to traverse the list, stopping when it reaches the head again.Performance: Linked lists offer O(1) insertion and deletion at the beginning (and end, with a tail pointer), but O(n) for random access.
Memory Usage: Each node in a linked list requires extra memory for storing the link(s), which can be a consideration for large lists.
STL Alternative: C++ Standard Template Library (STL) provides std::list
and std::forward_list
for doubly and singly linked lists, respectively. These are often preferable for general use due to their robust implementation and integration with other STL components.
Custom Allocators: For advanced use cases, you might consider implementing custom allocators to optimize memory management for your specific needs.
Thread Safety: The examples provided are not thread-safe. In a multi-threaded environment, proper synchronization mechanisms would need to be implemented.
Linked lists are versatile data structures that offer efficient insertion and deletion operations. We've explored three main types of linked lists: singly linked, doubly linked, and circular linked lists. Each type has its own advantages and use cases:
The examples provided demonstrate modern C++ implementations using smart pointers for automatic memory management. While linked lists have some limitations, such as lack of random access and potential cache inefficiency, they remain an important data structure in many scenarios, particularly when frequent insertion and deletion operations are required.
Certainly! I'll provide examples using the Standard Template Library (STL) implementations of linked lists in C++. The STL provides two types of linked lists: std::list
(doubly linked list) and std::forward_list
(singly linked list). I'll demonstrate various operations and use cases for both.
The C++ Standard Template Library (STL) provides robust implementations of linked lists through std::list
and std::forward_list
. These container classes offer efficient insertion and deletion operations along with a wide range of member functions for list manipulation.
std::list
: Doubly linked list, allowing bidirectional traversalstd::forward_list
: Singly linked list, more memory-efficient but only allows forward traversalstd::list
#include <iostream>
#include <list>
#include <algorithm>
int main() {
std::list<int> myList = {3, 1, 4, 1, 5, 9};
// Insert at the beginning and end
myList.push_front(0);
myList.push_back(2);
// Display the list
std::cout << "List contents: ";
for (const auto& elem : myList) {
std::cout << elem << " ";
}
std::cout << std::endl;
// Sort the list
myList.sort();
// Remove duplicates
myList.unique();
// Display the sorted list without duplicates
std::cout << "Sorted list without duplicates: ";
for (const auto& elem : myList) {
std::cout << elem << " ";
}
std::cout << std::endl;
return 0;
}
std::list
.sort()
member function is used to sort the list.unique()
removes consecutive duplicate elements.#include <iostream>
#include <forward_list>
#include <algorithm>
int main() {
std::forward_list<int> myForwardList = {3, 1, 4, 1, 5, 9};
// Insert at the beginning
myForwardList.push_front(0);
// Insert after a specific position
auto it = myForwardList.begin();
std::advance(it, 2);
myForwardList.insert_after(it, 2);
// Display the list
std::cout << "Forward list contents: ";
for (const auto& elem : myForwardList) {
std::cout << elem << " ";
}
std::cout << std::endl;
// Remove all elements with a specific value
myForwardList.remove(1);
// Reverse the list
myForwardList.reverse();
// Display the modified list
std::cout << "Modified forward list: ";
for (const auto& elem : myForwardList) {
std::cout << elem << " ";
}
std::cout << std::endl;
return 0;
}
std::forward_list
, which is a singly linked list.push_front()
is used to add an element at the beginning.insert_after()
demonstrates insertion after a specific position.remove()
function removes all occurrences of a specified value.reverse()
reverses the order of elements in the list.std::forward_list
doesn't have push_back()
or size()
member functions to maintain its efficiency.#include <iostream>
#include <list>
#include <string>
#include <algorithm>
class Person {
public:
std::string name;
int age;
Person(const std::string& n, int a) : name(n), age(a) {}
// For sorting based on age
bool operator<(const Person& other) const {
return age < other.age;
}
};
// For displaying Person objects
std::ostream& operator<<(std::ostream& os, const Person& p) {
return os << p.name << " (" << p.age << ")";
}
int main() {
std::list<Person> people = {
{"Alice", 30},
{"Bob", 25},
{"Charlie", 35},
{"David", 28}
};
// Display original list
std::cout << "Original list:" << std::endl;
for (const auto& person : people) {
std::cout << person << std::endl;
}
// Sort the list based on age
people.sort();
// Display sorted list
std::cout << "\nSorted list by age:" << std::endl;
for (const auto& person : people) {
std::cout << person << std::endl;
}
// Find a person by name
auto it = std::find_if(people.begin(), people.end(),
[](const Person& p) { return p.name == "Charlie"; });
if (it != people.end()) {
std::cout << "\nFound: " << *it << std::endl;
}
return 0;
}
std::list
with custom objects (Person
class).<
) for sorting based on age.sort()
member function, which uses the defined comparison operator.std::find_if
algorithm is used to search for a person by name, demonstrating the compatibility of std::list
with STL algorithms.Performance: While std::list
and std::forward_list
provide O(1) insertion and deletion, they may have worse cache performance compared to contiguous containers like std::vector
for traversal operations.
Memory Allocation: These containers allocate memory for each node separately, which can lead to memory fragmentation in some scenarios.
Iterator Invalidation: Iterators to std::list
and std::forward_list
remain valid after insertion or removal operations, except for the erased elements.
Use Cases: These containers are particularly useful when frequent insertion and deletion operations are required at arbitrary positions in the sequence.
Algorithms: Many STL algorithms work efficiently with these containers, but some (like std::sort
) are not applicable to std::forward_list
due to its unidirectional nature.
The STL provides powerful and flexible implementations of linked lists through std::list
and std::forward_list
. These containers offer efficient insertion and deletion operations, along with a rich set of member functions and compatibility with STL algorithms.
std::list
is a doubly linked list that allows bidirectional traversal and provides operations like push_back()
, which are not available in std::forward_list
. It's more versatile but uses more memory per node.
std::forward_list
is a singly linked list, offering a more memory-efficient solution when only forward traversal is needed. It's particularly useful in scenarios where memory usage is a critical factor.
Both containers are excellent choices when the primary operations involve frequent insertions and deletions at arbitrary positions in the sequence. They provide a balance of functionality and performance, making them suitable for a wide range of applications in C++ programming.