0% found this document useful (0 votes)
21 views19 pages

Data Structure

Uploaded by

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

Data Structure

Uploaded by

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

DATA STRUCTURE

UNIT – 4
Singly Linked List definition & meaning DSA
A singly linked list is a special type of linked list in which each node has only one link that
points to the next node in the linked list.

Singly linked list


Characteristics of a Singly Linked List:
 Each node holds a single value and a reference to the next node in the list.
 The list has a head, which is a reference to the first node in the list. We can access all
items of a list using the head node. Sometimes we also separately maintain tail of
linked list to quickly access the last node. For example, in queue implementation of
the linked list, we prefer to quickly insert at the end.
 The nodes are not stored in a contiguous block of memory, but instead, each node
holds the address of the next node in the list.
 Accessing elements in a singly linked list requires traversing the list from the head to
the desired node, as there is no direct access to a specific node in memory.
Linked List Operations: Traverse, Insert and Delete
There are various linked list operations that allow us to perform different actions on linked
lists. For example, the insertion operation adds a new element to the linked list.
Here's a list of basic linked list operations that we will cover in this article.
 Traversal - access each element of the linked list
 Insertion - adds a new element to the linked list
 Deletion - removes the existing elements
 Search - find a node in the linked list
 Sort - sort the nodes of the linked list
Before you learn about linked list operations in detail, make sure to know about Linked
List first.
Things to Remember about Linked List
 head points to the first node of the linked list
 next pointer of the last node is NULL, so if the next current node is NULL, we have
reached the end of the linked list.
In all of the examples, we will assume that the linked list has three nodes 1 --->2 --->3 with
node structure as below:
struct node {
int data;
struct node *next;
};

Traverse a Linked List


Displaying the contents of a linked list is very simple. We keep moving the temp node to the
next one and display its contents.
When temp is NULL, we know that we have reached the end of the linked list so we get out
of the while loop.
struct node *temp = head;
printf("\n\nList elements are - \n");
while(temp != NULL) {
printf("%d --->",temp->data);
temp = temp->next;
}
The output of this program will be:
List elements are -
1 --->2 --->3 --->

Insert Elements to a Linked List


You can add elements to either the beginning, middle or end of the linked list.
1. Insert at the beginning
 Allocate memory for new node
 Store data
 Change next of new node to point to head
 Change head to point to recently created node
struct node *newNode;
newNode = malloc(sizeof(struct node));
newNode->data = 4;
newNode->next = head;
head = newNode;
2. Insert at the End
 Allocate memory for new node
 Store data
 Traverse to last node
 Change next of last node to recently created node
struct node *newNode;
newNode = malloc(sizeof(struct node));
newNode->data = 4;
newNode->next = NULL;

struct node *temp = head;


while(temp->next != NULL){
temp = temp->next;
}

temp->next = newNode;
3. Insert at the Middle
 Allocate memory and store data for new node
 Traverse to node just before the required position of new node
 Change next pointers to include new node in between
struct node *newNode;
newNode = malloc(sizeof(struct node));
newNode->data = 4;

struct node *temp = head;

for(int i=2; i < position; i++) {


if(temp->next != NULL) {
temp = temp->next;
}
}
newNode->next = temp->next;
temp->next = newNode;

Delete from a Linked List


You can delete either from the beginning, end or from a particular position.
1. Delete from beginning
 Point head to the second node
head = head->next;
2. Delete from end
 Traverse to second last element
 Change its next pointer to null
struct node* temp = head;
while(temp->next->next!=NULL){
temp = temp->next;
}
temp->next = NULL;
3. Delete from middle
 Traverse to element before the element to be deleted
 Change next pointers to exclude the node from the chain
for(int i=2; i< position; i++) {
if(temp->next!=NULL) {
temp = temp->next;
}
}

temp->next = temp->next->next;

Search an Element on a Linked List


You can search an element on a linked list using a loop using the following steps. We are
finding item on a linked list.
 Make head as the current node.
 Run a loop until the current node is NULL because the last element points to NULL.
 In each iteration, check if the key of the node is equal to item. If it the key matches
