Telerik blogs

Data structures are the basis for the efficient and optimized development of web applications in ASP.NET Core. In this first part, we will learn about the simplest types of data structures and how we can implement them using the C# programming language.

ASP.NET Core development relies on manipulating, storing and retrieving data. A solid understanding of data structures allows developers to design and implement web applications that can handle large volumes of data and quickly respond to requests, scaling the applications efficiently.

Whether managing user sessions, caching data or optimizing database queries, data structures play a crucial role in improving the performance and reliability of ASP.NET Core applications, making them more competitive and capable of meeting the demands of modern web development.

In this post, we will cover the simplest topics in data structure in the context of ASP.NET Core, implementing an example of each and understanding each approach’s meaning.

What are Data?

Data represents a unit or element of information and can take different forms such as text, numbers, dates, images, videos and others.

ASP.NET Core uses the C# (C-Sharp) programming language, which has a wide variety of data types. The main types are:

- Primitive types:

  • int: Represents signed integers
  • short: Represents short integers
  • float: Represents single-precision floating-point numbers
  • char: Represents a single Unicode character
  • bool: Represents a logical value, which can be true or false
  • And others

- Composite types:

  • Arrays: Arrays are composite data structures that store a collection of elements of the same type. Elements in an array are accessed through numeric indices.
  • Dictionaries: The Dictionary<TKey, TValue> class represents a collection of key-value pairs, where each key is unique. It allows efficient retrieval of values based on their keys.
  • Queues and stacks: The queue and stack data structures are used to organize data according to the principle of “first in, first out” (queue) or “last in, first out” (stack).
  • Tuples: Tuples are data structures that can store a heterogeneous collection of elements of different types. They are useful when you need to group related values together but don’t want to create a custom class.
  • Interfaces: Although they do not store data directly, interfaces are composite types that define contracts for classes that must implement certain methods and properties.
  • And others.

What are Data Structures?

Data structures are a fundamental concept in computer science and programming. They are a way of organizing and storing data in a structured and efficient way so that it can be accessed and manipulated easily.

Data structures define how data is organized, stored and operated in a computer’s memory.

Next, we will understand the concept behind each of these data structures and how they can be implemented in C# within the context of ASP.NET Core. You can access the source code of the examples here: Practicing Data Structure source code.

Arrays

An array is a data structure used to store a collection of elements of the same data type under a single variable name. These elements are stored in contiguous memory locations, making their access and manipulation efficient. Each element in an array is identified by an index or position, starting from 0 for the first element, 1 for the second, and so on.

Arrays are commonly used to store lists of data, such as numbers, strings or objects. They offer several advantages, including:

  • Efficient access: Access to the elements of an array is fast and constant (O(1)) because you can directly access an element through its index.
  • Memory efficiency: Arrays allocate memory for a fixed number of elements, which can be more memory efficient than other data structures such as linked lists or dynamic arrays.
  • Sequential storage: The elements of an array are stored sequentially in memory, making them ideal for situations where data needs to be accessed in order.

However, arrays also have some limitations, mainly with regard to size. In some scenarios, it may be necessary to use more complex data structures, such as dynamic lists.

Different programming languages have their own syntax for defining and working with arrays.

In C#, arrays can be declared as follows:

int[] intArray = { 3, 5, 10, 7, 12 }; // Integer type element

string[] stringArray = { "Apple", "Banana", "Cherry", "strawberry", "Fig" }; // Sting type element

int[] definedSizeArray = new int[5] { 3, 5, 10, 7, 12 }; // Array with defined size

int[] dynamicSizeArray = new int[] { }; // Array with dynamic size

Array structure

Types of Arrays

- One-dimensional arrays (1-D arrays):
Also known as a vector, it is an array with only one dimension. Elements are organized linearly.

int[] vector = new int[5]; // Creates an array with 5 integer elements
vector[0] = 1;
vector[1] = 2;
vector[2] = 3;
vector[3] = 4;
vector[4] = 5;

- Two-dimensional arrays (2-D arrays):
Also called a matrix, a two-dimensional array is organized into rows and columns. It can be viewed as a table or grid.

int[,] matrix = new int[3, 3]; // Creates a 3x3 matrix of integers
matrix[0, 0] = 1;
matrix[0, 1] = 2;
matrix[0, 2] = 3;
matrix[1, 0] = 4;
matrix[1, 1] = 5;
matrix[1, 2] = 6;
matrix[2, 0] = 7;
matrix[2, 1] = 8;
matrix[2, 2] = 9;

- Three-dimensional array (3-D arrays):
A three-dimensional array is organized into layers, rows and columns. It can be useful for representing three-dimensional data, such as cubes.

