Top Data Structures That Every Programmer Must Know

By | March 4, 2023

Data structures are fundamental tools that every programmer must be familiar with. They are used to organize and manipulate data in a way that makes programming more efficient and effective. In this article, we will explore some of the most important data structures that every programmer should know, along with examples in C++.

1. Arrays


Arrays are one of the most commonly used data structures in programming. They are a collection of elements of the same data type, stored in contiguous memory locations. Arrays provide fast access to individual elements, making them a popular choice for tasks that require fast retrieval of data. They also have efficient memory usage, making them a suitable choice for large amounts of data.

In C++, arrays can be declared as follows:

int arr[10]; // declares an array of 10 integers

In the above example, we have declared an array of 10 integers named arr. To access an element of the array, we use its index, which starts at 0. For example, to access the first element of the array, we use arr[0]. We can also initialize an array at the time of declaration, like so:

// initializes an array of 5 integers with the values 1, 2, 3, 4, and 5
int arr[5] = {1, 2, 3, 4, 5}; 

Arrays can be used for various tasks such as sorting, searching, and matrix operations.

2. Linked Lists


Linked lists are a more flexible data structure than arrays. They don’t require contiguous memory locations, and their size can change dynamically during runtime. A linked list consists of nodes, each containing a data element and a pointer to the next node.

In C++, a linked list can be implemented as follows:

struct Node {
    int data;
    Node* next;
};

In the above example, we have defined a Node structure that contains an integer data and a pointer next to the next Node. To create a linked list, we can use a series of Node structures that are linked together via their next pointers. Here is an example of how to create a linked list of integers:

Node* head = new Node; // creates the head of the linked list
head->data = 1;
head->next = NULL; // the head has no next node yet

Node* second = new Node; // creates the second node
second->data = 2;
second->next = NULL; // the second node has no next node yet

head->next = second; // links the head to the second node

In the above example, we have created a linked list with two nodes containing the integers 1 and 2. The head pointer points to the first node, and the next pointer of the first node points to the second node.

Linked lists can be used for tasks such as representing a sequence of elements, implementing a stack or a queue, or as an alternative to arrays for dynamic data storage.

3. Stacks


Stacks are a data structure that follows a Last-In-First-Out (LIFO) approach. Elements are added and removed from the top of the stack. Stacks are useful for tasks such as parsing expressions, evaluating mathematical formulas, and for undoing actions in applications. They can also be used to store temporary data in functions.

In C++, a stack can be implemented using the stack class from the standard template library (STL):

#include <stack>

std::stack<int> s;
s.push(1);
s.push(2);
s.push(3);
int top = s.top(); // returns the top

In the above example, we have included the header file to use the stack class from the STL. We have then created a stack object s that can hold integers. We have added three integers to the stack using the push() method. The top() method returns the top element of the stack, which is 3 in this case.

We can remove the top element from the stack using the pop() method. The empty() method checks if the stack is empty, and the size() method returns the size of the stack. Here is an example:

while (!s.empty()) {
    int top = s.top();
    std::cout << top << std::endl;
    s.pop();
}

In the above example, we have used a while loop to remove all elements from the stack and print them. The loop continues until the stack is empty.

4. Queues


Queues are a data structure that follows a First-In-First-Out (FIFO) approach. Elements are added to the back of the queue and removed from the front. Queues are useful for tasks such as scheduling tasks in an operating system, managing tasks in a printer spooler, and for implementing a buffer in communication systems.

In C++, a queue can be implemented using the queue class from the STL:

#include <queue>

std::queue<int> q;
q.push(1);
q.push(2);
q.push(3);
int front = q.front(); // returns the front

In the above example, we have included the header file to use the queue class from the STL. We have then created a queue object q that can hold integers. We have added three integers to the queue using the push() method. The front() method returns the front element of the queue, which is 1 in this case.

We can remove the front element from the queue using the pop() method. The empty() method checks if the queue is empty, and the size() method returns the size of the queue. Here is an example:

while (!q.empty()) {
    int front = q.front();
    std::cout << front << std::endl;
    q.pop();
}

In the above example, we have used a while loop to remove all elements from the queue and print them. The loop continues until the queue is empty.

5. Trees


Trees are a hierarchical data structure that consists of nodes connected by edges. Each node has zero or more child nodes, except for the root node, which has no parent. Trees are useful for tasks such as representing hierarchical relationships, searching, and sorting.

In C++, a binary tree can be implemented using a struct:

struct Node {
    int data;
    Node* left;
    Node* right;
};

In the above example, we have defined a Node structure that contains an integer data, a pointer left to the left child node, and a pointer right to the right child node. We can create a binary tree using a series of Node structures that are linked together via their child pointers.

Here is an example of how to create a binary tree:

Node* root = new Node;
root->data = 1;

root->left = new Node;
root->left->data = 2;

root->right = new Node;
root->right->data = 3;

In the above example, we have created a binary tree with a root node containing the integer 1, a left child node containing the integer 2, and a right child node containing the integer 3.

To traverse a binary tree, we can use one of the following methods:

  • In-order traversal: Traverse the left subtree, visit the root, traverse the right subtree.
  • Pre-order traversal: Visit the root, traverse the left subtree, traverse the right subtree.
  • Post-order traversal: Traverse the left subtree, traverse the right subtree, visit the root.

Here is an example of how to perform an in-order traversal of a binary tree:

void inorderTraversal(Node* node) {
    if (node != nullptr) {
        inorderTraversal(node->left);
        std::cout << node->data << std::endl;
        inorderTraversal(node->right);
    }
}

In the above example, we have defined a function inorderTraversal that performs an in-order traversal of a binary tree. The function takes a pointer to a Node object as an argument. If the node is not null, the function first traverses the left subtree recursively, then visits the root node, and finally traverses the right subtree recursively. When we call this function with the root node of a binary tree, it will print the nodes in the order 2, 1, 3.

6. Graphs


Graphs are a data structure that consists of a set of vertices connected by edges. Graphs are useful for tasks such as representing networks, maps, and social connections. There are many types of graphs, including directed graphs, undirected graphs, weighted graphs, and unweighted graphs.
In C++, a graph can be implemented using an adjacency list or an adjacency matrix. An adjacency list is a vector of vectors that stores the vertices and their adjacent vertices. An adjacency matrix is a two-dimensional array that stores a boolean value indicating if there is an edge between two vertices.

Here is an example of how to implement a graph using an adjacency list:

#include <vector>

std::vector<std::vector<int>> graph(5);
graph[0].push_back(1);
graph[0].push_back(2);
graph[1].push_back(2);
graph[2].push_back(0);
graph[2].push_back(3);
graph[3].push_back(3);

In the above example, we have created a graph with 5 vertices and stored it in a vector of vectors called graph. We have then added edges between the vertices using the push_back() method. The first line adds edges between vertex 0 and vertices 1 and 2. The second line adds an edge between vertex 1 and vertex 2. The third line adds edges between vertex 2 and vertices 0 and 3. The fourth line adds a self-loop edge to vertex 3.

To traverse a graph, we can use one of the following algorithms:

  • Breadth-First Search (BFS): Visit all the vertices at the same level before moving on to the next level.
  • Depth-First Search (DFS): Explore as far as possible along each branch before backtracking.

Here is an example of how to perform a DFS traversal of a graph:

#include <iostream>
#include <vector>

void dfsTraversal(int node, std::vector<bool>& visited, std::vector<std::vector<int>>& graph) {
    visited[node] = true;
    std::cout << node << std::endl;
    for (int i = 0; i < graph[node].size(); ++i) {
        int neighbor = graph[node][i];
        if (!visited[neighbor]) {
            dfsTraversal(neighbor, visited, graph);
        }
    }
}

int main() {
    std::vector<std::vector<int>> graph(4);
    graph[0].push_back(1)
    graph[0].push_back(2);
    graph[1].push_back(2);
    graph[2].push_back(0);
    graph[2].push_back(3);
    graph[3].push_back(3);

    std::vector<bool> visited(graph.size(), false);
    dfsTraversal(0, visited, graph);

return 0;

In the above example, we have defined a function dfsTraversal that performs a DFS traversal of a graph. The function takes three arguments: the current node being visited, a vector visited that keeps track of which nodes have been visited, and the graph itself, represented as a vector of vectors.

The function starts by marking the current node as visited and printing its value. It then iterates through all the neighbors of the current node and recursively calls itself on any unvisited neighbors. When we call this function with the starting node of a graph, it will print the nodes in the order 0, 1, 2, 3.

7. Hash Tables


A hash table is a data structure that maps keys to values using a hash function. Hash tables are used to implement associative arrays, sets, and caches. A hash function takes a key as input and returns an index to an array where the value corresponding to that key is stored. The goal of a hash function is to minimize collisions, where two keys are mapped to the same index.
In C++, a hash table can be implemented using the unordered_map container. An unordered_map is a hash table that stores key-value pairs. Keys must be unique, but values can be duplicated. The unordered_map container uses a hash function to compute the index of each key-value pair.

Here is an example of how to use an unordered_map:

#include <iostream>
#include <unordered_map>

int main() {
    std::unordered_map<std::string, int> myMap;
    myMap["hello"] = 42;
    myMap["world"] = 23;
    std::cout << myMap["hello"] << std::endl;
    std::cout << myMap["world"] << std::endl;
    return 0;
}

In the above example, we have created an unordered_map called myMap that maps strings to integers. We have added two key-value pairs to the map using the [] operator. We have then printed the values corresponding to the keys “hello” and “world” using the [] operator.

Conclusion


In conclusion, data structures are essential tools for any programmer. By understanding how to use data structures effectively, programmers can improve the efficiency and readability of their code. The data structures discussed in this article are just a few of the many that programmers should be familiar with. It is important to choose the right data structure for each task to achieve optimal performance.

Please write comments below if you find anything incorrect, or you want to share more information about the topic discussed above. A gentle request to share this topic on your social media profile.

Author: Mithlesh Upadhyay

I hold an M.Tech degree in Artificial Intelligence (2023) from Delhi Technological University (DTU) and possess over 4 years of experience. I worked at GeeksforGeeks, leading teams and managing content, including GATE CS, Test Series, Placements, C, and C++. I've also contributed technical content to companies like MarsDev, Tutorialspoint, StudyTonight, TutorialCup, and Guru99. My skill set includes coding, Data Structures and Algorithms (DSA), and Object-Oriented Programming (OOPs). I'm proficient in C++, Python, JavaScript, HTML, CSS, Bootstrap, React.js, Node.js, MongoDB, Django, and Data Science.