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

Linked List Data Structure

The document discusses different data types including built-in and derived types. It also covers basic operations on data structures like traversing, searching, insertion and deletion. It then defines algorithms and explains time and space complexity analysis methods like Big O, Big Theta and Big Omega notations.

Uploaded by

Jessica Lasaria
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
20 views

Linked List Data Structure

The document discusses different data types including built-in and derived types. It also covers basic operations on data structures like traversing, searching, insertion and deletion. It then defines algorithms and explains time and space complexity analysis methods like Big O, Big Theta and Big Omega notations.

Uploaded by

Jessica Lasaria
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 18

Data Type

Data type is a way to classify various types of data such as integer, string, etc. which determines the values th

● Built-in Data Type


● Derived Data Type

Built-in Data Type


Those data types for which a language has built-in support are known as Built-in Data types.
For example, most of the languages provide the following built-in data types.

● Integers
● Boolean (true, false)
● Floating (Decimal numbers)
● Character and Strings

Derived Data Type

Those data types which are implementation independent as they can be implemented in one or the other way

● List
● Array
● Stack
● Queue

Basic Operations
The data in the data structures are processed by certain operations. The particular data
structure chosen largely depends on the frequency of the operation that needs to be performed
on the data structure.

● Traversing
● Searching
● Insertion
● Deletion
● Sorting
● Merging

What is an Algorithm ?
An algorithm is a finite set of instructions or logic, written in order, to accomplish a certain
predefined task. Algorithm is not the complete code or program, it is just the core
logic(solution) of a problem, which can be expressed either as an informal high level
description as pseudocode or using a flowchart.
Every Algorithm must satisfy the following properties:

1. Input- There should be 0 or more inputs supplied externally to the algorithm.


2. Output- There should be atleast 1 output obtained.
3. Definiteness- Every step of the algorithm should be clear and well defined.
4. Finiteness- The algorithm should have finite number of steps.
5. Correctness- Every step of the algorithm must generate a correct output.

An algorithm is said to be efficient and fast, if it takes less time to execute and consumes less
memory space. The performance of an algorithm is measured on the basis of following
properties :

1. Time Complexity
2. Space Complexity

Space Complexity
Its the amount of memory space required by the algorithm, during the course of its execution.
Space complexity must be taken seriously for multi-user systems and in situations where
limited memory is available.
An algorithm generally requires space for following components :

● Instruction Space: Its the space required to store the executable version of the program.
This space is fixed, but varies depending upon the number of lines of code in the program.
● Data Space: Its the space required to store all the constants and variables(including
temporary variables) value.
● Environment Space: Its the space required to store the environment information needed to
resume the suspended function.

To learn about Space Complexity in detail, jump to the Space Complexity tutorial.
Time Complexity
Time Complexity is a way to represent the amount of time required by the program to run till
its completion. It's generally a good practice to try to keep the time required minimum, so
that our algorithm completes it's execution in the minimum time possible. We will study
about Time Complexity in details in later sections.

Types of Asymptotic Notations


We use three types of asymptotic notations to represent the growth of any algorithm, as input
increases:

1. Big Theta (Θ)


2. Big Oh(O)
3. Big Omega (Ω)

Tight Bounds: Theta


When we say tight bounds, we mean that the time compexity represented by the Big-Θ
notation is like the average value or range within which the actual time of execution of the
algorithm will be.
For example, if for some algorithm the time complexity is represented by the expression 3n 2
+ 5n, and we use the Big-Θ notation to represent this, then the time complexity would be
Θ(n2), ignoring the constant coefficient and removing the insignificant part, which is 5n.
Upper Bounds: Big-O
This notation is known as the upper bound of the algorithm, or a Worst Case of an algorithm.
It tells us that a certain function will never exceed a specified time for any value of input n.
The question is why we need this representation when we already have the big-Θ notation,
which represents the tightly bound running time for any algorithm. Let's take a small example
to understand this.
Consider Linear Search algorithm, in which we traverse an array elements, one by one to
search a given number.
In Worst case, starting from the front of the array, we find the element or number we are
searching for at the end, which will lead to a time complexity of n, where n represents the
number of total elements.
But it can happen, that the element that we are searching for is the first element of the array,
in which case the time complexity will be 1.
Now in this case, saying that the big-Θ or tight bound time complexity for Linear search is
Θ(n), will mean that the time required will always be related to n, as this is the right way to
represent the average time complexity, but when we use the big-O notation, we mean to say
that the time complexity is O(n), which means that the time complexity will never exceed n,
defining the upper bound, hence saying that it can be less than or equal to n, which is the
correct representation.
Lower Bounds: Omega
Big Omega notation is used to define the lower bound of any algorithm or we can say the
best case of any algorithm.
This always indicates the minimum time required for any algorithm for all input values,
therefore the best case of any algorithm.
In simple words, when we represent a time complexity for any algorithm in the form of big-
Ω, we mean that the algorithm will take atleast this much time to cmplete it's execution. It
can definitely take more time than this too.

