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

Unit 1 DS

The document discusses linked lists and their advantages over arrays. It describes the basic components of linked lists including nodes and pointers. It provides examples of how to implement linked lists in C including creating node structures, inserting nodes, and deleting nodes from different positions in the list. It also covers more advanced linked list types like doubly linked lists and circular linked lists.

Uploaded by

Balaji Narayanan
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
69 views

Unit 1 DS

The document discusses linked lists and their advantages over arrays. It describes the basic components of linked lists including nodes and pointers. It provides examples of how to implement linked lists in C including creating node structures, inserting nodes, and deleting nodes from different positions in the list. It also covers more advanced linked list types like doubly linked lists and circular linked lists.

Uploaded by

Balaji Narayanan
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 25

Abstract Data Type A mathematical model, together with the different operations defined on that model Data Structure

e Collection of variables, possibly of different data types, connected in various ways Data structures are used to represent the mathematical model of an ADT Ad v a n t a g e s o f u s i n g ADT S
-teamwork

- prototyping - modular programming approach

Linked Lists
Evolution of Linked Lists
In real time systems most often the number of elements will not be known in advance. The major drawback of an array is that the number of elements must be known in advance. Hence an alternative approach was required. This give rise to the concept called linked lists. A linear list is a linear collection of data elements called nodes, where the linear order is given by means of pointers. The key here is that every node will have two parts: first part contains the information/data and the second part contains the link/address of the next node in the list. Memory is allocated for every node when it is actually required and will be freed when not needed. What is a Structure in C? Aggregate data types built using elements of other types. Example: struct Time { int hour; int minute; int second; }; Self-referential structure Self-referential structures contain pointers within the structs that refer to another identical structure. Linked lists are the most basic self-referential structures. Linked lists allow you to have a chain of structs with related data. Graphical Representation of Linked List A graphical representation of a node will look like

This means that for traversing through the list we need to know the starting point always. This is achieved by having a START pointer always pointing to the first element in the list. Memory organization of a Linked List

Programmatic representation of a node typedef struct node { int iData; struct node* pNext; }node; node* pSTART = NULL; The node structure contains the data and the pointer for the next node. The address of the next node must again be of type node and hence we have a pointer to the node within node itself. Such kind of structures which refers to itself are called as Re-Directions or Self Referential Structures. Inserting a node into a list Insertion logic varies depending on where in the list the element is going to be inserted, first, middle or at the end. In all situations it is as simple as making the pointer point to different nodes and hence insertion at any place will be much faster. Steps for inserting an element in the beginning of a list 1. Create a new node 2. Make the next part of new node point to the node pointed by START 3. Make the START pointer point to new node

Inserting a node in the beginning of a list void add_begin(int iData) { node* pNewNode = (node*)malloc(sizeof(node)); pNewNode->iData = iData; pNewNode->pNext = pSTART; pSTART = pNewNode; } Steps for inserting an element in the middle 1. Create a new node 2. Make the next part of new node point to the next of previous node 3. Make the previous node point to new node

Inserting a node in the middle of a list

void add_Middle(int iData, int iLoc) { int iPos = 1; node* pTempNode = NULL; node* pNewNode = NULL; if (iLoc == 1) { add_begin(iData); } else
3

{ for (pTempNode = pSTART; pTempNode->pNext != NULL && iPos < iLoc; pTempNode = pTempNode->pNext, iPos++); if (iPos == iLoc) { pNewNode = (node*)malloc(sizeof(node)); pNewNode->iData = iData; pNewNode->pNext = pTempNode->pNext; pTempNode->pNext = pNewNode; } } }

Steps for inserting an element in the end 1. Create a new node 2. Make the next of new node point to NULL 3. Make the previous node point to new node

Inserting a node at the end

void add_End(int iData) { int iPos = 0; node* pTempNode = NULL; node* pNewNode = NULL; for (pTempNode = pSTART; pTempNode->pNext != NULL; pTempNode = pTempNode->pNext); pNewNode = (node*)malloc(sizeof(node)); pNewNode->iData = iData; pNewNode->pNext = NULL; pTempNode->pNext = pNewNode; }

