User-defined Data structures in Python

User-defined data structures are not inbuilt in Python, but we can still implement them. We can use the existing functional options in Python to create new data structures. For example, when we say a list = [], Python recognizes it as a list and calls everything related to a list. But when we say a linked list or a queue, Python won't know what these are. In this article, we will discuss some user-defined data structures in Python:

1. Linked Lists

A linked list, like its name suggests, is linked. Every node in the linked list consists of two segments- the data field with the data/ value and the next field holding the reference to the next node, thus linking together. It is a linear data structure, but the elements are not stored in contiguous memory locations.

Important points about Linked lists:

  1. A linked list is an ordered collection of elements.
  2. A linked list is also used to implement other user-defined data structures like stack and queue.
  3. Using the collections module in Python, we can use the deque object to implement operations like insert and delete on linked lists.
  4. The first node in a linked list is the head, and we must start all the operations on the linked list from it.
  5. The last node of the linked list refers to None showing that the linked list is complete.
User-defined Data structures in Python

Further, linked lists are of three types:

  1. Simple linked list
  2. Double linked list
  3. Circular linked list

A simple linked list looks like this:

User-defined Data structures in Python

As shown in the above figure, the head is the first node, and the next (reference) part of the last node holds None.

A double-linked list looks like this:

User-defined Data structures in Python

In a double-linked list, every node will have three sections. Head holds the reference of the first node, the "previous" section of the first node holds None, and the next field of the last node refers to None. Each node will hold two references along with the data, one to its previous node and the next to the succeeding node.

Circular linked list:

A circular linked list can be single or double:

Circular single linked list:

User-defined Data structures in Python

It is a single linked list, but the last node in the list holds the reference of the first node like a circle.

Circular double-linked list:

User-defined Data structures in Python

It is a double-linked list, but the last node in the list holds the reference of the first node, and the 'previous' section of the first node holds the reference of the last node like a circle.

Example Program:

Output:

Displaying the linked list:  10 -> 20 -> 30 -> None
Traversing from node to node:
10
20
30
After inserting 5 at the beginning: 5 -> 10 -> 20 -> 30 -> None
After inserting 40 at the end: 5 -> 10 -> 20 -> 30 -> 40 -> None
After inserting a node after 15: 5 -> 10 -> 15 -> 20 -> 30 -> 40 -> None
After inserting a node before 30: 5 -> 10 -> 15 -> 20 -> 25 -> 30 -> 40 -> None

2. Stack

Stack is a linear data structure. It is implemented on the principle "LIFO" abbreviation: Last in, first out. It means that the element that is last inserted into a stack will be the first one that gets deleted. A stack only has one opening, which means to insert or delete elements; we need to use the same end. When we insert elements into a stack, we insert elements on top of each other-new elements on the existing element. After inserting all the elements, if we want to delete elements from the stack, the last element inserted will be the first to come out.

Terminologies:

  1. Inserting an element into the stack: push
  2. Deleting an element from the stack: pop
  3. The end/ opening of the stack: top of the stack

Functions in Python for stacks:

Implementation of a stack:

We can implement a stack:

  1. Using lists
  2. Using linked lists
  3. Using deque
  4. Using queues

Output:

The elements of the stack
1
2
3
The first element to come out: 3
The second element to come out: 2
Final stack: [1]
  • Implementation by lists is the simplest implementation of all. To push elements into the stack, we use the list's append() method, and to pop the elements, we use the stack's pop()

Output:

Stack without pushing any elements: head None
Stack after pushing elements:
 head 14 13 12 11 10 None
After pop 3 times:
14
13
12
Stack:
 head 11 10 None
Element at the top of the stack:
11
Pop till stack becomes empty:
11
10
Traceback (most recent call last):
  File "D:\Programs\DSA \Language\Python data structures programs\stacks.py", line 59, in 
    print(mystack.pop())
  File "D:\Programs\DSA\Language\Python data structures programs\stacks.py", line 34, in pop
    raise Exception("Empty stack")
Exception: Empty stack

