Click here to load reader

Self-referential data structures, dynamic memory allocation, linked lists, stacks, queues, trees … Abstract Data Structures

Embed Size (px)

Citation preview

Array Data Structures & Algorithms

Self-referential data structures, dynamic memory allocation, linked lists, stacks, queues, trees Abstract Data Structures1OutlineSelf-referential data structuresStatically versus Dynamically allocated memoryConcept of linked structuresLinked listsStacks and QueuesTrees and advanced data structures

Self-referential data structuresIn previous lectures on structs we introduced the notion of self-referential data structuresThese are structs that contain a pointer sub-field that is intended to point at a struct of the same type definitionExample:

struct NodeStruct { int ID ; char Name[50] ; double Score ; struct NodeStruct * NextPtr ;} ;typedef struct NodeStruct Node_t ;

Node_t Node = { 0, , 0.0, NULL }, Node2 ;

Node_t * NodePtr = &Node ;

Node.NextPtr = &Node2 ; // This way NodePtr->NextPtr = &Node2 ; // or that way!Statically vs Dynamically allocated memoryC provides for support of a logical (ie. conceptual) model in which user allocated memory (for a given program) is divided into several distinct blocksThese include a block for code, for static data, for stack data, for buffers (I/O) and for dynamical memory allocation (on the Heap)When programmers write programs, they utilize names to declare variables and data structures so that they can refer to those memory locations within their codeThese named variables refer to a statically assigned Namespace (once compiled, names and logical locations do not change)Statically vs Dynamically allocated memoryWe are now going to consider dynamically allocated memory and techniques for exploiting itThis is done at execution time, hence we cannot create variable names to refer to locations we must use pointers insteadWe focus on malloc(), free() and sizeof all three are important!Example:NodePtr = malloc( sizeof( Node_t ) ) ;

if( NodePtr != NULL ) printf( Memory allocation successful!\n ) ;else printf( Memory not allocated!\n ) ;

free( NodePtr ) ;Pointer Data PointerMemory block used for program variables with assigned namesMemory block used for dynamically allocated blocks for storing data, each block addressable only using pointers (no names of variables!) Data Pointer Data NULLStatically allocated namespaceDynamically allocated memory The HeapRootPtrsizeof( Node_t )struct Nodetype { struct Nodetype * NextPtr ; . . . } ;typedef struct Nodetype Node_t ;Node_t * RootPtr ;Concept of linked structuresWe will be creating dynamic (ie. runtime) data structures that will contain pointers to other structuresThese are called linked structures. Example: Node_t Node1, Node2 ;

Node1.NextPtr = &Node2 ; // points at Node2 Node2.NextPtr = NULL ; // points nowhere!

Data NULL Data PointerNode1Node2Linked listsThe concept of a linked list refers to a set of dynamically allocated structures that contain pointer sub-fields, so that each pointer points at another allocated structureBy linking all elements of the set together, starting from a known address location, the entire set is called a linked list.All linked lists must have an associated root pointer that is a named pointer variable. This provides the known address location to enter the listThere will be a last, or final, element and that one must have a NULL value in its link pointer to indicate the logical end-of-list.There are several kinds of linked list structuresSingly linked listDoubly linked listLinked listsTo illustrate the concept with code, we consider the example problemInput data from a direct access file into a linked list.

typedef struct { int ID ; char Name[50] ; double Score ; } Payload_t ;struct NodeStruct { Payload_t Data ; struct NodeStruct * NextPtr ; } ;

typedef struct NodeStruct Node_t ;

FILE * cfPtr ;Node_t * NodePtr, * Nptr, * RootPtr ;int PayloadSize = sizeof( Payload_t ), NodeSize = sizeof( Node_t ) ;Linked listsExample continuedAlways deal with the named root pointer first as a special case Head of the ListcfPtr = fopen( DAFile.dat, rb ) ;

RootPtr = malloc( NodeSize ) ;fread( RootPtr, PayloadSize, 1, cfPtr ) ; RootPtr->NextPtr = NULL ; PointerRootPtrData 010Linked listsExample continuedAnd now deal with the dynamically allocated list nodes and linksNptr = RootPtr ;