int[,,] cube = new int[3, 3, 3]; // Creates a 3x3x3 cube of integers
cube[0, 0, 0] = 1;
cube[0, 0, 1] = 2;
cube[0, 0, 2] = 3;
cube[0, 1, 0] = 4;
cube[0, 1, 1] = 5;
cube[0, 1, 2] = 6;
cube[0, 2, 0] = 7;
cube[0, 2, 1] = 8;
cube[0, 2, 2] = 9;
// And so on for the other layers of the cube

Where to Use Arrays?

Arrays are fundamental data structures in C# and are used in various applications to store and manipulate collections of data. Below are some common applications of arrays in C#:

  • Storing lists of data: Arrays are often used to store collections of data such as numbers, strings or objects. For example, you can use an array to store a list of integers that represent test scores.
  • Sorting and searching: Arrays can be used for sorting and searching operations. You can sort an array of elements in ascending or descending order using algorithms like bubble sort or quicksort. Search algorithms such as binary search can be applied to find specific elements in sorted arrays efficiently.
  • Implementing data structures: Arrays are building blocks for implementing more complex data structures such as lists, stacks, queues and hash tables. Many data structures rely on arrays to store and manage their elements.
  • Arrays and grids: Two-dimensional arrays are commonly used to represent matrices and grids. They are useful in graphics programming, game development and scientific simulations.
  • Input/output handling: Arrays are used to buffer data read from or written to files, databases or network sockets. This allows efficient processing of data in blocks.
  • Algorithm implementation: Many algorithms, such as dynamic programming or backtracking, use arrays to store intermediate results or memorization tables.
  • Managing collections: Arrays can be used to store collections of objects or data structures. For example, you can use arrays to implement a simple database, a contact list or a book collection in a library management system.
  • Graphics and game development: Arrays are essential for storing and manipulating pixel data, managing game state, and creating levels and game maps.

These are just a few examples of how arrays are applied in C# programming. Arrays are versatile and serve as the basis for many data storage and manipulation tasks in various software applications.

Advantages of Using Arrays

  • Efficient random access: Arrays provide constant-time (O(1)) access to elements based on their index. You can directly access any element by knowing its position in the array.
  • Memory efficiency: Arrays are memory-efficient because they store elements in contiguous memory locations. This leads to minimal memory overhead compared to some other data structures.
  • Predictable performance: Accessing elements in an array has consistent and predictable performance characteristics, which makes it suitable for many applications.
  • Simple syntax: Most programming languages have straightforward syntax for creating, initializing and accessing arrays, making them easy to work with.
  • Fixed size: In some cases, having a fixed-size array can be an advantage because it enforces a limit on the number of elements, which can help prevent memory issues and improve predictability.

Disadvantages of Using Arrays

  • Fixed size: The fixed size of arrays can also be a disadvantage when the number of elements is unknown or can change over time. Resizing an array can be inefficient and may require creating a new, larger array and copying elements.
  • Wasted memory: Arrays allocate memory for the maximum number of elements they can hold, even if the actual number of elements is smaller. This can lead to wasted memory if the array size significantly exceeds the data size.
  • Inefficient insertions and deletions: Inserting or deleting elements in the middle of an array can be inefficient because it requires shifting elements to accommodate the change, resulting in a time complexity of O(n), where n is the number of elements.
  • Lack of built-in functions: Arrays often lack built-in functions for common operations like searching, sorting or filtering. You may need to implement these functions manually or use external libraries.
  • Homogeneous elements: Arrays typically store elements of the same data type. If you need to store elements of different types, you might need to use an array of objects or a more complex data structure.
  • Inflexible data structures: Arrays are not always the best choice for complex data structures or when you need dynamic resizing, efficient insertions/deletions or key-value associations (which are better handled by data structures like lists, dictionaries or hash tables).

Linked Lists

A linked list is a data structure consisting of a sequence of elements, where each element (node) contains a value and a reference (or link) to the next element in the sequence.

Thus we can define that each node contains the following elements:

  • Data - Each node can store data.
  • Address − Each node contains an address for the next node, called Next.
  • Head − The first node of a linked list is referenced by a pointer called Head.

In C#, there are three main types of linked lists:

Singly Linked Lists

In a singly linked list, each node contains a value and a reference to the next node in the list. The last node in the list usually has a reference to null, indicating the end of the list.

Singly linked lists are efficient for operations that involve adding or removing elements from the beginning of the list, but less efficient for operations that involve accessing elements by index.

Linked List singly structure

Creating a Singly Linked List in C#

To create a singly-linked list in C#, create a web application with the command: dotnet new web -o PracticingDataStructure.

Open the project and create a new folder called “Models” and inside it create the classes below:

  • Node
namespace PracticingDataStructures.Models;

public class Node<T>
{
     public T Data { get; set; }
     public Node<T> Next { get; set; }

