0% found this document useful (0 votes)
28 views

DSA-Chapter-3.1-2024 (1)

Uploaded by

roinieva22
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
28 views

DSA-Chapter-3.1-2024 (1)

Uploaded by

roinieva22
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 9

Data Structures and Algorithms

Chapter 3.1: ADTs, Standard Library

Abstract Data Type (ADT)


An Abstract Data Type is a high-level type definition that describes a set of data and the
operations that can be performed on that data, without specifying how the data is stored or how
the operations are implemented.

The internal details of an ADT are hidden from the user, and only the public interface
(operations) is exposed.

Common ADTs and Their Operations

Each ADT has its own key operations regardless of the data structure used to implement it.

1. Stack (LIFO: Last In, First Out)

Key Operations:

• push(item): Add an item to the stack.


• pop(): Remove the top item from the stack.
• peek(): View the top item without removing it.
• isEmpty(): Check if the stack is empty.

2. Queue (FIFO: First In, First Out)

Key Operations:

• enqueue(item): Add an item to the queue.


• dequeue(): Remove the front item from the queue.
• peek(): View the front item without removing it.
• isEmpty(): Check if the queue is empty.

3. List (Ordered Collection)

Key Operations:

• access an item by index


• add item to beginning or end
• access the first or last item
• test if the list is empty
4. String (Sequence of characters)

Key Operations:

• size/length(): Returns the number of characters in the string.


• charAt(index): Returns the character at the specified index.
• substring(start, end): Returns a new string that is a substring of this string, starting
at start and ending just before end.
• concat(str): Concatenates the specified string to the end of this string.
• toLowerCase() and toUpperCase(): Converts all characters in the string to
lowercase or uppercase.

These ADTs will be discussed in later chapters:


5. Tree (Hierarchy of Nodes connected by Edges)
6. Graph (Network of Nodes and Edges)
7. Set (Unordered Collection of Unique Elements)

Key Operations:

• add(item): Add an item to the set.


• remove(item): Remove an item from the set.
• contains(item): Check if the set contains an item.
• size(): Get the number of items in the set.
8. Map (Key-Value Pairs)

Key Operations:

• put(key, value): Add or update a key-value pair.


• get(key): Retrieve the value associated with a key.
• remove(key): Remove a key-value pair by key.
• containsKey(key): Check if the map contains a specific key.

9. Priority Queue (Queue with Priority-based ordering)

Key Operations:

• enqueue(element, priority): Inserts an element with a given priority into the queue.
• dequeue(): Removes and returns the element with the highest priority.
• peek(): Returns the element with the highest priority without removing it.
• isEmpty(): Checks if the priority queue is empty.
• size(): Returns the number of elements in the queue.

Common implementations of each ADT

1. Stack
• Array: Fixed-size or dynamically resizable array.
• Linked List: Singly linked list or doubly linked list.
• Deque (Double-Ended Queue): Can be implemented using a doubly linked list or a
dynamic array.
2. Queue
• Array: Fixed-size or circular array.
• Linked List: Singly linked list or doubly linked list.
• Circular Buffer (Ring Buffer): A fixed-size buffer that overwrites the oldest data with
new data when full.
• Deque (Double-Ended Queue): Can be implemented using a doubly linked list or a
dynamic array.
3. List
• Array: A fixed-size or dynamically resizable array.
• Linked List: A sequence of nodes where each node points to the next node.
o Singly linked list
o Doubly linked list
o Circular linked list

4. String
a. Array: A fixed-size or dynamically resizable array of characters.
b. Linked list: Singly linked list or doubly linked list.
c. Rope: A balanced binary tree where each leaf node contains a substring. Useful for
efficient concatenation and splitting.
d. Suffix Tree/Array: Specialized data structures for efficient string operations like
substring search and pattern matching.

These ADTs will be discussed in later chapters:

5. Tree
a. Binary Tree
b. Binary Search Tree (BST)
c. AVL Tree:
d. Red-Black Tree:
e. B-Tree:
f. Trie (Prefix Tree):
6. Graph
a. Adjacency List
b. Adjacency Matrix
c. Edge List
7. Set
a. Hash Table
b. Hash Set
c. Balanced Binary Search Tree
8. Map
a. Hash Table
b. Hash Map
c. Balanced Binary Search Tree
d. Trie
9. Priority Queue
a. Binary Heap
b. Binomial Heap
c. Fibonacci Heap

Note:

The choice of data structure to implement an Abstract Data Type (ADT) can significantly impact
the time complexity of operations, space efficiency, and ease of use. Different data structures
have different strengths and weaknesses, and these affect how efficiently they can perform the
operations required by the ADT.
The C++ Standard Library
The Standard Library in C++ includes a robust set of tools that are incredibly useful. It provides:

• Templated Containers: These are classes like vector, list, map, set etc., which are generic
so they can work with any data type. This eliminates the need to write a new linked list or
stack class for every different type of element you want to store.
• Algorithms: The library comes with a wide array of pre-written algorithms like sorting (sort),
searching (find), and many more. These are optimized for performance, reducing the risk of
errors and enhancing efficiency.
• Iterators: These provide a way to traverse through the elements of containers in a
standardized manner, which is critical for using many of the standard algorithms.

The Standard Library uses advanced C++ features, especially templates (see Chapter 3.0), which
can present a steep learning curve. However, mastery of these features is invaluable for enhancing
code quality and development speed.

Containers

The Standard Library offers a variety of container classes tailored for various applications. These
containers can be broadly classified into three main types:

1. Sequence Containers: These maintain the ordering of inserted elements as specified by