Space Complexity of Algorithms


Whenever a solution to a problem is written some memory is required to complete. For any
algorithm memory may be used for the following:

1. Variables (This include the constant values, temporary values)


2. Program Instruction
3. Execution

Space complexity is the amount of memory used by the algorithm (including the input values
to the algorithm) to execute and produce the result.

Sometime Auxiliary Space is confused with Space Complexity. But Auxiliary Space is the
extra space or the temporary space used by the algorithm during it's execution.

Space Complexity = Auxiliary Space + Input space

Linked list Data Structure


In this tutorial, you will learn about linked list data structure and it's implementation in Python, Java,
C, and C++.

A linked list is a linear data structure that includes a series of connected nodes. Here, each node
stores the data and the address of the next node. For example,

You have to start somewhere, so we give the address of the first node a special name called HEAD.
Also, the last node in the linked list can be identified because its next portion points to NULL.

Linked lists can be of multiple types: singly, doubly, and circular linked list. In this article, we will
focus on the singly linked list.
Representation of Linked List

Let's see how each node of the linked list is represented. Each node consists:

● A data item

● An address of another node

We wrap both the data item and the next node reference in a struct as:

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

Let us create a simple Linked List with three items to understand how this works.

/* Initialize nodes */

struct node *head;

struct node *one = NULL;

struct node *two = NULL;

struct node *three = NULL;

/* Allocate memory */

one = malloc(sizeof(struct node));

two = malloc(sizeof(struct node));

three = malloc(sizeof(struct node));

/* Assign data values */

one->data = 1;

two->data = 2;

three->data=3;

/* Connect nodes */
one->next = two;

two->next = three;

three->next = NULL;

/* Save address of first node in head */

head = one;

In just a few steps, we have created a simple linked list with three nodes.

Linked List Complexity


Time Complexity

Worst case Average Case

Search O(n) O(n)

Insert O(1) O(1)

Deletion O(1) O(1)

Singly Linked List


It is the most common. Each node has data and a pointer to the next node.

Singly linked list

Doubly Linked List


We add a pointer to the previous node in a doubly-linked list. Thus, we can go in either direction:
forward or backward.

Circular Linked List


A circular linked list is a variation of a linked list in which the last element is linked to the first
element. This forms a circular loop.

Stack data Structure


In this tutorial, you will learn about the stack data structure and its implementation in Python,
Java and C/C++.

A stack is a linear data structure that follows the principle of Last In First Out (LIFO). This
means the last element inserted inside the stack is removed first.

You can think of the stack data structure as the pile of plates on top of another.
Here, you can:

● Put a new plate on top


● Remove the top plate

And, if you want the plate at the bottom, you must first remove all the plates on top. This is
exactly how the stack data structure works.

LIFO Principle of Stack


In programming terms, putting an item on top of the stack is called push and removing an
item is called pop.
Basic Operations of Stack
There are some basic operations that allow us to perform different actions on a stack.

● Push: Add an element to the top of a stack


● Pop: Remove an element from the top of a stack
● IsEmpty: Check if the stack is empty
● IsFull: Check if the stack is full
● Peek: Get the value of the top element without removing it

Working of Stack Data Structure


The operations work as follows:

1. A pointer called TOP is used to keep track of the top element in the stack.
2. When initializing the stack, we set its value to -1 so that we can check if the stack is empty by
comparing TOP == -1.
3. On pushing an element, we increase the value of TOP and place the new element in the
position pointed to by TOP.
4. On popping an element, we return the element pointed to by TOP and reduce its value.
5. Before pushing, we check if the stack is already full
6. Before popping, we check if the stack is already empty
Applications of Stack Data Structure
Although stack is a simple data structure to implement, it is very powerful. The most
common uses of a stack are:

● To reverse a word - Put all the letters in a stack and pop them out. Because of the LIFO order
of stack, you will get the letters in reverse order.
● In compilers - Compilers use the stack to calculate the value of expressions like 2 + 4 / 5
* (7 - 9) by converting the expression to prefix or postfix form.
● In browsers - The back button in a browser saves all the URLs you have visited previously in
a stack. Each time you visit a new page, it is added on top of the stack. When you press the
back button, the current URL is removed from the stack, and the previous URL is accessed.

C Arrays

● An array is a variable that can store multiple values. For example, if you want to store
100 integers, you can create an array for it.

● int data[100];

How to declare an array?


dataType arrayName[arraySize];
For example,
float mark[5];
Here, we declared an array, mark, of floating-point type. And its size is 5. Meaning, it can
hold 5 floating-point values.
It's important to note that the size and type of an array cannot be changed once it is declared.

Access Array Elements


You can access elements of an array by indices.