     public Node(T data)
     {
         Date = date;
         Next = null;
     }
}
  • MyLinkedList
namespace PracticingDataStructures.Models;

public class MyLinkedList<T>
{
     private Node<T> head;

     public void Add(T data)
     {
         Node<T> newNode = new Node<T>(data);
         if (head == null)
         {
             head = newNode;
         }
         else
         {
             Node<T> current = head;
             while (current.Next != null)
             {
                 current = current.Next;
             }
             current.Next = newNode;
         }
     }

     public void Display()
     {
         Node<T> current = head;
         while (current != null)
         {
             Console.Write(current.Data + " -> ");
             current = current.Next;
         }
         Console.WriteLine("null");
     }
}

Then in the Program.cs file, add the code below:

using PracticingDataStructures.Models;

MyLinkedList<int> myList = new MyLinkedList<int>();
myList.Add(1);
myList.Add(2);
myList.Add(3);

Console.WriteLine("Singly Linked List:");
Console.WriteLine("");

myList.Display();
Console.WriteLine("");

Finally, in the terminal, execute the following command to run the application: dotnet run

Thus, you will have the following result, which shows exactly the structure of a linked list.

Singly Linked list result

In the code above we defined a class Node<T>, which represents a single element in the linked list. It contains a Data property to store the element’s value and a Next property to point to the next element.

We define a class MyLinkedList<T>, which represents the linked list itself and has a head property to point to the first element of the list.

The Add method in the MyLinkedList<T> class allows you to add elements to the end of the list, while the Display method displays the list elements.

In the “Program.cs” file, we create a linked list of integers, add some elements to it, and then display the contents of the linked list.

Doubly Linked Lists

In a doubly linked list, each node contains two links—the first points to the previous node, and the second points to the next node in the sequence.

The previous pointer of the first node and the next pointer of the last node will point to null, as demonstrated in the image below:

Linked List Doubly structure

Creating a Doubly Linked List in C#

In C#, you can use the LinkedList<T> class from the System.Collections.Generic namespace to work with doubly linked lists. Here is an example of how to use the built-in LinkedList<T> class:

//Doubly linked list
// Create a LinkedList of integers
LinkedList<int> myDoublyList = new LinkedList<int>();

// Add elements to the list
myDoublyList.AddLast(1);
myDoublyList.AddLast(2);
myDoublyList.AddLast(3);

// Display the elements in the list
Console.WriteLine("Doubly LinkedList:");
foreach (int item in myDoublyList)
{
    Console.Write(item + " <-> ");
}
Console.WriteLine("null");

In the code above, we create a LinkedList<int> called myDoublyList and add elements to it, then the list items are displayed through a for-each loop.

If you run the application you will have the following result in the console:

Doubly Linked list result

Circular Linked Lists

A circular linked list is a type of linked list in which the last element (node) of the list points back to the first element, forming a closed loop or cycle.

In other words, unlike a traditional linear linked list, where the “next” pointer to the last element is typically set to null, in a circular linked list, the “next” pointer to the last element points to the first element of the list as shown in the image below:

Linked Circular Doubly structure

Creating a Circular-Linked List in C#

To create a circular linked list, inside the “Models” folder add the class below:

  • MyCircularLinkedList
namespace PracticingDataStructures.Models;

public class MyCircularLinkedList<T>
{
    private Node<T> head;
    private Node<T> tail;

    public void Add(T data)
    {
        Node<T> newNode = new Node<T>(data);
        if (head == null)
        {
            head = newNode;
            tail = newNode;
            tail.Next = head; // Make it circular
        }
        else
        {
            newNode.Next = head;
            tail.Next = newNode;
            tail = newNode;
        }
    }

    public void Display()
    {
        if (head == null)
        {
            Console.WriteLine("Circular Linked List is empty.");
            return;
        }

        Node<T> current = head;
        do
        {
            Console.Write(current.Data + " -> ");
            current = current.Next;
        } while (current != head);

        Console.WriteLine(" (Back to head)");
    }
}

And in the Program.cs file add the following code:

// Circular Linked List
var circularLinkedList = new MyCircularLinkedList<int>();
circularLinkedList.Add(1);
circularLinkedList.Add(2);
circularLinkedList.Add(3);

Console.WriteLine("Circular Linked List:");
circularLinkedList.Display();

In the code above we defined a CircularLinkedList<T> class to represent a circular linked list, where we used the Node<T> class to represent the list elements.

The CircularLinkedList class has head and tail properties, which represent the first and last nodes. When adding elements, we make sure to close the loop by pointing the tail Next back to the head.

The Display method allows you to display the elements of the circular linked list.

In the Program.cs file, we create a circular linked list of integers, add some elements and display the contents of the list.

So if you run the application, you will have the following result in the console:

Circular Linked list result