the user. Examples include vector, list, and deque.
2. Associative Containers: These containers automatically sort elements upon insertion,
facilitating quick lookup, insertion, and deletion. Examples are set, map, multiset, and
multimap.
3. Container Adapters: These provide a different interface for sequence containers to
achieve specific behaviors. They include stack, queue, and priority_queue.

Standard library Algorithms and Iterators will be discussed in upcoming chapters.


Example for std::array and std::vector
#include <iostream>
#include <array>
#include <vector>

int main() {
// std::array example: Size must be a compile-time constant
std::array<int, 5> arr = { 1, 2, 3, 4, 5 };

std::cout << "std::array elements: ";


for (int val : arr) {
std::cout << val << " ";
}
std::cout << '\n';

// std::vector example: Size can be dynamic (resizeable at runtime)


std::vector<int> vec = { 10, 20, 30, 40, 50 };
vec.push_back(60); // Adding an element dynamically

std::cout << "std::vector elements: ";


for (int val : vec) {
std::cout << val << " ";
}
std::cout << '\n';

return 0;
}

Example for std::stack


#include <iostream>
#include <stack>

int main() {
std::stack<int> stack;

stack.push(10);
stack.push(20);
stack.push(30);

std::cout << "Top element: " << stack.top() << '\n';

stack.pop();
std::cout << "After pop: ";
while (!stack.empty()) {
std::cout << stack.top() << " ";
stack.pop();
}
std::cout << '\n';

return 0;
}
Example for std::list
#include <iostream>
#include <list>

void printList(const std::list<int>& myList) {


for (int num : myList) {
std::cout << num << " ";
}
std::cout << '\n';
}

int main() {
std::list<int> myList;

myList.push_back(10);
myList.push_back(20);
myList.push_back(30);
myList.push_front(5);

std::cout << "The elements in the list are: ";


printList(myList);

myList.remove(20);

std::cout << "The elements in the list after removing 20 are: ";
printList(myList);

std::cout << "The size of the list is: " << myList.size() << '\n';

myList.clear();

if (myList.empty()) {
std::cout << "The list is now empty." << '\n';
}
else {
std::cout << "The list is not empty." << '\n';
}

return 0;
}

Example for std::queue of std::strings


#include <iostream>
#include <queue>
#include <string>
#include <string_view>
#include <algorithm> // For std::transform

// Function to convert a string to lowercase


// so we can end the loop even if user enters "Done", "DONE"
// or other capitalizations of "done"
std::string toLower(const std::string& s) {
std::string result = s;
std::transform(result.begin(), result.end(), result.begin(),
[](unsigned char c) { return std::tolower(c); });
return result;
}

int main() {
std::queue<std::string> myQueue;
std::string input;
std::string_view inputView;
std::cout << "Enter elements to enqueue (type 'done' without quotes to finish)\n";

// Loop to read input and add to the queue until "done" is entered
do {
std::getline(std::cin, input);

// copy the input to a string view


// so we can pass this as a function parameter without allocating memory
inputView = input;

// only add the input to queue if entered string is not empty or not "done"
if (!input.empty() && toLower(std::string(inputView)) != "done") {
myQueue.push(input);
}
} while (toLower(std::string(inputView)) != "done");

std::cout << "\nQueue size: " << myQueue.size() << '\n';

// Display queue info


// note that we can't use front(), back(), pop()
// if the queue is empty
if (myQueue.empty()) {
std::cout << "Front element: none\n";
std::cout << "Back element: none\n";
}
else {
std::cout << "Front element: " << myQueue.front() << '\n';
std::cout << "Back element: " << myQueue.back() << '\n';

// Dequeue the front element


myQueue.pop();

if (myQueue.empty()) {
std::cout << "Front element after pop: none\n";
}
else {
std::cout << "Front element after pop: " << myQueue.front() << '\n';
}
}

// Dequeue all remaining elements one by one


std::cout << "\nDequeueing elements...\n";
while (!myQueue.empty()) {
std::cout << "Dequeueing element: " << myQueue.front() << '\n';
myQueue.pop();
}
std::cout << "Queue is empty.\n";

return 0;
}
std::string_view (which lives in the <string_view> header) provides read-only access to
an existing string (a C-style string, a std::string, or another std::string_view) without making a copy.
It does not allocate memory and simply references the underlying string.

Note:

std::string is slow and expensive to initialize (or copy). Prefer std::string_view over std::string when
you need a read-only string, especially for function parameters.

Using the standard library vs custom implementations


Using the standard library containers like std::stack, std::queue, and algorithms like
std::sort are generally preferred for most programming tasks due to their speed, thorough
documentation, and efficiency.

However, it is essential to learn how to implement basic data structures to gain a deeper
understanding and improve algorithmic thinking. Knowing how the underlying mechanisms work
enables writing more efficient and robust code when necessary.

Implementing ADTs and data structures from scratch provides a clearer understanding of
time and space complexity. For example, understanding why certain operations on a std::vector
might be O(n) (e.g., insert at the beginning), whereas they’re O(1) for a linked list.

It also gives the ability to implement data structures in environments where standard
libraries are unavailable or not optimized enough, such as the following examples:

Embedded Systems: In microcontrollers or low-memory environments, it might be necessary to


manually write simple, memory-efficient stacks or queues to avoid the overhead of dynamic
memory allocation that the standard library containers often use. In addition, some environments
don’t support the standard library,

Operating Systems: In kernel programming, standard library support is typically absent, so custom
data structures must be written with careful attention to memory management and real-time
constraints.

High-Performance Systems: In high-frequency trading or game engines, custom, highly optimized


data structures may be employed to meet strict performance requirements.

You might also like