Data Structures - Detailed Semester Notes
Data Structures - Detailed Semester Notes
Linked lists are classified into singly, doubly, and circular linked lists based on their structural properties. A singly linked list contains nodes with a data field and a reference to the next node, making it simple and lightweight for operations primarily at the head . Doubly linked lists extend singly linked lists by storing an additional pointer to the previous node, allowing bidirectional traversal, which makes them useful for scenarios requiring forward and backward navigation . Circular linked lists connect the last node back to the first, forming a circular loop, thus efficiently handling applications like round-robin scheduling where uniform traversal from in one direction in an endless loop is critical . Each type’s structure caters to different computational needs, from straightforward data consumption to intricate modifications and accesses.
A binary search algorithm improves efficiency by utilizing a divide-and-conquer approach, significantly reducing the search space by half with each comparison, leading to a time complexity of O(log n) compared to the O(n) of linear search, which checks each element sequentially . The binary search, however, requires the data to be sorted beforehand, which can be a limitation if the dataset is subject to frequent updates. On the contrary, linear search's O(n) time complexity remains constant regardless of the order of elements, making it suitable for unsorted arrays or linked lists . Thus, binary search is more efficient for static datasets with frequent queries but less adaptable for dynamic, unordered datasets.
The key operations of a stack include push, pop, and peek. Push adds an element to the top of the stack, and pop removes the top element while peek allows viewing the top element without removing it . These operations support the Last-In-First-Out (LIFO) access pattern, which is crucial in scenarios like backtracking during pathfinding or syntax parsing in compilers, where the most recent activity or query needs immediate resolution . Stacks effectively manage nested structures and recursive functions, aiding in memory management for recursive operations through system call stacks . Thus, they are indispensable for managing data in a LIFO context efficiently.
Circular queues solve the limitation of linear queues where the queue can't utilize memory space beyond the rear once the queue reaches its capacity, despite there being space at the front due to dequeuing. By connecting the end of the queue back to the beginning, circular queues allow the reuse of this available memory . This configuration is highly advantageous in scenarios requiring continuous cycling through a series of operations, such as in simulation systems or buffers in multimedia applications, which demand frequent enqueuing and dequeuing without memory allocation inefficiencies associated with regular linear queues . Circular queues thus enhance performance by utilizing memory space optimally and resolving the fixed capacity issue.
Infix notation, where operators are written between operands (e.g., A + B), is most familiar to humans as it resembles natural mathematical expressions; however, it requires knowledge of operator precedence for computers to evaluate correctly . Prefix notation (e.g., +AB), places operators before their operands, minimizing the need for brackets as it inherently expresses precedence but isn’t naturally readable for humans. Postfix notation (e.g., AB+), also known as Reverse Polish Notation, places operators after operands, allowing it to be evaluated on-the-fly using a stack, making it very efficient for computer computations like those in compilers and calculators . All notations have their roles, with infix being human-friendly and prefix/postfix being machine-efficient for computations.
Recursion uses more memory than iteration because each recursive call is added to the call stack, resulting in additional overhead for stack frame management. This can lead to stack overflow if the recursion depth is too high without proper base cases . Each iteration, however, doesn’t require additional stack space because the loop merely runs within the initial context without repeated context switches. Computationally, while both can solve similar problems, recursion can be less efficient than iteration due to this overhead and potential repeated calculations unless optimizations like memoization are applied to alleviate redundant computations . Thus, iteration is generally more memory-efficient and often computationally cheaper for problems that don’t inherently require recursive structure.
Tree traversal methods—Inorder (LNR), Preorder (NLR), and Postorder (LRN)—are pivotal in manipulating binary search trees (BSTs). Inorder traversal visits nodes in ascending order, reflecting the inherent ordered structure of BSTs, which is suitable for operations requiring sorted data output, such as database management . Preorder traversal is beneficial in scenarios demanding structure preservation like serialization or cloning of trees, while Postorder is suited for removing and entail removal of nodes or computation of systems reliant on cumulative processing, such as computing space usage . These traversal methods provide essential pathways to explore, process, and manipulate the hierarchical data structures within BSTs effectively.
The conversion from infix to postfix using stacks involves scanning the infix expression from left to right, where operands are directly added to the postfix expression, and operators are pushed to a stack. Operators are popped from the stack based on precedence and associativity rules, ensuring that higher precedence operators are processed before lower precedence operators once encountered . The stack helps handle operator precedence by pushing higher or equal precedence operators to the output before the current operator is pushed . This systematic use of stacks allows the proper evaluation order in postfix, which naturally aligns with operation precedence.
Arrays store elements in contiguous memory locations which allows O(1) time complexity for accessing elements by index . This support for continuous memory can lead to deficiencies when enlarging the array as it may need to allocate a new larger space in one contiguous block. Linked lists, however, store elements in nodes connected by pointers, allowing easier insertion and deletion of elements with a time complexity of O(1) for operations at the head and O(n) for access by index . This structure can lead to overhead from storing additional pointers, potentially impacting cache performance negatively due to non-contiguous memory storage. Thus, arrays are generally more performant for frequent element access and lookups, whereas linked lists are superior when frequent additions and deletions are needed.
Hashing optimizes search operations by mapping keys to values or addresses using a hash function, allowing for average O(1) time complexity for search, insertion, and deletion operations . Collisions occur when two keys hash to the same index, and they are managed through techniques such as open addressing—where subsequent slots are checked following a probing sequence—and separate chaining, which involves maintaining a list of all elements that hash to the same index . These strategies prevent collisions from causing access issues by either reassigning slots or maintaining collectives, ensuring efficient data retrieval even with potentially colliding inputs.