Unit 1 DS
Unit 1 DS
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
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
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
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; }
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
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
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
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
Graphical Representation
15
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
18
19
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
21
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
22
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