A heap is a specialized tree - based data structure that satisfies the heap property. In a min - heap, for any given node, the value of the node is less than or equal to the values of its children. In a max - heap, the value of the node is greater than or equal to the values of its children. Heaps are commonly used to implement priority queues, where the element with the highest (or lowest) priority can be retrieved in $O(1)$ time after $O(log n)$ time for insertion and deletion.
A segment tree is a binary tree data structure used for storing information about intervals, or segments. It allows for efficient querying and updating of ranges in an array. Each node in the segment tree represents an interval of the original array. The root node represents the entire array, and the leaf nodes represent single elements of the array.
A Fenwick tree, also known as a binary indexed tree, is a data structure that can efficiently update elements and calculate prefix sums in an array. It uses a clever binary representation to store partial sums, allowing for $O(log n)$ time complexity for both update and query operations.
A trie, also called a prefix tree, is an ordered tree data structure used to store a dynamic set or associative array where the keys are usually strings. Each node in the trie represents a prefix of one or more strings. Tries are useful for tasks such as autocomplete, spell - checking, and searching for strings with a common prefix.
In Python, the heapq
module provides an implementation of the heap queue algorithm, also known as the priority queue algorithm. Here is an example of using a min - heap to find the k smallest elements in a list:
import heapq
nums = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
k = 3
# Convert the list into a min - heap
heapq.heapify(nums)
# Get the k smallest elements
k_smallest = heapq.nsmallest(k, nums)
print(k_smallest)
Here is a simple implementation of a segment tree for range sum queries:
class SegmentTree:
def __init__(self, arr):
self.n = len(arr)
self.tree = [0] * (4 * self.n)
self.build(arr, 0, 0, self.n - 1)
def build(self, arr, node, start, end):
if start == end:
self.tree[node] = arr[start]
else:
mid = (start + end) // 2
left_child = 2 * node + 1
right_child = 2 * node + 2
self.build(arr, left_child, start, mid)
self.build(arr, right_child, mid + 1, end)
self.tree[node] = self.tree[left_child] + self.tree[right_child]
def query(self, node, start, end, l, r):
if r < start or l > end:
return 0
if l <= start and end <= r:
return self.tree[node]
mid = (start + end) // 2
left_child = 2 * node + 1
right_child = 2 * node + 2
return self.query(left_child, start, mid, l, r) + self.query(right_child, mid + 1, end, l, r)
arr = [1, 3, 5, 7, 9, 11]
seg_tree = SegmentTree(arr)
print(seg_tree.query(0, 0, len(arr) - 1, 1, 3))
Here is an implementation of a Fenwick tree for prefix sum queries:
class FenwickTree:
def __init__(self, n):
self.n = n
self.bit = [0] * (n + 1)
def update(self, idx, val):
while idx <= self.n:
self.bit[idx] += val
idx += idx & -idx
def query(self, idx):
res = 0
while idx > 0:
res += self.bit[idx]
idx -= idx & -idx
return res
arr = [1, 2, 3, 4, 5]
n = len(arr)
fenwick_tree = FenwickTree(n)
for i in range(n):
fenwick_tree.update(i + 1, arr[i])
print(fenwick_tree.query(3))
Here is a simple implementation of a trie:
class TrieNode:
def __init__(self):
self.children = {}
self.is_end_of_word = False
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word):
node = self.root
for char in word:
if char not in node.children:
node.children[char] = TrieNode()
node = node.children[char]
node.is_end_of_word = True
def search(self, word):
node = self.root
for char in word:
if char not in node.children:
return False
node = node.children[char]
return node.is_end_of_word
trie = Trie()
trie.insert("apple")
print(trie.search("apple"))
In some cases, you may need to combine multiple data structures to solve a problem. For example, you can use a heap and a segment tree together. Suppose you have an array of elements, and you need to find the minimum element in a given range after performing some updates on the array. You can use a segment tree to perform range queries and a heap to keep track of the minimum elements during the updates.
Advanced data structures are powerful tools in competitive programming. By understanding the fundamental concepts, usage methods, common practices, and best practices of data structures such as heaps, segment trees, Fenwick trees, and tries, you can significantly improve your problem - solving skills. Python provides convenient libraries and built - in functions that make it easier to implement and use these data structures. With practice, you will be able to choose the right data structure for the right problem and write efficient and error - free code.