Related Topics
Data Structure
- Question 17
What is a queue implemented using a linked list?
- Answer
A queue is a data structure that follows the First-In-First-Out (FIFO) principle, which means that the first element inserted is the first one to be removed. A queue implemented using a linked list is a linked list in which insertion is performed at one end of the list, called the “rear” of the queue, and deletion is performed at the other end of the list, called the “front” of the queue.
Here is an example implementation of a queue using a singly linked list in Python:
class Node:
def __init__(self, value):
self.value = value
self.next = None
class Queue:
def __init__(self):
self.front = None
self.rear = None
def enqueue(self, value):
new_node = Node(value)
if not self.rear:
self.front = new_node
self.rear = new_node
else:
self.rear.next = new_node
self.rear = new_node
def dequeue(self):
if not self.front:
return None
value = self.front.value
if self.front == self.rear:
self.front = None
self.rear = None
else:
self.front = self.front.next
return value
def peek(self):
if not self.front:
return None
return self.front.value
def is_empty(self):
return self.front is None
In this implementation, the Queue
class has two instance variables front
and rear
that point to the front and rear of the queue, respectively. When we enqueue a new element into the queue, we create a new node with the given value and set its next
attribute to None
. If the queue is empty, we set both front
and rear
to be the new node. Otherwise, we set the next
attribute of the current rear
node to be the new node, and update rear
to point to the new node. When we dequeue an element from the queue, we return the value of the front
node and update front
to point to the next node in the queue. If the queue becomes empty after the dequeue, we set both front
and rear
to be None
. The peek
method returns the value of the front
node without removing it from the queue. Finally, the is_empty
method checks if the front
node is None
to determine if the queue is empty.
- Question 18
What is a priority queue and how is it implemented?
- Answer
A priority queue is a special type of queue where each element has a priority assigned to it, and the element with the highest priority is dequeued first. In other words, elements are not dequeued in the order in which they are added, but in order of their priority.
A priority queue can be implemented using various data structures such as an array, a linked list, a binary heap, or a balanced binary search tree. One common implementation is using a binary heap.
A binary heap is a binary tree with the following properties:
The tree is a complete binary tree, which means that all levels of the tree are completely filled except possibly for the last level, which is filled from left to right.
For a min-heap, the value of each node is greater than or equal to the value of its parent node. For a max-heap, the value of each node is less than or equal to the value of its parent node.
Here is an example implementation of a priority queue using a min-heap in Python:
import heapq
class PriorityQueue:
def __init__(self):
self.heap = []
self.index = 0
def push(self, item, priority):
heapq.heappush(self.heap, (priority, self.index, item))
self.index += 1
def pop(self):
return heapq.heappop(self.heap)[-1]
In this implementation, we use the built-in heapq
module in Python to create a min-heap. The push
method takes two arguments: the item to be inserted and its priority. We add the item to the heap as a tuple with three elements: its priority, an index value that is incremented for each insertion, and the item itself. The use of the index value ensures that items with the same priority are dequeued in the order in which they were added. The pop
method removes and returns the item with the highest priority from the heap.
- Question 19
What is a binary search tree and how is it implemented?
- Answer
A binary search tree (BST) is a binary tree where each node has at most two children, and the value of each node is greater than or equal to the values in its left subtree and less than or equal to the values in its right subtree. In other words, the left subtree contains only nodes with values less than or equal to the node’s value, and the right subtree contains only nodes with values greater than or equal to the node’s value.
BSTs are commonly used to implement dictionaries, where keys are associated with values. The keys are stored in the BST in sorted order, which allows for efficient searching, insertion, and deletion of key-value pairs.
Here is an example implementation of a BST in Python:
class Node:
def __init__(self, key, value):
self.key = key
self.value = value
self.left = None
self.right = None
class BST:
def __init__(self):
self.root = None
def insert(self, key, value):
self.root = self._insert(self.root, key, value)
def _insert(self, node, key, value):
if node is None:
return Node(key, value)
if key < node.key:
node.left = self._insert(node.left, key, value)
elif key > node.key:
node.right = self._insert(node.right, key, value)
else:
node.value = value
return node
def search(self, key):
node = self.root
while node is not None:
if key < node.key:
node = node.left
elif key > node.key:
node = node.right
else:
return node.value
return None
def delete(self, key):
self.root = self._delete(self.root, key)
def _delete(self, node, key):
if node is None:
return None
if key < node.key:
node.left = self._delete(node.left, key)
elif key > node.key:
node.right = self._delete(node.right, key)
else:
if node.left is None:
return node.right
elif node.right is None:
return node.left
else:
successor = self._min_node(node.right)
node.key = successor.key
node.value = successor.value
node.right = self._delete(node.right, successor.key)
return node
def _min_node(self, node):
while node.left is not None:
node = node.left
return node
In this implementation, we define a Node
class to represent each node in the tree, with key
and value
attributes for the key-value pairs. We also define a BST
class to represent the entire tree, with methods for insertion, searching, and deletion.
The insert
method takes a key
and a value
and inserts them into the tree, following the BST property. The _insert
method is a helper function that recursively inserts the new node into the correct position in the tree.
The search
method takes a key
and returns the corresponding value in the tree, if it exists. The _delete
method deletes a node with the given key
from the tree, also following the BST property. If the node has two children, we find the successor node (i.e., the node with the smallest key in its right subtree) and replace the current node’s key and value with the successor’s key and value before deleting the successor node.
- Question 20
What is an AVL tree and how is it implemented?
- Answer
An AVL tree is a self-balancing binary search tree where the heights of the left and right subtrees of any node differ by at most 1. It is named after its inventors, Adelson-Velsky and Landis. The balancing of the tree is done by performing rotations of subtrees.
AVL trees are implemented similar to binary search trees, but with additional operations to balance the tree. Each node in the AVL tree contains a value, a left child pointer, a right child pointer, and a balance factor. The balance factor is the difference between the height of the left subtree and the height of the right subtree, and it can be -1, 0, or 1.
Insertion and deletion operations in an AVL tree are performed as follows:
Insertion: When a new node is inserted, the balance factor of each node on the path from the root to the new node is updated, and the tree is rebalanced by performing rotations if necessary. There are four possible cases for rebalancing, depending on the balance factors of the nodes involved. These cases are known as LL, LR, RR, and RL rotations.
Deletion: When a node is deleted, the balance factor of each node on the path from the deleted node to the root is updated, and the tree is rebalanced by performing rotations if necessary. Again, there are four possible cases for rebalancing.
AVL trees ensure that the height of the tree is logarithmic in the number of nodes, which guarantees efficient search, insertion, and deletion operations. The time complexity of these operations is O(log n), where n is the number of nodes in the tree. However, the extra overhead of maintaining balance makes AVL trees less efficient than simple binary search trees for small datasets or for datasets with infrequent updates.