Suppose you declared an array mark as above. The first element is mark[0], the second
element is mark[1] and so on.

How to initialize an array?


It is possible to initialize an array during declaration. For example,
int mark[5] = {19, 10, 8, 17, 9};
You can also initialize an array like this.
int mark[] = {19, 10, 8, 17, 9};

Change Value of Array elements


int mark[5] = {19, 10, 8, 17, 9}

// make the value of the third element to -1


mark[2] = -1;

// make the value of the fifth element to 0


mark[4] = 0;
input and Output Array Elements
Here's how you can take input from the user and store it in an array element.
// take input and store it in the 3rd element
scanf("%d", &mark[2]);

// take input and store it in the ith element


scanf("%d", &mark[i-1]);
Here's how you can print an individual element of an array.
// print the first element of the array
printf("%d", mark[0]);

// print the third element of the array


printf("%d", mark[2]);

// print ith element of the array


printf("%d", mark[i-1]);

Hashing Data Structure


Hashing is a technique or process of mapping keys, and values into the hash table by using

a hash function. It is done for faster access to elements. The efficiency of mapping

depends on the efficiency of the hash function used.

Let a hash function H(x) maps the value at the index x%10 in an Array. For example if

the list of values is [11,12,13,14,15] it will be stored at positions {1,2,3,4,5} in the array or

Hash table respectively.

What is Collision?

Since a hash function gets us a small number for a key which is a big
integer or string, there is a possibility that two keys result in the same
value. The situation where a newly inserted key maps to an already
occupied slot in the hash table is called collision and must be handled
using some collision handling technique.
How to handle Collisions?

There are mainly two methods to handle collision:

1. Separate Chaining
2. Open Addressing

Separate Chaining:

The idea behind separate chaining is to implement the array as a linked


list called a chain. Separate chaining is one of the most popular and
commonly used techniques in order to handle collisions.

The linked list data structure is used to implement this technique. So


what happens is, when multiple elements are hashed into the same slot
index, then these elements are inserted into a singly-linked list which is
known as a chain.

Let us consider a simple hash function as “key mod 7” and sequence of


keys as 50, 700, 76, 85, 92, 73, 101.
Advantages:

1. Simple to implement.
2. Hash table never fills up, we can always add more elements to
the chain.
3. Less sensitive to the hash function or load factors.
4. It is mostly used when it is unknown how many and how
frequently keys may be inserted or deleted.

Disadvantages:

1. Cache performance of chaining is not good as keys are stored


using a linked list. Open addressing provides better cache
performance as everything is stored in the same table.
2. Wastage of Space (Some Parts of hash table are never used)
3. If the chain becomes long, then search time can become O(n) in
the worst case.
4. Uses extra space for links.
Open Addressing:

Like separate chaining, open addressing is a method for handling


collisions. In Open Addressing, all elements are stored in the hash table
itself. So at any point, the size of the table must be greater than or equal
to the total number of keys (Note that we can increase table size by
copying old data if needed). This approach is also known as closed
hashing. This entire procedure is based upon probing. We will
understand types of probing ahead.

● Insert(k): Keep probing until an empty slot is found. Once an


empty slot is found, insert k.
● Search(k): Keep probing until slot’s key doesn’t become equal
to k or an empty slot is reached.
● Delete(k): Delete operation is interesting. If we simply delete a
key, then the search may fail. So slots of deleted keys are
marked specially as “deleted”.
The insert can insert an item in a deleted slot, but the search
doesn’t stop at a deleted slot.

n linear probing, the hash table is searched sequentially that starts from
the original location of the hash. If in case the location that we get is
already occupied, then we check for the next location.

he function used for rehashing is as follows: rehash(key) = (n+1)%table-


size. For example, the typical gap between two probes is 1 as seen in the
example below.
Let hash(x) be the slot index computed using a hash function and S be
the table size

Let us consider a simple hash function as “key mod 7” and a sequence


of keys as 50, 700, 76, 85, 92, 73, 101.

S.No
Separate Chaining Open Addressing
.

Chaining is Simpler to Open Addressing requires


1.
implement. more computation.
In chaining, Hash table never
In open addressing, table
2. fills up, we can always add
may become full.
more elements to chain.

Chaining is Less sensitive to Open addressing requires


3. the hash function or load extra care to avoid clustering
factors. and load factor.

Chaining is mostly used when


Open addressing is used
it is unknown how many and
4. when the frequency and
how frequently keys may be
number of keys is known.
inserted or deleted.

Open addressing provides


Cache performance of
better cache performance as
5. chaining is not good as keys
everything is stored in the
are stored using linked list.
same table.

Wastage of Space (Some Parts In Open addressing, a slot


6. of hash table in chaining are can be used even if an input
never used). doesn’t map to it.
Chaining uses extra space for
7. No links in Open addressing
links.

You might also like