Steps for deleting a node in the beginning

The next step is deleting a node from a list. Again deletion logic varies depending on from where the node is getting deleted, beginning, middle or at the end. 1. Store the node to be deleted in a temporary pointer 2. Make START point to next node in the list 3. Delete the node pointed by temporary pointer
Steps for deleting a node from the middle / end 1. Store the node to be deleted in a temporary pointer 2. Make the previous nodes next point to the next of the node that is being deleted 3. Delete the node pointed by temporary pointer Deleting a node from the middle

Deleting a node at the end

void delete(int iData) { node* pTempNode = NULL; node* pDelNode = NULL; node* pPrevNode = NULL; for(pTempNode = pPrevNode = pSTART; pTempNode != NULL; pPrevNode = pTempNode, pTempNode = pTempNode->pNext) { if (iData == pTempNode->iData)

{ pDelNode = pTempNode; pPrevNode->pNext = pDelNode->pNext; if (pDelNode == pSTART) { pSTART = pSTART->pNext; } free(pDelNode); } } } Applications of Linked Lists A linked list is used typically in computer applications for memory allocations. The system keeps tracks of all the memory available in the system with the help of a list. This area of memory is called as the memory pool. As and when the user requests for memory the system allocates memory to the user from this available pool. Once allocated these blocks will be deleted from the available list. Once the user frees the memory again the memory will be added to the list. In general linked lists are used in almost all places whenever a collection of elements are required and when the number of elements in the collection is not known in advance. Advantages The number of elements in the linked lists need not be known in advance. As and when required elements can be added to a list Insertions and deletions are much faster in a linked list as there is no physical movement of elements of a list. Limitations Searching a list is always sequential and hence it is a time consuming operation Traversal is always possible in only one direction.

Circular Linked Lists Circular linked lists are very similar to a linear list except that the last node will be pointing to the first node again instead of NULL. So care should be taken while doing the traversal to avoid infinite loops. A graphical representation of a circular linked list is as follows

Assuming that someNode is some node in the list, this code iterates through that list starting with someNode: Forwards node := someNode do <do something with node.value> node := node.next while node not someNode
6

Backwards node := someNode do <do something with node.value> node := node.prev while node not someNode

Doubly Linked Lists


With the linked list traversal is possible in only one direction. This limitation is overcome with the help of doubly linked lists. A doubly linked list is a list in which each node will have 3 parts: one part containing the information / data, one part containing the pointer/address of next node and one part containing the pointer / address of previous node. In addition to the START pointer, which contains the first node there will also be a TAIL pointer pointing to the last node in the list. Memory Organization Of A Doubly Linked List

Programmatic Representation of a node in Doubly Linked Lists typedef struct node { int iData; struct node* pNext; struct node* pPrev; }node; node* pSTART = NULL, *pTAIL = NULL; The node of a doubly linked list will have 2 pointers apart from the data, one for pointing to the previous element and the other for pointing to the next element in the list. There will also be a TAIL pointer pointing to the last node in the list. It is possible for us to traverse from the beginning of a list till the end or from the end of the list to the beginning. The end of the list is identified with the help of a NULL value in the next / previous parts. Inserting an element in a doubly linked list Inserting an element in a doubly linked list involves just changing the pointers unlike arrays where every element after the inserted element needs to be shifted once. Hence inserting an element in a doubly linked list is much faster than inserting a node in an array. Steps for inserting in the beginning of a doubly linked list 1. Create a new node 2. Make the next part of new node equal to START 3. Make the previous part of new node equal to NULL
7

4. Make the previous part of the node pointed by START to new node 5. Make START point to new node