the item, return true otherwise return false.
// Search a node
bool searchNode(struct Node** head_ref, int key) {
struct Node* current = *head_ref;

while (current != NULL) {


if (current->data == key) return true;
current = current->next;
}
return false;
}
What is a Linked List?
Before we start to discuss, we need to formulate a clear understanding of what a linked list
is. A collection structure represents a sequence of nodes. But, wait! ✋ What does node
mean? 🤔 An object that contains value and pointer with reference to stores the address for
the next element into the sequence of the list, as you can see in the following figure:
Figure 1: Linked List representation.
Actually, you can imagine a pointer, as a place where you can find and obtain the stored
value in the node, is a reference to a location in memory. The first node in the list represent a
head and has a pointer to the next element, and as you can imagine the last node is the tail
because has a null pointer to the next node.
You also can use a pointer to the previous object. As a result, a Doubly Linked List type is
created.
Another important aspect to understand linked list is related to the efficient memory
utilization. Is not necessary to pre-allocate memory, as a consequence you can add as much
items you want in the list. However, some problems can show up if is required more memory
than you can have, because each node has a pointer and other memory for itself.
Terminology
As you can see in the image in the section above, we define two properties:
 value: Element that holds the data.
 next: Point to the next node.
 prev (optional): Can be used to point to the previous node. You can see more about in
Doubly Linked List Structure.
Let's begin!
Now that we are on the same page with the concepts, let’s start the discussion more deeply
about Linked List methods, translate the concepts into to our code, and finally implement our
data structure. At the beginning, we are going to focus in the Linked List, because it is the
most common and simplest data structure linear collection of data elements.

Let's start to work! 😃


◼️Singly Linked List
Is called as singly because a node only hold a reference to the next element of the sequence
and you cannot access previous elements because it does not store any pointer or reference
to the previous node, as you can see in the figure.
Figure 2: A singly linked list that contain an element and a pointer to the next node
Before we describe the operations, we need to define the most important part in our code
that will help us to build the linear list structure, the node class.
class Node {
constructor(value, next) {
this.value = value;
this.next = next;
}
}
Our main class only has a reference to the value and the next node, pretty simple, right? So,
let’s move on and define the Linked List class, which has the head property that point to the
first element into the list, other property we have to declared is the size, which give to us the
number of nodes that exist into our list.
class LinkedList {
constructor() {
this.head = null;
this.length = null;
}
}
Okay, continuing the discussion we have to add methods to our class. Let’s check out:
 addAtHead: Our first method is used to add a new element at the beginning of our
data structure. This method has a constant running time (O(1)). But what does it
mean? 🧐 It means that it takes the same amount of time to add a value in the list, is
a constant time. In this case is necessary only to move one time to add a new
element in the first position into the list. As result, we need to update only the current
head that will be pointing to the new item that we are going to be creating. Here’s
how it should be:
addAtHead(value){
if(linkedList.head){
var newNode = new Node(value, this.head );
this.head = newNode;
}else{
var newNode = new Node(value, null);
this.head = newNode;
}
this.length++;
}
 removeAtHead: If we want remove one element from the head all we have to do is
replace the head by the following element. Like the method before the constant
running time is O(1).
removeAtHead(value){
if(this.head){
var newHead = this.head.next;
this.head = newHead;
this.length--;
}else{
return false;
}
}
 search: If we are looking for a specific item? Do not be hurry; we only need iterate