Stacks

A “stack” is an abstract data type that follows the Last-In-First-Out (LIFO) principle and is represented by a collection of elements that has three main operations:

  1. Push: This operation adds an element to the top of the stack.
  2. Pop: This operation removes and returns the top element from the stack.

Stacks are commonly used to manage data collections where insertion and removal order is important.

The most recently added item is the first to be removed. Think of it like a stack of dishes: you can only add or remove dishes from the top.

The image below represents the concept of a stack in C#:

Stack structure

Additionally, there is a third operation called Peek (or Top): This operation allows you to view the top element of the stack without removing it.

In the context of C#, we can implement a stack data structure using the System.Collections.Generic.Stack<T> class, where T is the type of elements you want to store on the stack. This class provides Push, Pop and Peek methods to perform the stack operations.

In the Program.cs file add the code below:

Stack<int> stack = new Stack<int>();

// Pushing elements onto the stack
stack.Push(1);
stack.Push(2);
stack.Push(3);

// Peeking at the top element without removing it
int topElement = stack.Peek();
Console.WriteLine("Top element: " + topElement);

// Popping elements from the stack
int poppedElement1 = stack.Pop();
int poppedElement2 = stack.Pop();

Console.WriteLine("Popped element 1: " + poppedElement1);
Console.WriteLine("Popped element 2: " + poppedElement2);

// Peek again to see the new top element
topElement = stack.Peek();
Console.WriteLine("Top element after popping: " + topElement);

In the code above, we create a stack of integers using Stack<int>, then push three integers (1, 2 and 3) onto the stack using the Push method.

Then we use the Peek method to see the top element without removing it. We then use the Pop method to remove and retrieve elements from the stack. After highlighting two elements, we peek again to see the new top element.

If you run the application, you will have the following result in the console:

Stack result

Queues

A queue is a structure that represents a collection of elements, where elements are added to one end (back) and removed from the other end (front).

A queue follows the “first in, first out” (FIFO) principle, which means that the element that has been in the queue the longest is the first to be removed. In other words, it works like a real queue of people waiting in line, where whoever enters the queue first is the first to be served.

A queue has two main operations:

  • Enqueue (also known as “push” or “insert”): This operation adds an element to the end of the queue.
  • Dequeue (also known as “pop” or “remove”): This operation removes and returns the element at the beginning of the queue.

In addition to these fundamental operations, queues often also provide methods for checking whether the queue is empty and for inspecting the element in front without removing it, commonly called “peek” or “front.”

Queue structure

We can use queues in a wide variety of scenarios, including:

  • Task scheduling: Queues can be used to manage tasks or jobs to be performed in a specific order.
  • Print job management: In a print queue, print jobs are processed in the order they are received.
  • Breadth First Search (BFS) algorithm: Queues are often used to traverse and search breadth-first graphs or trees.
  • Common variations of queues include priority queues (where elements have associated priorities), double queues (deque or dequeues) and circular queues, each with their own specific use cases and variations in behavior.

In C# we can define queues using the System.Collections.Generic.Queue<T> class. The code below demonstrates how to implement a queue and how to perform both Enqueue and Dequeue operations.

// Create a new queue of integers
Queue<int> myQueue = new Queue<int>();

// Enqueue elements to the queue
myQueue.Enqueue(1);
myQueue.Enqueue(2);
myQueue.Enqueue(3);
myQueue.Enqueue(4);
myQueue.Enqueue(5);

// Dequeue and process elements in FIFO order
while (myQueue.Count > 0)
{
    int item = myQueue.Dequeue();
    Console.WriteLine($"Dequeued: {item}");
}

In the code above we declare a Queue<int> called myQueue to store integers. Then, we use the Enqueue method to add elements to the end of the queue.

We then use a while loop to repeatedly dequeue elements using the Dequeue method until the queue is empty. The elements removed from the queue are processed in FIFO order.

You can also use other methods provided by the Queue<T> class, such as Peek to view the front element without removing it and Count to check the number of elements in the queue.

If you run this piece of code you will get the result shown in the image below:

Queue result

Conclusion

Learning data structures is essential for solving software problems efficiently. In this first part, we check the simplest types of structures in the context of ASP.NET Core where it is not necessary to install any type of external resource as ASP.NET Core has a set of built-in classes that help the developer to deal with structures simplest to the most complex.

In this post we saw four types of data structures: arrays, linked lists, stacks and queues. In the next part, we will implement more complex types of data structures such as trees, heaps and graphs.


assis-zang-bio
About the Author

Assis Zang

Assis Zang is a software developer from Brazil, developing in the .NET platform since 2017. In his free time, he enjoys playing video games and reading good books. You can follow him at: LinkedIn and Github.

Related Posts

Comments

Comments are disabled in preview mode.