node* getNode(int iData) { node* pNewNode = (node *)malloc(sizeof(node)); pNewNode->iData = iData; pNewNode->pNext = NULL; pNewNode->pPrev = NULL; } void addHead(int iData) { node* pNewNode = getNode(iData); pNewNode->pNext = pSTART; pSTART->pPrev = pNewNode; pSTART = pNewNode; } Steps for inserting an element in the middle of a doubly linked list 1. Create a new node 2. Make the next part of new node equal to next part of the previous node 3. Make the previous part of new node equal to previous part of next node 4. Make the next part of previous node point to new node
8

5. Make the previous part of next node point to new node

void insertAt(int iLoc, int iData) { node* pNewNode = NULL; node* pTempNode = NULL; int iPos = 1; if (iLoc == 1) { addHead(iData); } else { for(pTempNode = pSTART; pTempNode->pNext != NULL && iLoc < iPos; pTempNode = pTempNode->pNext, iPos++); if (iLoc == iPos) { pNewNode = getNode(iData); pNewNode->pNext = pTempNode->pNext; pNewNode->pPrev = pTempNode; if (pTempNode->pNext != NULL) { pTempNode->pNext->pPrev = pNewNode; } else // If it is the last node { pTAIL = pNewNode; } pTempNode->pNext = pNewNode; } else { printf(Invalid Position); } } }

Steps for inserting an element at the end of a doubly linked list 1. Create a new node 2. Make the next part of the new node equal to NULL 3. Make the previous part of the new node equal to TAIL
9

4. Make the next part of the previous node equal to new node 5. Make TAIL equal to new node

void addTail(int iData) { node* pNewNode = NULL; node* pTempNode = NULL; pNewNode = getNode(iData); pNewNode->pPrev = pTAIL; pTail->pNext = pNewNode; pTAIL = pNewNode; } Deleting an element from a doubly linked list Deleting a node from a list is as simple as changing the links. Hence deleting a node from a list is much faster when compared to arrays. Like insertion the deletion logic also varies depending on from where in the list we are going to delete the node. Deletion in the beginning of a doubly linked list 1. Make the temporary pointer point to the node to be deleted 2. Make the START point to the next node of START
10

3. Make the previous of the next node equal to previous of the node to be deleted 4. Delete the node pointed to by temporary pointer

void removeHead() { node* pDelNode = NULL; pDelNode = pSTART; pSTART = pSTART->pNext; if (pDelNode == pTAIL) // If it is the last element in the list { pTAIL = NULL; } else { pSTART->pPrev = NULL; } free(pTempNode); } Deletion in the middle of a doubly linked list 1. Make the temporary pointer point to the node to be deleted 2. Make the next part of the previous node equal to next of the node to be deleted 3. Make the previous part of the next node equal to previous part of the node to be deleted 4. Delete the node pointed to by temporary pointer

11

void deleteAt(int iLoc) { node* pTempNode = NULL; node* pDelNode = NULL; int iPos = 1; if (iLoc == 1) { removeHead(); } else { for (pTempNode = pSTART; iPos < iLoc && pTempNode->pNext != NULL; pTempNode = pTempNode->pNext, iPos++); if (iLoc == iPos) { pDelNode = pTempNode->pNext; pTempNode->pNext = pDelNode->pNext; if (pDelNode == pTAIL) { pTAIL = pTAIL->pNext;
12

} else { pDelNode->pNext->pPrev = pTempNode; } free(pDelNode); } } } Deletion at the end of a doubly linked list 1. Make the temporary pointer point to the node to be deleted 2. Make the next part of the previous node equal to next of the node to be deleted 3. Make the TAIL equal to the previous part of the node to be deleted 4. Delete the node pointed to by temporary pointer

void removeTail() { struct node* pTempNode = NULL; struct node* pDelNode = NULL; if (pSTART == NULL) { printf(List is Empty); }

13

else { for (pTempNode = pSTART; pTempNode->pNext != NULL; pTempNode = pTempNode->pNext); pDelNode = pTempNode->pNext; pTempNode->pNext = NULL; pTAIL = pTAIL->pNext; if (pTAIL == NULL) { pSTART = NULL; } free(pDelNode); } } Traversing a doubly linked list A doubly linked list can be traversed in both directions. void displayFromStart() { node* pTempNode = NULL; for(pTempNode = pSTART; pTempNode != NULL; pTempNode = pTempNode>pNext) { printf(Data = %d\n, pTempNode->iData); } } void displayFromTail() {
14

node* pTempNode = NULL; for(pTempNode = pTAIL; pTempNode != NULL; pTempNode = pTempNode>pPrev) { printf(Data = %d\n, pTempNode->iData); } } Application of Doubly Linked Lists A doubly linked list can be used in all the places where we use a linear list if the traversal happens frequently in both the directions.

Advantages The number of elements in a doubly linked list need not be known in advance. As and when required elements can be added to a list Insertions and deletions are much faster as there is no physical movement of elements of a list. Traversal is possible in both the directions unlike a linear list where the traversal is possible in only one direction

Limitations Takes up additional memory for storing the previous pointer in each node Makes the insertion and deletion logic a bit complex

Stack ADT Introduction to Stacks


A stack is an ordered list in which items are inserted and removed at only one end called the TOP. There are only 2 operations that are possible on a stack. They are the Push and the Pop operations. A Push operation inserts a value into the stack and the Pop operation retrieves the value from the stack and removes it from the stack as well. An example for a stack is a stack of plates arranged on a table. This means that the last item to be added is the first item to be removed. Hence a stack is also called as Last-InFirst-Out List or LIFO list.

Graphical Representation
15

A graphical representation of a stack is shown below:

Memory Organization of a Stack

The memory organization of stack is very similar to that of a linear list

Programmatic representation of a stack typedef struct node { int iData; struct node* pNext; }node; node* pTop = NULL; The node of a stack is very similar to the node in a list. The difference is only in the way the data is organized. As discussed there are only 2 operations permitted on a stack: the Push and the Pop.
Push Operation A push operation is for inserting an element in to a stack. Elements are always inserted in the beginning of a stack. Steps for the push operation 1. Create a new node 2. Make the new nodes next point to the node pointed by Top 3. Make the top point to the new node Push Operation of a Stack

16

void push(int iData) { node* pNewNode = (node *)malloc(sizeof(node)); pNewNode->iData = iData; pNewNode->pNext = pTop; pTop = pNewNode; } If you had a careful look over this code this is nothing but equal to inserting an element at the end of a list. Pop Operation A pop operation is basically used for retrieving a value from the stack. This also removes the element from the Stack.

int pop() { int iData; node* pTempNode = NULL; if (pTop == NULL) { iData = -1; printf(Stack Empty); } else { pTempNode = pTop; iData = pTop->iData; pTop = pTop->pNext; free(pTempNode); }
17

return iData; } Application of Stacks

18

19

Evaluation of postfix expression

Evaluating Expressions In normal practice any arithmetic expression is written in such a way that the operator is placed between its operands. For example (A + B) * (C + D) Such kind of expressions is called as infix notation. Polish notation refers to the notation in which the operator is placed before the operands. For example the above expression can be written as *+AB+CD The idea is, whenever we write expressions in this notation, parenthesis are not required for determining the priority of the expressions. Let us see the steps involved in the conversion (A+B)*(C+D) = (+AB)*(+CD) = *+AB+CD Reverse polish notation is exactly the opposite of polish notation i.e. the operator is always placed after the operands. For example, the above expression can be written in Reverse Polish Notation as (A+B) * (C+D) = (AB+) * (CD+) = AB+CD+* Whenever an expression is evaluated by the system it usually performs it by means of 2 steps. First it converts any expression into prefix or postfix notation and then it evaluates the expression as it makes the job much simpler.
20

Converting an infix expression to postfix or prefix expression makes use of stacks extensively. The following is the algorithm for doing this conversion. A complete program requires a lot more than what is described in the algorithm. PostFixExpression ConvertToPolishNotation(InfixExpression) { 1. Push ( onto the STACK and add ) to the end of the InfixExpression 2. Scan the InfixExpression from left to right and repeat steps 3 to 6 for each element of InfixExpression until the stack is empty 3. If an operand is encountered, add it to Postfix Expression 4. If a left parenthesis is encountered, push it to STACK 5. If an operator XX is encountered a. Pop from STACK repeatedly and add it to Postfix expression which has the same / higher precedence than XX. b. Add XX to STACK 6. If a right parenthesis is encountered, then a. Pop from STACK repeatedly and add it to Postfix Expression until a left parenthesis is encountered. b. Remove the left parenthesis 7. Exit

Infix to postfix Examples

Conversion of infix to postfix examples

21

Conversion of infix to prefix examples

Queues
A Queue is an ordered list in which all insertions can take place at one end called the rear and all deletions take place at the other end called the front. The two operations that are possible in a queue are Insertion and Deletion. A real time example for a queue is people standing in a queue for billing on a shop. The first person in the queue will be the first person to get the service. Similarly the first element inserted in the queue will be the first one that will be retrieved and hence a queue is also called as First In First Out or FIFO list.

Graphical Representation

Memory Organization of a Queue

22

Programmatic representation of a Queue


The node of a queue will have 2 parts. The first part contains the data and the second part contains the address of the next node. This is pretty much similar to a linear list representation. But in a queue insertions and deletions are going to occur on two different ends. If we have only one pointer called START then for every insertion we need to traverse the complete queue as insertions are always on the end and hence will be time consuming. To avoid this we are going to have 2 pointers to represent a queue, one pointer is called the FRONT which will always be pointing to the first element and is used for deletions, and the other pointer called REAR which will always be pointing to the last element in the queue and is used in insertions. typedef struct node { int iData; struct node* pNext; }node; node* pFRONT = NULL, *pREAR = NULL; When the queue is empty, both FRONT and REAR will be pointing to NULL. When there is only one element in the queue, then both FRONT and REAR will be pointing to the same element. Steps for inserting an element into a Queue Inserting into a queue can happen only at the REAR end. 1. Create a new node 2. Make the next of new node as NULL as it is the last element always 3. If the queue is empty, then make FRONT point to new node 4. Otherwise make the previous nodes next ( the node pointed by REAR is always the previous node ) point to this node 5. Make Rear point to new node

Inserting a node in a queue void QInsert(int iData) { node* pTempNode = getNode(iData); if ( pFRONT == NULL ) { //If the queue is empty pFRONT = pTempNode; }
23

else { pREAR->pNext = pTempNode; } pREAR = pTempNode; } Steps for deleting an element from the queue The deletion is the only way through which we can retrieve values from a queue. Along with retrieving the value this will remove the entry from the queue. Deletions always happen in the FRONT end. 1. Store the node pointed by FRONT in a temporary pointer 2. Make Front as Fronts next 3. Delete the node pointed by temporary pointer 4. If all the nodes are deleted from the queue make the FRONT and REAR as NULL

Deleting a node in a queue int QDelete() { int iData = -1; node* pDelNode = NULL; if (pFRONT == NULL) { printf(Queue Empty); } else { iData = pFRONT->iData; pDelNode = pFRONT; pFRONT = pFRONT->pNext; if (pFRONT == NULL) { pREAR = NULL; } free(pTempNode); } return iData;
24

Deque A Deque is a queue in which insertions and deletions can happen at both ends of a queue. A deque, or double-ended queue is a data structure, which unites the properties of a queue and a stack. Like the stack, items can be pushed into the deque; once inserted into the deque the last item pushed in may be extracted from one side (popped, as in a stack), and the first item pushed in may be pulled out of the other side (as in a queue).
Implementation of Deque The push (insert/assign) and pop operation is done at both the end that is start and end of the deque. The following pictures show how a deque is formed based on this change in algorithm. Initially the base and end pointer will be pointing to NULL or 0 (zero).

We define two pointers, p_base and p_end to keep track of front and back of the deque. Initially when the deque object is created, both p_base and p_end would point to NULL.

When the first node is created, p_base assumes the position and p_end starts pointing to p_base. The next and previous pointer of p_base assumes NULL or 0(zero).

25

You might also like