the list until the end to find the element in the list. But imagine the following
scenario: We have a list with 1000 items and we are looking for the 999 item. Can
you guess what can happen? If we want to get some specific value or node at
position N then we have to moving the pointer throw the entire list to find it. This can
cause a problem with the access time.
search(value){
if(this.head){
var node = this.head;
var count = 0;
while(node != null && node.value != value){
if(count >= this.length && node.value != value){
return false;
}
node = node.next;
count++;
}
if(node == null){
return false;
}else{
return true;
}
}else{
return false;
}
}
There are others functions like getAtIndex, addAtIndex, removeAt and reverse that I would
like to discuss, but they have similar logic applies as the previous methods described before,
so I’ll skip the explanation of them to not waste your time.
⚡️But if you would like to know how I implemented, you can access all the code just
clicking here.
__
◼️Doubly Linked List
As I mentioned earlier, the Doubly Linked List is a structure that has capacity to pointer to the
previous node, which is the biggest difference comparing with the Singly List. Now we gain
the power to move traversed backward in the list. For instance, each node has a pointer to
the previous element, allowing you to move through the list from the tail, as show in the
picture below.
As Uncle Ben said to Peter Parker, “with great power comes great responsibility”. As
consequence, is required more space to store the addresses of previous elements instead just
one to the next element in list, so takes two more memory comparing with the singly
structure.
Besides that, mostly all functions and behaviors are quite similar with the Singly List. With
basic understanding of Linked List, it is so easy to build and extend functionality to make it a
Double List. So easy, right? 😁 You can feeling that we are having progress. 💪
Figure 3: A doubly linked list with pointer to the previous element
Even though the behavior is similar, we need to update the Singly List functions such
as addAtHead, removeAtHead, search and others to consider the previous property.
Besides these functions, we have new weapons to use here, as you can see below:
 addAtTail: We define a new element at the bottom of the list and point the last
element as the tail. Can you imagine the constant running time?
addAtTail(value){
var newNode = new Node(value, null, this.tail);
if(this.tail){
this.tail.next = newNode;
this.tail = newNode;
}else{
this.head = newNode;
this.tail = newNode;
}
this.length++;
}
 removeAtTail: Here the last item from the list is set to the null value. As a result, the
final element become the previous element of the last element.
removeAtTail(){
if(this.length === 1){
this.removeAtHead();
this.tail = null;
return;
} else if (this.length > 1){
this.tail = this.tail.prev;
this.tail.next = null;
this.length--;
return;
}
return false;
}
◼️Circular Linked List
The only difference between the doubly Linked List is the fact that the tail element is linked
with the first element in the list. As a result, a loop was created and now we can move
forward and back-forward into the entire list.

Figure 4: Circular linked list that contain a link between the first and last element.
Now we will use the entire acknowledgement that we learned to implement two new data
structure.
◼️Queue
The First-In-First-Out (FIFO) is an example of a linear data structure where the first element
added to the queue will be the first to be removed. For instance, you can visualize this
behavior where you are in a queue in a store, bank or supermarket.
A new element is added to the end of the list by the enqueuer (addFromTail) function and
removed from the top of the list using the dequeue (removeFromTail) function. You can see
other people or find in a book referencing the queue as removing or poling method, for me I
prefer only dequeue. Other common operation in this structure is the peek that return the
item at the top of the stack as peek.
However, when should I use these structure data? 🤔 It is suggested to use Queue when the
order matter, like a queueing system for requests.

Figure 5: Representation of a Queue.


◼️Stack
Known as LIFO (last in, first out) data structure, you can visualize understanding how it works
making an analogy when a set of items is stacked on top of each other, creating a pile of
books.
Like I said before, this structure has some similarities from Linked List and you can use
addFromTail (Push) and removeFromTail (Pop) operations in your stack structure. Just like a
queue, the operation that return an item at the top of the stack is called as peek.
You can find this structure in mechanisms in text editors, compiler syntax checking or also on
a graph.

Figure 6: A representation of a stack and the Push and Pop functions.

◼️Time Complexity
You can see the time complexity in the image below, where n is the length of Linked List.

Figure 7: The time complexity.


Let’s create an example by adding some values in the head and then removing in a Linked
List using addAtHead and removeAtHead functions. In addition, using the time() object in
Javascript will allowed us to time and analyzes the performance of our code, as the follow
figure:
Figure 8: Output after insert and remove some values in the Singly Linked List.
As you can see, we add some values in the list that show us how faster it is. Seeing the values
we can realize that the execution time become a constant. The image below show the plot
using Python with the Panda DataFrame library.

Figure 9: The consume time between addAtHead and removeAtHead functions.


Tree Data Structure
A tree is a nonlinear hierarchical data structure that consists of nodes connected by edges.