while( ! feof( cfPtr ) ) {

NodePtr = malloc( NodeSize ) ; fread( NodePtr, PayloadSize, 1, cfPtr ) ;

Nptr->NextPtr = NodePtr ; Nptr = NodePtr ; Nptr->NextPtr = NULL ;

} fclose( cfPtr ) ;PointerData NxtRootPtrData NxtData 0Linked listsAnother example: Traversal of a list to find an IDNote that this is a sequential search with complexity O(N) for a list with N nodesNptr = RootPtr ; // Head of the list

while( Nptr != NULL) ) { // list traversal loop

if( Nptr->Data.ID == IDval ) break ; // search criterion

Nptr = NPtr->NextPtr ; // point at successor (next) element}

if( Nptr != NULL ) printf( ID = %d, Name = %s, Score = %lf\n, Nptr->Data.ID, Nptr->Data.Name, Nptr->Data.Score ) ;else printf( ID: %d not found\n, IDval ) ;Linked listsAnd another example: Traversal of a list to output data to stdoutAlways start from the named root pointerNptr = RootPtr ;

while( Nptr != NULL ) {

printf( ID = %d, Name = %s, Score = %lf\n, Nptr->Data.ID, Nptr->Data.Name, Nptr->Data.Score ) ;

Nptr = Nptr->NextPtr ;} Linked listsDeletion of a listAlso called de-allocation of memoryThis is an extremely important issue for programmersNptr = RootPtr ;

while( Nptr != NULL) ) { // Traverse the list

// CAUTION: Order of statements is important NodePtr = Nptr ; // 1. Save current node address Nptr = NodePtr->NextPtr ; // 2. Point at successor node free( NodePtr ) ; // 3. Release memory for node } RootPtr = NULL ; // list is now empty!IMPORTANT POINT !

Since memory blocks are allocated in unpredictable ways (effectively random), freeing those blocks may leave gaps, or holes, in the Heap.

A systematic freeing up of all blocks eventually returns the Heap to its original unallocated state.

For every malloc() call there must be a matching free() call !Linked listsInput data into a linked list so that it is sorted with each insertion of a node Insertion SortRecall from a previous example:typedef struct { int ID ; char Name[50] ; double Score ; } Payload_t ;struct NodeStruct { Payload_t Data ; struct NodeStruct * NextPtr ; } ;

typedef struct NodeStruct Node_t ;

FILE * cfPtr ;Node_t * NodePtr, * Nptr, * RootPtr ;int PayloadSize = sizeof( Payload_t ), NodeSize = sizeof( Node_t ) ;cfPtr = fopen( DAFile.dat, rb ) ;

RootPtr = malloc( NodeSize ) ;fread( RootPtr, PayloadSize, 1, cfPtr ) ; RootPtr->NextPtr = NULL ; Linked listsWe must consider three different special cases1. Insert at Head of List2. Insert between two list nodes3. Insert at End of ListPointerData NxtRootPtrData NxtData 0123Linked listsInput data from a direct access file into a linked listHere is a complete listing of the C code for this algorithm\\ ASSUMPTION: File has been opened using cfPtrwhile( ! feof( cfPtr ) ) { NodePtr = malloc( NodeSize ) ; fread( NodePtr, PayloadSize, 1, cfPtr ) ;

if( RootPtr == NULL ) { NodePtr->NextPtr = NULL ; RootPtr = NodePtr ; } else if(NodePtr->Payload.ID < RootPtr->Payload.ID ) { NodePtr->NextPtr = RootPtr ; RootPtr = NodePtr ; } else { Node_t * PrevNodePtr = RootPtr ; Nptr = RootPtr->NextPtr ;

while( Nptr != NULL ) { if( NodePtr->Payload.ID < NPtr->Payload.ID ) { NodePtr->NextPtr = PrevNodePtr ; PrevNodePtr->NextPtr = NodePtr ; break ; } else { PrevNodePtr = Nptr ; Nptr = Nptr->NextPtr ; } } if( PrevNodePtr->NextPtr == NULL ) { NodePtr->NextPtr = NULL ; PrevNodePtr->NextPtr = NodePtr ; } }} Linked lists\\ ASSUMPTION: File has been opened using cfPtrwhile( ! feof( cfPtr ) ) { NodePtr = malloc( NodeSize ) ; fread( NodePtr, PayloadSize, 1, cfPtr ) ;

if( RootPtr == NULL ) { NodePtr->NextPtr = NULL ; RootPtr = NodePtr ; } else if(NodePtr->Payload.ID < RootPtr->Payload.ID ) { NodePtr->NextPtr = RootPtr ; RootPtr = NodePtr ; } else { Node_t * PrevNodePtr = RootPtr ; Nptr = RootPtr->NextPtr ;

while( Nptr != NULL ) { if( NodePtr->Payload.ID < NPtr->Payload.ID ) { NodePtr->NextPtr = PrevNodePtr ; PrevNodePtr->NextPtr = NodePtr ; break ; } else { PrevNodePtr = Nptr ; Nptr = Nptr->NextPtr ; } } if( PrevNodePtr->NextPtr == NULL ) { NodePtr->NextPtr = NULL ; PrevNodePtr->NextPtr = NodePtr ; } }} \\ ASSUMPTION: File has been opened using cfPtrwhile( ! feof( cfPtr ) ) { NodePtr = malloc( NodeSize ) ; fread( NodePtr, PayloadSize, 1, cfPtr ) ;

\\ Logic to find where to insert this inputted data block \\ in the singly linked list.

} PointerNodePtrDataLinked lists\\ ASSUMPTION: File has been opened using cfPtrwhile( ! feof( cfPtr ) ) { NodePtr = malloc( NodeSize ) ; fread( NodePtr, PayloadSize, 1, cfPtr ) ;

if( RootPtr == NULL ) { NodePtr->NextPtr = NULL ; RootPtr = NodePtr ; } else if(NodePtr->Payload.ID < RootPtr->Payload.ID ) { NodePtr->NextPtr = RootPtr ; RootPtr = NodePtr ; } else { Node_t * PrevNodePtr = RootPtr ; Nptr = RootPtr->NextPtr ;

while( Nptr != NULL ) { if( NodePtr->Payload.ID < NPtr->Payload.ID ) { NodePtr->NextPtr = PrevNodePtr ; PrevNodePtr->NextPtr = NodePtr ; break ; } else { PrevNodePtr = Nptr ; Nptr = Nptr->NextPtr ; } } if( PrevNodePtr->NextPtr == NULL ) { NodePtr->NextPtr = NULL ; PrevNodePtr->NextPtr = NodePtr ; } }} if( RootPtr == NULL ) { NodePtr->NextPtr = NULL ; RootPtr = NodePtr ; }PointerRootPtrData 0Linked lists\\ ASSUMPTION: File has been opened using cfPtrwhile( ! feof( cfPtr ) ) { NodePtr = malloc( NodeSize ) ; fread( NodePtr, PayloadSize, 1, cfPtr ) ;

if( RootPtr == NULL ) { NodePtr->NextPtr = NULL ; RootPtr = NodePtr ; } else if(NodePtr->Payload.ID < RootPtr->Payload.ID ) { NodePtr->NextPtr = RootPtr ; RootPtr = NodePtr ; } else { Node_t * PrevNodePtr = RootPtr ; Nptr = RootPtr->NextPtr ;

while( Nptr != NULL ) { if( NodePtr->Payload.ID < NPtr->Payload.ID ) { NodePtr->NextPtr = PrevNodePtr ; PrevNodePtr->NextPtr = NodePtr ; break ; } else { PrevNodePtr = Nptr ; Nptr = Nptr->NextPtr ; } } if( PrevNodePtr->NextPtr == NULL ) { NodePtr->NextPtr = NULL ; PrevNodePtr->NextPtr = NodePtr ; } }} else if( NodePtr->Payload.ID < RootPtr->Payload.ID ) { NodePtr->NextPtr = RootPtr ; RootPtr = NodePtr ; } else { \\ Locate insertion point in SL list }PointerRootPtrData ?Linked lists\\ ASSUMPTION: File has been opened using cfPtrwhile( ! feof( cfPtr ) ) { NodePtr = malloc( NodeSize ) ; fread( NodePtr, PayloadSize, 1, cfPtr ) ;

if( RootPtr == NULL ) { NodePtr->NextPtr = NULL ; RootPtr = NodePtr ; } else if(NodePtr->Payload.ID < RootPtr->Payload.ID ) { NodePtr->NextPtr = RootPtr ; RootPtr = NodePtr ; } else { Node_t * PrevNodePtr = RootPtr ; Nptr = RootPtr->NextPtr ;

while( Nptr != NULL ) { if( NodePtr->Payload.ID < NPtr->Payload.ID ) { NodePtr->NextPtr = PrevNodePtr ; PrevNodePtr->NextPtr = NodePtr ; break ; } else { PrevNodePtr = Nptr ; Nptr = Nptr->NextPtr ; } } if( PrevNodePtr->NextPtr == NULL ) { NodePtr->NextPtr = NULL ; PrevNodePtr->NextPtr = NodePtr ; } }} PointerData NxtRootPtrData NxtData 0 Node_t * PrevNodePtr = RootPtr ; Nptr = RootPtr->NextPtr ;

while( Nptr != NULL ) { if( NodePtr->Payload.ID < NPtr->Payload.ID ) { NodePtr->NextPtr = NPtr ; PrevNodePtr->NextPtr = NodePtr ; break ; } else { PrevNodePtr = Nptr ; Nptr = Nptr->NextPtr ; } } Linked listsRgnInput data from a direct access file into a linked list\iggffi\\ ASSUMPTION: File has been opened using cfPtrwhile( ! feof( cfPtr ) ) { NodePtr = malloc( NodeSize ) ; fread( NodePtr, PayloadSize, 1, cfPtr ) ;

if( RootPtr == NULL ) { NodePtr->NextPtr = NULL ; RootPtr = NodePtr ; } else if(NodePtr->Payload.ID < RootPtr->Payload.ID ) { NodePtr->NextPtr = RootPtr ; RootPtr = NodePtr ; } else { Node_t * PrevNodePtr = RootPtr ; Nptr = RootPtr->NextPtr ;

while( Nptr != NULL ) { if( NodePtr->Payload.ID < NPtr->Payload.ID ) { NodePtr->NextPtr = PrevNodePtr ; PrevNodePtr->NextPtr = NodePtr ; break ; } else { PrevNodePtr = Nptr ; Nptr = Nptr->NextPtr ; } } if( PrevNodePtr->NextPtr == NULL ) { NodePtr->NextPtr = NULL ; PrevNodePtr->NextPtr = NodePtr ; } }} if( PrevNodePtr->NextPtr == NULL ) { NodePtr->NextPtr = NULL ; PrevNodePtr->NextPtr = NodePtr ; }PointerData NxtRootPtrData 0Linked lists\\ ASSUMPTION: File has been opened using cfPtrwhile( ! feof( cfPtr ) ) { NodePtr = malloc( NodeSize ) ; fread( NodePtr, PayloadSize, 1, cfPtr ) ;

if( RootPtr == NULL ) { NodePtr->NextPtr = NULL ; RootPtr = NodePtr ; } else if(NodePtr->Payload.ID < RootPtr->Payload.ID ) { NodePtr->NextPtr = RootPtr ; RootPtr = NodePtr ; } else { Node_t * PrevNodePtr = RootPtr ; Nptr = RootPtr->NextPtr ;

while( Nptr != NULL ) { if( NodePtr->Payload.ID < NPtr->Payload.ID ) { NodePtr->NextPtr = PrevNodePtr ; PrevNodePtr->NextPtr = NodePtr ; break ; } else { PrevNodePtr = Nptr ; Nptr = Nptr->NextPtr ; } } if( PrevNodePtr->NextPtr == NULL ) { NodePtr->NextPtr = NULL ; PrevNodePtr->NextPtr = NodePtr ; } }} WHEW !

This example illustrates the Top-Down approach where the original problem has been broken down into sub-problems. These are organized methodically, and the algorithm developed accordingly.

There are many details to consider and care must be taken when writing code with pointers, ensuring that the order of statements is correct.

Always test code !!Linked lists\\ ASSUMPTION: File has been opened using cfPtrwhile( ! feof( cfPtr ) ) { NodePtr = malloc( NodeSize ) ; fread( NodePtr, PayloadSize, 1, cfPtr ) ;

if( RootPtr == NULL ) { NodePtr->NextPtr = NULL ; RootPtr = NodePtr ; } else if(NodePtr->Payload.ID < RootPtr->Payload.ID ) { NodePtr->NextPtr = RootPtr ; RootPtr = NodePtr ; } else { Node_t * PrevNodePtr = RootPtr ; Nptr = RootPtr->NextPtr ;

while( Nptr != NULL ) { if( NodePtr->Payload.ID < NPtr->Payload.ID ) { NodePtr->NextPtr = PrevNodePtr ; PrevNodePtr->NextPtr = NodePtr ; break ; } else { PrevNodePtr = Nptr ; Nptr = Nptr->NextPtr ; } } if( PrevNodePtr->NextPtr == NULL ) { NodePtr->NextPtr = NULL ; PrevNodePtr->NextPtr = NodePtr ; } }} \\ USING LINKED LIST FUNCTIONS

\\ ASSUMPTION: File has been opened using cfPtrwhile( ! feof( cfPtr ) ) { NodePtr = malloc( NodeSize ) ; fread( NodePtr, PayloadSize, 1, cfPtr ) ; NodePtr->NextPtr = NULL ;

Nptr = FindNode ( RootPtr, NodePtr->Payload.ID ) ; InsertNode( RootPtr, Nptr, NodePtr ) ; }

\\ This still leaves the functions to write, but it makes\\ it easier to understand the primary algorithm, \\ and the start of the Top-Down design process.Linked listsFrom the previous example it is seen that developing and using functions with well defined algorithms is worthwhile when working with dynamically allocated list data structuresThis separates high-level intention from low-level detailsDesign focuses on Use-Cases, while details focus on how it works

Some other example functions to consider: Node_t * FindNode ( Node_t * Head, int IDval ) ; void * InsertNode ( Node_t * Root, Node_t * InsertPtr, Node_t * InsertNodePtr ) ; Node_t * FindLastNode ( Node_t * Head ) ; void * OutputList ( Node_t * Head ) ; void * DeleteList ( Node_t * Head ) ; // deallocate all nodes int isEmpty( Node_t * Head ) ; // only need to test if Head == NULL long int ListLength( Node_t * Head ) ; // count number of nodesPointer Data Prv Nxt Data NULL Nxt Data Prv NULLHeadPtrstruct NodetypeDL { Payload_t Data ; struct NodetypeDL * PrevPtr ; struct NodetypeDL * NextPtr ;} ;typedef struct NodetypeDL Node_tDL ;

Node_tDL * HeadPtr = NULL, * TailPtr = NULL ;PointerTailPtrDoubly linked lists Conceptual ViewDoubly linked listsDoubly linked lists feature two self-referential pointers, usually called Predecessor (Previous) and Successor (Next) linksThere are two named pointers, usually called Head and Tail pointers, the latter pointing to the last node in the listTraversal can be performed in both directionsLimited traversals (to adjacent, or nearby, nodes) can be performed in both directionsTypical operations are similar to those for singly linked lists InsertNode, DeleteNode, DeleteList, FindNode, and so on

Bi-directional traversalDoubly linked lists Useful functionsInsertHeadNodeInsertTailNodeFindNodebyIDInsertNodebyIDDeleteNodebyIDDeleteList

Doubly linked lists Useful functionsInsertHeadNodeInsertTailNodeFindNodebyIDInsertNodebyIDDeleteNodebyIDDeleteList

\\ Assume that Nptr points to the data structure to\\ be inserted at the head of the list, and it is fully \\ initialized, including both Next and Prev pointers\\ with NULL values.\\ Note: Both head and tail pointer arguments can be\\ modified (use call-by-reference).

void InsertHeadNode( StackNodePtr_t * Hptr, StackNodePtr_t * Tptr, StackNodePtr_t Nptr ) {

if( *Hptr == NULL ) \\ empty list, update tail *Tptr = Nptr ; else \\ update new node to point Nptr->NextPtr = *Hptr ; \\ to rest of list

*Hptr = Nptr ; \\ update head and exit return ; // success}\\ NOTE: It is possible to achieve this in different\\ ways.Doubly linked lists Useful functionsInsertHeadNodeInsertTailNodeFindNodebyIDInsertNodebyIDDeleteNodebyIDDeleteList

\\ Assume that Nptr points to the data structure to\\ be inserted at the tail of the list, and it is fully \\ initialized, including both Next and Prev pointers\\ with NULL values.\\ Note: Both head and tail pointer arguments can be\\ modified (use call-by-reference).

void InsertTailNode( StackNodePtr_t * Hptr, StackNodePtr_t * Tptr, StackNodePtr_t Nptr ) {

if( *Tptr == NULL ) \\ empty list, update head *Hptr = Nptr ; else \\ update new node to point Nptr->PrevPtr = *Tptr ; \\ to rest of list

*Tptr = Nptr ; \\ update tail and exit return ; // success}

Doubly linked lists Useful functionsInsertHeadNodeInsertTailNodeFindNodebyIDInsertNodebyIDDeleteNodebyIDDeleteList

\\ Assume that IDval contains the search value and\\ Hptr points to the head of the list.\\ Returns a pointer to the first node containing IDval,\\ otherwise returns NULL.

StackNodePtr_t * FindNodebyID( StackNodePtr_t Hptr, int IDval ) { StackNodePtr_t Nptr ;

if( *Hptr == NULL ) \\ empty list return NULL ; else \\ search list Nptr = Hptr ; while( Nptr != NULL ) { if( Nptr->Payload.ID == Idval ) return Npr ; \\ search successful Nptr = Nptr->NextPtr ; } return NULL ;}

Linked lists Some Deeper DetailsLogical model view of the Heap SpaceMemory allocation model Based on heap pointers Unpredictable address associations with the memory blockRequires care once a pointer is lost, the memory block (and all data inside it) is lost and cannot be accessed ManagementOverhead costs time and spaceAllocation Tables and Heap LimitsHoles and fragmentationDefragmentationPointerData NxtRootPtrData NxtData 0??32Stacks and QueuesThere are two kinds of singly linked list structures that merit special attention

A Stack is a linked list with an access pointer called the Stack Pointer, and whose nodes are added or deleted only at the beginning of the listThey are referred to as LIFO (Last In, First Out) lists

A Queue is a linked list with two access pointers, called Head and Tail, and whose nodes are added only to the Tail, or deleted only from the Head of the listThey are referred to as FIFO lists (First In, First Out)StacksThere are two basic operations on stacks. Both are applied only at the beginning of the list, otherwise called the Top of the StackPUSH insert a new node at the top of the stackPOP remove (delete) a new node from the top of the stackThe named entry pointer is typically called the Stack Pointer\\ ASSUMPTIONS:struct stackNode { Data_t Data ; struct stackNode * Nextptr ;}typedef struct stackNode StackNode ;typedef StackNode * StackNodePtr_t ;StackNodePtr_t StackPtr = NULL ;PointerData NxtStackPtrData NxtData 0LIFO

Stacks Push()Push() inserts a node into the first list position of the stackThe new top-of-stack node must point to the rest of the listThe stack pointer must point at the new top-of-stack nodeAccount for possible errorThis is one alternative coding of Push() there are others (eg. textbook) PointerData NxtStackPtrData NxtData 0StackNodePtr Push( StackNodePtr * SP, Data_t D ) { StackNodePtr NewPtr ;

NewPtr = malloc( sizeof( StackNode ) ) ; if( NewPtr == NULL ) return NULL ;

Copy( NewPtr->Data, D ) ; NewPtr->NextPtr = NULL ;

if( *SP != NULL ) NewPtr->NextPtr = *SP ; *SP = NewPtr ; return SP ;}

\\ USE CASEif( Push( &stackPtr, Data ) != NULL ) printf( Push to stack successful\n ) ;else printf( Allocation failed, no Push\n ) ;Top-of-stack nodeStacks Pop()Pop() obtains and returns the payload data from the top-of-stack node, then it removes this node from the stackA return mechanism must be chosen and implemented for the payload dataScalar data can be returned directly through function return statements this limits the ability to report errors such as an empty stackCall-by-reference is always useful, but necessary for complex data structuresThe stack pointer must be updated to point at the second node, which then becomes the new top-of-stack nodeThe used node is then de-allocated (and its memory block can be re-used)

PointerData NxtStackPtrData NxtData 0Top-of-stack nodeint Pop( StackNodePtr * SP, Data_t * Dptr ) { StackNodePtr tempPtr ; Data_t D ;

if( *SP == NULL ) return 0 ; \\ Stack is empty so fail

Copy( (*SP)->Data, Dptr ) ; \\ Copy-return payload data tempPtr = (*SP)->NextPtr ; \\ Get address of next node free( *SP ) ; \\ Deallocate top node *SP = tempPtr ; \\ Update stack pointer

return 1 ; \\ Success is TRUE}

\\ USE CASE :: Assume: Data_t DataRec ;\\ StackNodePtr * StackPtr ;

if( Pop( &StackPtr, &DataRec ) ) printf( ID = %d\n, DataRec.ID ) ;else printf( No data found on empty stack\n ) ;

QueuesThe basic queue is a singly linked list with two pointers, Head and TailThere are two fundamental operations enqueue() insert a node at the Tail position dequeue() obtain payload data from the node at the Head position, then delete the nodePointerData NxtHeadPtrData NxtData 0PointerTailPtrDequeueEnqueueQueuesThe basic queue is a singly linked list with two pointers, Head and TailThere are two fundamental operations enqueue() insert a node at the Tail position dequeue() obtain payload data from the node at the Head position, then delete the nodePointerData NxtHeadPtrData NxtData 0PointerTailPtrDequeueEnqueueNodePtr_t enqueue( NodePtr_t * NQP, Data_t D ) { NodePtr_t NPtr ;

NPtr = malloc( sizeof( Node_t ) ) ; if( NPtr == NULL ) return NULL ; NPtr->NextPtr = NULL ;

Copy( NPtr->Data, D ) ; if( *NQP != NULL ) NPtr->NextPtr = *NQP ; *NQP = NPtr ; return NPtr ;}

int dequeue( NodePtr_t * DQP, Data_t * Dptr ) { NodePtr_t tempPtr ; Data_t D ;

if( *DQP == NULL ) return 0 ; \\ Stack is empty so fail

Copy( (*DQP)->Data, Dptr ) ; \\ Copy-return payload data tempPtr = *DQP ; \\ Get address of next node *DQP = (*DQP)->NextPtr ; \\ Update stack pointer free( tempPtr ) ; \\ Deallocate node return 1 ; \\ Success is TRUE}

QueuesThere are different kinds of queuesCircular Queues are singly linked lists with only a single named pointer, sometimes called CQptrAll enqueue and dequeue operations are performed using CQptr. (CQptr == NULL indicates an empty queue.)A further distinction is that the last node points to the first node the NextPtr field is never NULL!Not necessarily FIFOSometimes called Round-Robin queueUsed for print spoolers, concurrency handling and many other applicationsPointerData NxtCQptrData NxtData NxtDequeueEnqueue

Advanced data structure abstractionsExploring the abstract connections between dynamically allocated data structures and designing efficient algorithms for ADTs

I think that I shall never see,A program lovelier than a Tree

- with apologies to Joyce Kilmer

Data NxtData NxtData 0Data L RData L RData L RData L RData L RData L RPtrRootPtrPtrRootPtrData L RData L R

Binary TreesA binary tree is a data structure that is based on a specific relationship that exists between two neighbour nodesExample: P1->Data.ID is less than P2->Data.ID

Each node contains two pointer fieldsA Left child pointerA Right child pointer

Nodes that do not have a Left (Right) child assign NULL values to that pointer fieldWhen both node pointers are NULL, the node is called a Leaf node.In the ideal case of N = 2K nodes with perfect balancing of Left and Right child nodes, there is: 1 root node level-1 2 level-2 nodes 4 level-3 nodes, and so on, up to log2(N/2) level-(K-1) nodes.

Recall that log2(N/2) = K-1. For N=1024 nodes there are only K=10 levels.This implies that for ordered nodes, a binary search strategy can be implemented.

A Binary Treestruct DataRec { int SID ; char Lname[30] ; char Fname[20] ; float GPA ;}typedef struct DataRec Data ;

struct BinTreeRec { Data D ; struct BinTreeRec * Left ; struct BinTreeRec * Right ;}Data L RData L RData L RData L RData L RData L RPtrRootPtrData L RData L RLeft childLeafLeafLeafLeafRight childRoot nodeLeft childRight childLeft childRight childTree ConstructionsConstructing a tree is done by inserting each new node at a leaf positionWe assume the Left Child is less than the Right Child.Consider three different input orders10, 20, 30, 40, 50, 60, 7020, 50, 30, 40, 10, 60, 7040, 20, 10, 30, 60, 50, 70

This produces a highly unbalancedtree it is actually a singly linked listwith wasted left children! Only linearsearch can be applied.40206010305070Tree ConstructionsConstructing a tree is done by inserting each new node at a leaf positionWe assume the Left Child is less than the Right Child.Consider three different input orders10, 20, 30, 40, 50, 60, 7020, 50, 30, 40, 10, 60, 7040, 20, 10, 30, 60, 50, 70

This version is better balanced, but search isbetween O(n) and O(log n)40206010305070Tree ConstructionsConstructing a tree is done by inserting each new node at a leaf positionWe assume the Left Child is less than the Right Child.Consider three different input orders10, 20, 30, 40, 50, 60, 7020, 50, 30, 40, 10, 60, 7040, 20, 10, 30, 60, 50, 70

This version is perfectly balancedand search is O(log n)40206010305070Tree Traversal TechniquesTraversalsInorderPreorderPostorder

40206010305155070253545556575Tree Traversal TechniquesTraversalsInorderPreorderPostorder

40206010305155070253545556575void inOrder ( TreeNodePtr_t TPtr ) {

if( TPtr != NULL ) { inOrder( TPtr->leftChild ) ; \\ First, go left outputNode( TPtr ) ; \\ output node inOrder( TPtr->rightChild ) ; \\ then go right }

return ;}inOrder: 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75Tree Traversal TechniquesTraversalsInorderPreorderPostorder

40206010305155070253545556575void preOrder ( TreeNodePtr_t TPtr ) {

if( TPtr != NULL ) { outputNode( TPtr ) ; \\ output node preOrder( TPtr->leftChild ) ; \\ then, go left preOrder( TPtr->rightChild ) ; \\ then go right }

return ;}preOrder: 40, 20, 10, 5, 15, 30, 25, 35, 60, 50, 45, 55, 70, 65, 75Tree Traversal TechniquesTraversalsInorderPreorderPostorder

40206010305155070253545556575void postOrder ( TreeNodePtr_t TPtr ) {

if( TPtr != NULL ) { postOrder( TPtr->leftChild ) ; \\ First, go left postOrder( TPtr->rightChild ) ; \\ then go right outputNode( TPtr ) ; \\ output node }

return ;}postOrder: 5, 15, 10, 25, 35, 30, 20, 45, 55, 50, 65, 75, 70, 60, 40Tree Traversal TechniquesTraversalsInorderPreorderPostorder

40206010305155070253545556575inOrder: 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75preOrder: 40, 20, 10, 5, 15, 30, 25, 35, 60, 50, 45, 55, 70, 65, 75postOrder: 5, 15, 10, 25, 35, 30, 20, 45, 55, 50, 65, 75, 70, 60, 40Tree Data StructuresThere are several important algorithms necessary to work with tree data structures, including:InsertTreeNodeDeleteTreeNodeRotateNodeLeft used to reorder parent-child node relationshipsRotateNodeRightFindTreeHeight

This subject is covered in much more detail in 2nd year courses on data structures and advanced programmingAdvanced Tree Data StructuresIn order to maintain a balanced tree, Adelson-Velskii and Landis developed the tree balancing algorithm leading to AVL trees.Youtube presentation: http://www.youtube.com/watch?v=5C8bLQBjcDI Closely related to AVL trees are Red-Black trees

Often, trees have more than 2 children4 children Quad-Trees (used in 2D graphics)8 children Oct-Trees (used in 3D graphics)Advanced Data Structures - GraphsIn closing we mention the structure called a Graph.Graphs are noteworthy due to the properties:There may be more than one entry pointer to the graphEach graph node may point to multiple nodesThere is no concept of level there is a notion of traversal distance, howeverIt is possible to traverse a graph such that a node is visited more than onceApplication Natural Language Problems (NLP)When select nodes contain clusters of child nodes and primary links are between these select nodes, the structure is called a network graph.Graphs and networks are often used in Queueing TheoryApplied to problems in manufacturing, process controlAdvanced Data Structures - GraphsA quick video introduction by Dickson Tsai (8+ minutes)https://www.youtube.com/watch?v=vfCo5A4HGKc

Graph Theory: An Introduction by patrickJMT (12+ minutes)https://www.youtube.com/watch?v=HmQR8Xy9DeM

A longer lecture by Jonathan Shewchuk of UCBerkeley (50+ minutes)https://www.youtube.com/watch?v=ylWAB6CMYiY

SummaryAdvanced data structures: self-referential data structures, dynamic memory allocation, linked lists, stacks, queues, trees57Topic SummarySelf-referential data structuresDynamic memory allocationLinked listsSingle and Double linkageStacksQueuesTrees

Study Chapter 12: Data StructuresAbstract data structures, dynamic memory allocation, using pointers and self-referential data structures, linked lists, stacks and queues.

Review all Chapters coveredPreparation for the Final Examination.Study examples Adapt them to your own uses !58