We wrote two methods push and pop, to implement a stack. We need to make sure of two points:

  1. When push is performed, we should always add the elements at the beginning of the linked list.
  2. When pop is performed, the element from the beginning has to be deleted.
  3. We created size() and isEmpty(), and top() to check if the stack is empty because if a stack is empty, we can't perform pop.

3. Queues

A queue is a linear data structure like a stack, but the principle of queue implementation is FIFO-First in, first out. It means that the first element inserted into the queue will be the first element to come out of the queue.

Important points about a queue:

  1. There will be two ends to a queue-front and rear ends.
  2. The elements are inserted from the front end and deleted from the rear end.

Terminology:

  1. Inserting an element into a queue: enqueue
  2. Deleting an element from the queue: dequeue
  3. Element at the beginning: front
  4. Element at the end: rear

We can implement a queue in Python:

  1. Using lists
  2. Using collections module
  3. Using queue.Queue

Output:

Using lists:
Queue:  []
Inserting elements:
Queue: [1, 2, 3, 4, 5]
Deleting two elements:
Final queue: [3, 4, 5]

Using the deque class in the collection module
Inserting elements:
Queue: deque([6, 7, 8, 9, 10])
Deleting elements:
Final queue: deque([8, 9, 10])

Using the Queue class in the queue module
Inserting elements:
Queue:
0 1 2 3 4 5 
Is the queue full? True
Deleting elements:
Final queue: [2, 3, 4, 5]
size of the queue: 4
  • All the inbuilt python methods used in different modules are shown above:
    User-defined Data structures in Python
  • A queue can be related to queues in real life. The person who starts the queue gets the ticket to the movie first.

There can be a scenario of high-priority situations where irrespective of the order, we must take care of some aspects first. For such situations, there is a type of queue: Priority Queue.

Difference between Queue and Priority Queue

push(element)Inserts the specified element into the stack
pop()Deletes and returns the element at the top of the stack
top()Returns the element at the top of the stack
peek()Same as the top()
size()Returns the size of the specified stack
empty()Checks if the given stack is empty
Regular QueuePriority Queue
The element at the rear end is deleted when the deque operation is performed.When the deque operation is performed, the element with the highest priority is deleted.
If two elements have the same priority, the first inserted element is deleted.
After the deque operation, the elements remain in FIFO order.After the deque operation, the elements will either be in increasing or decreasing order.

Implementation:

Output:

Created Q: 3 2 19 90 11
Dequeue operation:
The element to be deleted: 90
Final Queue: 2 3 11 19

4. Binary Tree

A tree is a hierarchical representation of nodes. Family trees are real-time examples of a tree. Every node is allowed to have only two children. The node at the highest hierarchy or the top-most node is called the "Root node".

Important points about Binary tree:

  1. Every node can have a left sub-tree and a right sub-tree.
  2. Hence, a node in a binary tree has 3 segments: data, a reference to the left child, and a reference to the right child.
  3. The nodes with the lowest hierarchy without any children are called leaf nodes.
    User-defined Data structures in Python
  4. A tree can be traversed using 2 methods:
    1. DFS: By depth
    2. BFS: By breadth (or) level
  5. DFS traversal further has three types of traversals:
    1. Pre-order Traversal: The root is first visited, then the left sub-tree, followed by the right sub-tree.
    2. Post-order Traversal: The left sub-tree is visited first, then the right sub-tree, followed by the root node.
    3. In-order traversal: The left sub-tree is visited first, then the root node, followed by the right sub-tree.
  6. BFS traversal is when we visit the tree level-wise.
    User-defined Data structures in Python

Output:

Preorder traversal:
4 3 2 3 5 
Postorder traversal:
2 3 3 5 4 
Inorder traversal:
2 3 3 4 5
BFS traversal:
4 3 5 2 3
  • There is a type of Binary Tree called the BST or Binary Search Tree. There are three qualifications a binary tree must pass to become a BST:
  1. The values of the nodes in the left sub-tree must be less than the value of the root node.
  2. The values of the nodes in the right sub-tree must be greater than the value of the root node.
  3. Every sub-tree in the tree must also follow the BST property.