A Tree
Why Tree Data Structure?
Other data structures such as arrays, linked list, stack, and queue are linear data structures
that store data sequentially. In order to perform any operation in a linear data structure, the
time complexity increases with the increase in the data size. But, it is not acceptable in
today's computational world.
Different tree data structures allow quicker and easier access to the data as it is a non-
linear data structure.

Tree Terminologies
Node
A node is an entity that contains a key or value and pointers to its child nodes.
The last nodes of each path are called leaf nodes or external nodes that do not contain a
link/pointer to child nodes.
The node having at least a child node is called an internal node.
Edge
It is the link between any two nodes.

Nodes and edges of a tree


Root
It is the topmost node of a tree.
Height of a Node
The height of a node is the number of edges from the node to the deepest leaf (ie. the
longest path from the node to a leaf node).
Depth of a Node
The depth of a node is the number of edges from the root to the node.
Height of a Tree
The height of a Tree is the height of the root node or the depth of the deepest node.
Height and depth of each node in a tree
Degree of a Node
The degree of a node is the total number of branches of that node.
Forest
A collection of disjoint trees is called a forest.

Creating forest from a tree


You can create a forest by cutting the root of a tree.

Types of Tree
1. Binary Tree
A binary tree is a tree data structure in which each parent node can have at most two
children. Each node of a binary tree consists of three items:
 data item
 address of left child
 address of right child

Binary Tree
1. Full Binary Tree
A full Binary tree is a special type of binary tree in which every parent node/internal node
has either two or no children.

Full Binary Tree


To learn more, please visit full binary tree.
2. Perfect Binary Tree
A perfect binary tree is a type of binary tree in which every internal node has exactly two
child nodes and all the leaf nodes are at the same level.

Perfect Binary Tree


To learn more, please visit perfect binary tree.
3. Complete Binary Tree
A complete binary tree is just like a full binary tree, but with two major differences
1. Every level must be completely filled
2. All the leaf elements must lean towards the left.
3. The last leaf element might not have a right sibling i.e. a complete binary tree
doesn't have to be a full binary tree.

Complete Binary Tree


To learn more, please visit complete binary tree.
4. Degenerate or Pathological Tree
A degenerate or pathological tree is the tree having a single child either left or right.
Degenerate Binary Tree
5. Skewed Binary Tree
A skewed binary tree is a pathological/degenerate tree in which the tree is either dominated
by the left nodes or the right nodes. Thus, there are two types of skewed binary tree: left-
skewed binary tree and right-skewed binary tree.

Skewed Binary Tree


6. Balanced Binary Tree
It is a type of binary tree in which the difference between the height of the left and the right
subtree for each node is either 0 or 1.
Balanced Binary Tree
To learn more, please visit balanced binary tree.

Binary Tree Representation


A node of a binary tree is represented by a structure containing a data part and two pointers
to other structures of the same type.
struct node
{
int data;
struct node *left;
struct node *right;
};

Binary Tree Representation


Binary Tree Applications
 For easy and quick access to data
 In router algorithms
 To implement heap data structure
 Syntax tree
2. Binary Search Tree
Binary search tree is a data structure that quickly allows us to maintain a sorted list
of numbers.
 It is called a binary tree because each tree node has a maximum of two children.
 It is called a search tree because it can be used to search for the presence of a
number in O(log(n)) time.
The properties that separate a binary search tree from a regular binary tree is
1. All nodes of left subtree are less than the root node
2. All nodes of right subtree are more than the root node
3. Both subtrees of each node are also BSTs i.e. they have the above two properties

AVL Tree
3. B-Tree

Tree Traversal
In order to perform any operation on a tree, you need to reach to the specific node. The
tree traversal algorithm helps in visiting a required node in the tree.
To learn more, please visit tree traversal.

Tree Applications
 Binary Search Trees(BSTs) are used to quickly check whether an element is present
in a set or not.
 Heap is a kind of tree that is used for heap sort.
 A modified version of a tree called Tries is used in modern routers to store routing
information.
 Most popular databases use B-Trees and T-Trees, which are variants of the tree
structure we learned above to store their data
 Compilers use a syntax tree to validate the syntax of every program you write.

You might also like