Programming Lists, Stacks, Queues
Summary Linked lists Create and insert elements Iterate over all elements of the list Remove elements Doubly Linked Lists Circular Linked Lists Stacks Operations and implementation using linked lists Queues Operations and implementation using linked lists 2
Linked Lists Definition: A dynamic data structure that consists of a sequence of records where each element contains a link to the next record in the sequence Linked lists can be singly linked, doubly linked or circular. For now, the focus will be on singly linked list Every node has a payload and a link to the next node in the list The start (head) of the list is maintained in a separate variable End of the list is indicated by NULL (sentinel) Next: circular list, stack, queue, etc 3
Arrays vs Linked Lists Although storing the items of S in an array A seems to be an obvious idea, it has a severe problem: Inserting an item into A exceeds the size of A, which necessitates creating another array B with size S + 1, and copying all the items in A to B This incurs more than O(1) time linked-list array size dynamic fixed indexing O(n) O(1) inserting O(1) O(n) deleting O(1) O(n) 4
Linked List Definition typdef struct type payload; Node *next; Node; Node *head; /*Head pointer references the first node in the linked list.*/ List of integers: 5
Linked Lists Considerations Items of list are usually of the same type Generally obtained from malloc/calloc Each item points to next item Last item points to null Need head to point to first item! If head is NULL, the linked list is empty Payload of item may be almost anything A single member or multiple members Including struct, union, char * or other pointers Arrays of fixed size at compile time 6
Usage of Linked Lists Not massive amounts of data Linear search is okay Sorting not necessary Or sometimes not possible Need to add and delete data on the fly Even from middle of list Linked list is able to grow in size as needed Does not require the shifting of items during insertions and deletions Items often need to be added to or deleted from the header/tail of the list 7
Manual List Manipulation Creating two nodes which are connected via a pointer #include <stdio.h> typedef struct int element; Node *next; Node; int main(int argc, char **argv) Node n1, n2; n1.element = 1; n1.next = &n2; n2.element = 2; n2.next = NULL; return 0; 8
Linked List Typical Operations 9
Creating a Node of the Linked List An element to be inserted in the list must be created dynamically Node* node_alloc(int data) Node* p=(node*)malloc(sizeof(node)); if ( p!= NULL) p >element=data; p >next=null; return p; 10
Iterating Over all Elements of the List Reference a node member with the -> operator p->element; A traverse operation visits each node in the linked list A pointer variable cur keeps track of the current node for ( cur = head; cur! = NULL ; cur = cur >next ) printf("%d\n", cur->element); 11
Adding an Item to the Head of a List #include <stdio.h> #include <stdlib.h> #include <assert.h> typedef struct int element; Node *next; Node; // add node with payload e to front of list head // and return pointer to new node at front of list Node *InsertHead(Node *head, int e) Node *newptr = (Node *) malloc(sizeof(node)); assert(newptr!= NULL); // crashes if false newptr->element = e; newptr->next = head; return newptr; int main(int argc, char **argv) Node *list = NULL; list = InsertHead(list, 1); list = InsertHead(list, 2); 12 return 0;
Inserting an Element in the Middle of the List To insert a node between two nodes newptr->next = cur; prev->next = newptr; 13
Inserting an Element at the End Inserting at the end of a linked list is NOT a special case if cur is NULL (last element points to null) newptr->next = cur; prev->next = newptr; 14
Where to Insert? Determining the point of insertion or deletion for a sorted linked list of objects (in this case ints) void sortedinsert(node** head_ref, Node* new_node) Node* current; /* Case for the head end */ if (*head_ref == NULL (*head_ref)->element >= new_node->element) new_node->next = *head_ref; *head_ref = new_node; else /* Locate the node before the point of insertion */ current = *head_ref; while (current->next!= NULL && current->next->element < new_node->element) current = current->next; new_node->next = current->next; current->next = new_node; 15
Finding an Element in a Sorted List Exaustive search: iterate over all list elements until the desired element is found returns a pointer for the element searched Optimized search: iterate until the element to search is less than the current position in the list When the cycle is interrupted it is evaluated if the end of list was found or if the element was found node* findsorted(node *head, int x) node *aux = head; while( (aux!= NULL) && (aux->element < x ) ) aux = aux->next; if( (aux!= NULL) && (aux ->element == x)) return aux; else return NULL; 16
Deleting a Node from a Linked List Deleting an interior node: prev->next=cur->next; Deleting the first node: head=head->next; Return deleted node to system: cur->next = NULL; free(cur); cur=null; 17
Deleting a Node from a Linked List 18
Arrays vs. Linked Lists Size Increasing the size of a resizable array can waste storage and time Storage requirements Array-based implementations require less memory than pointerbased ones Access time Array-based: constant access time Pointer-based: the time to access the i th node depends on i Insertion and deletions Array-based: require shifting of data Pointer-based: require a list traversal 19
Saving and Restoring a Linked List Use an external file to preserve a list Do not write pointers to a file, only data Recreate the list from the file by placing each item at the end of the list Use a tail pointer to facilitate adding nodes to the end of the list Treat the first insertion as a special case by setting the tail to head 20
Passing a Linked List to a Function A function with access to a linked list s head pointer has access to the entire list Pass the head pointer to a function as a reference argument 21
Dummy Head Nodes Always present, even when the linked list is empty Insertion and deletion algorithms initialize prev to reference the dummy head node, rather than NULL 22
Circular Linked Lists Last node references the first node Every node has a successor No node in a circular linked list contains NULL 23
Doubly Linked Lists Frequently, is necessary to traverse a sequence in BOTH directions efficiently Solution: Use doubly-linked list where each node has two pointers Each node points to both its predecessor and its successor Circular doubly linked list: prev pointer of the dummy head node points to the last node next reference of the last node points to the dummy head node No special cases for insertions and deletions 24
Doubly Linked Lists typedef struct listitem type payload; struct listitem *prev; struct listitem *next; Node; Node *head, *tail; head forward traversal Doubly Linked List. next x 1 x 4 prev x 2 x 3 backward traversal 25
Doubly Linked Lists with Dummy Nodes (a) A circular doubly linked list with a dummy head node (b) An empty list with a dummy head node 26
Doubly Linked Lists To delete the node to which cur points (cur->prev)->next = cur->next; (cur->next)->prev = cur->prev; To insert a new node pointed to by newptr before the node pointed to by cur newptr->next = cur; newptr->prev = cur->prev; cur->prev = newptr; newptr->prev->next = newptr; 27
Some Additional Considerations Tail Pointer: The list is not represented by a single head pointer. Instead the list is represented by a head pointer which points to the first node and a tail pointer which points to the last node. The tail pointer allows operations at the end of the list such as adding an end element or appending two lists. Head struct: Special header struct (a different type from the node type) which contains a head pointer, a tail pointer. 28
Other Kinds Of Data Structures Stack LIFO (Last In, First Out) Items added at beginning, removed from beginning Queue FIFO (First In, First Out) Items added at end Items removed from beginning 29
Stacks The stack is a very common data structure used in programs. A stack data structure behavior is similar to a stack in physical terms. Considering a stack of books... we may want to do the following: Place a book on the top... Take one off the top... See if the stack is empty... Pulling out the 3rd book from the stack cannot be done directly because it might fall over. Many application areas use stacks. Line editing, bracket matching, postfix calculation, function call stack 30
Stack Operations Stacks hold objects, usually all of the same type. The main property of a stack is that objects go on and come off of the top of the stack. The operations needed for an abstract stack are: Push: Places an object on the top of the stack. Pop: Removes an object from the top of the stack and returns that object. IsEmpty: Reports whether the stack is empty or not. Clear: Remove all items from stack Top: Returns the object at the top of the stack Because we think of stacks in terms of the physical analogy, stacks are draw vertically (so the top is really on top). 31
Sample Operation stack *S = (stack *) malloc(sizeof(stack)); push(s, a ); push(s, b ); s push(s, c ); d=top(s); pop(s); push(s, e ); pop(s); top d a b c CS 201 2/16/17
Implementation by Linked Lists 33
Implementation with Arrays Since a stack usually holds a bunch of items with the same type, it can be implemented as an array. To hold the contents of the stack it is needed: An integer top that holds the index of the element at the top of the stack. Big disadvantage: cannot grow in size 34
Defining and Creating a Stack of Ints #include <stdio.h> typedef struct stacknode int data; struct stacknode* next; node; /* init the stack */ node* init() return NULL; 35
Inserting an Element in the Stack /* * Push an element into stack using the follwing operations: * - allocate memory for the new node * - copy the data for the elment created * - insert the element in the top of the stack * - returns the new top */ node* push(node* top, int data) node* new_node = (node*)malloc(sizeof(node)); if(new_node == NULL) exit(exit_failure); new_node->data = data; new_node->next = top; return new_node; 36
Removing an Element from the Stack /* * pop an element from the stack: * - receives a pointer to store the value of the element to be removed * - returns the new top */ node* pop(node *top,int *element) node* tmp = top; *element = top->data; top = top->next; free(tmp); return top; /* * returns 1 if the stack is empty, otherwise returns 0 */ int empty(node* top) return top == NULL? 1 : 0; 37
Printing the Stack Contents /* * displays the stack content */ void display(node* top) node *current = top; if(current!= NULL) printf("stack: "); do printf("%d ",current->data); current = current->next; while (current!= NULL); printf("\n"); else printf("the Stack is empty\n"); 38
Queues In a queue insertion is done at one end whereas deletion is done at the other end. Usually implemented with linked lists, like stacks. Queues implement the FIFO (first-in first-out) policy. There are many applications for queues: Printer queues, Telecommunication queues, Simulations, Etc. dequeue enqueue 39
Operations on Queues The main operations needed for a queue are: Enqueue: insert an element in the back of the queue Dequeue: remove oldest element of the queue (front) and returns it IsEmpty: checks if the queue is empty Clear: makes the queue empty dequeue() enqueue(o) isempty() getfront() createqueue() 40
Sample Operations queue *Q = (queue *) malloc(sizeof(queue)); enqueue(q, a ); q enqueue(q, b ); enqueue(q, c ); d=getfront(q); dequeue(q); d front back enqueue(q, e ); a b c e dequeue(q); 41
Queues Implementation The queue can be implemented by linked lists or by arrays Array implementation: Number of elements in the queue, since not all the elements in the array may be storing elements. Index of the element at the front. Linked List implementation: Needs two pointers: one for the head and another for the tail 42
Defining and Creating a Queue #include <stdio.h> typedef struct queuenode int data; struct queuenode* next; node; typedef struct node *front; node *back; int size; queue; /* create an empty queue */ void create(queue *q) q->front = NULL; q->back = NULL; q->size = 0; 43
Inserting an Element in the Queue /* Returns queue size */ int queuesize(queue *q) return q->size; /* Enqueing the queue */ void enque(queue *q, int data) node *new_node; new_node = (node *)malloc(sizeof(node)); if (new_node == NULL) exit(exit_failure); new_node->data = data; 44 if (q->back == NULL) q->front = new_node; else q->back->next = new_node; q->back = new_node; q->size++;
Removing an Element to the Queue /* Dequeing the queue */ void deque(queue *q, int *data) queue *aux; 45 if(isempty(q)) printf("error: Attempting to remove from an empty queue\n"); exit(exit_failure); *data = q->front->data; aux = q->front; q->front = q->front->next; if(q->front == NULL) q->back = NULL; free(aux); q->size--; /* Display if queue is empty or not */ bool isempty() return (q->front == NULL);
Printing the Queue Elements /* displaying the queue elements */ void display(queue *q) node* aux = front; if (isempty(q)) printf("queue is empty"); return; while (aux!= back) printf("%d ", aux->data); aux = aux->next; printf("%d", aux->data); 46
Abstract Data Types A data structure and its operations can be packaged together into an entity called an abstract data type (ADT). Lists, stacks and queues are abstract data types Clean, simple interface between the ADT and the program(s) that use it. The lower-level implementation details of the data structure are hidden from view of the rest of the program. The implementation details can be changed without altering the ADT interface. Choose different files: File to hold the type and constant definitions (ADTTypes.h) File to hold the prototypes of the functions in the ADT s (public) interface (ADTInterface.h). File to hold the implementations of the public and private functions (ADTImplementation.h) 47
To Review Elemento de estudo fundamental para a aprendizagem de apontadores e estruturas dinâmicas: Apontadores e Estruturas de Dados Dinâmicas em C, Fernando Mira da Silva 48