Here is an example BST:

User-defined Data structures in Python

5. Graphs

In short form, G = (V, E). Here V represents vertices, and E represents edges. A graph is a non-linear Data structure. It consists of nodes/ vertices joined/ connected by edges. Both vertices and edges have to be a finite set. An edge can be represented as (u, v) given u and v are the two vertices the edge connects.

A graph can be directed or undirected. In an undirected graph, E = (u, v) and E = (v, u) are the same, while in a directed graph, they are not the same as the directed matters. Hence, edges are represented as ordered pairs of vertices the edge joins.

User-defined Data structures in Python

Important points about graphs:

  1. The edges of a graph can have costs or weights.
  2. Networks in real-time are represented using Graphs.
  3. A graph can be implemented using:
    1. Incidence matrix
    2. Incidence List
    3. Adjacency Matrix
    4. Adjacency List
  4. It is the programmer's choice of how to implement the graph based on the need in the scenario.
  5. A graph can consist of cycles.
  6. For graph traversal, BFS and DFS techniques are used like in trees, but to avoid visiting the same vertex again and again in the case of cycles, we need to maintain an array of visited vertices not to visit them again.

Adjacency matrix: An adjacency matrix is a (V X V) 2D array where V represents the vertices in the graph. In the matrix, adj[u][v], if in the graph, there exists an edge between u and v, adj[u][v] = 1, else 0 is assigned.

  • In an undirected graph, if there exists an edge from u to v, adj[u][v] = 1 and adj[v][u] = 1 as there are no directions. Hence, the adjacency matrix of an undirected graph is always symmetrical.
  • In a directed graph, adj[u][v] is not equivalent to adj[v][u].
  • If the edges have weights are costs given, in the place of 1, we give the assigned weight/ cost in the matrix.
  • The disadvantage of this representation is that it takes more space-O(V2)

Here is the representation:

User-defined Data structures in Python
User-defined Data structures in Python

Here is a very simple code of adjacency matrix implementation for the graph:

User-defined Data structures in Python

Output:

0 3 0 12 4 
3 0 5 0 0 
0 5 0 0 2 
12 0 0 0 7 
4 0 2 7 0

Adjacency list:

To implement an adjacency list, we use an array/ list of linked lists to represent the vertices and edges in the graph. The number of linked lists used in the representation equals the number of vertices in the graph.

  • An array with length = no-of vertices is created, and for every vertex, we will create a linked list with all the adjacent vertices, and these linked lists will be arranged in the array.
  • In the case of a directed graph, all the nodes/ vertices we can travel to from the node in the array are linked in the linked list.
  • Simple, an adjacency list is an array of linked lists with adjacent nodes of the first node.

Here is the representation:

User-defined Data structures in Python
  • In the above adjacency list representation, the graph is undirected. Hence, each node's neighboring nodes in the graph are linked as separate linked lists.
User-defined Data structures in Python
  • This is a directed graph. Hence, for each node in the graph, adjacent/ neighboring nodes that we can direct from the node are linked.
  • Also, costs are given to every edge in the graph. Hence, the costs are also represented in the linked lists.

Here is a simple code with an adjacency list representation of a graph:

Output:

0: -> 2 -> 1

1: -> 4 -> 2 -> 3 -> 0

2: -> 4 -> 1 -> 0

3: -> 4 -> 1

4: -> 2 -> 3 -> 1

Understanding:

A list of the size number of vertices in the graph is created with all None values:

[None, None, None, None, None]

Now, when an edge(source, destination) call is made:

Using the class node, a destination node is created, and its next is pointed to the source in the array, and then the linked list is assigned to the source position in the array.

When edge(0, 1) is called:

[1 -> None, 0 -> None, None, None, None]

edge(0, 2):

[2 -> 1 -> None, 0 -> None, 0 -> None, None, None]

edge(1, 3):

[2 -> 1 -> None, 3 -> 0 -> None, 0 -> None, 1 -> None, None]

This way, all the adjacent nodes are attached to the linked lists in the array.






Latest Courses