All Data Structures
Advanced lists
Advanced Lists
1. Memory-Efficient Doubly Linked List
class MENode:
def __init__(self, val=0):
self.val = val
self.npx = 0 # XOR of next and prev addresses
class MemoryEfficientDLL:
def __init__(self):
self.head = None
self.nodes = [] # Keep references to prevent garbage collection
def _get_addr(self, node):
"""Get memory address of node"""
return id(node) if node else 0
def _xor(self, a, b):
"""XOR of two node addresses"""
return a ^ b
def insert_beginning(self, val):
new_node = MENode(val)
if not self.head:
self.head = new_node
else:
new_node.npx = self._get_addr(self.head)
self.head.npx = self._xor(self._get_addr(new_node),
self.head.npx)
self.head = new_node
self.nodes.append(new_node)
def traverse(self):
if not self.head:
return []
result = []
curr = self.head
prev_addr = 0
while curr:
result.append(curr.val)
next_addr = self._xor(prev_addr, curr.npx)
if not next_addr:
break
# Find next node
prev_addr = self._get_addr(curr)
curr = self._find_node(next_addr)
return result
def _find_node(self, addr):
"""Find node by memory address"""
for node in self.nodes:
if self._get_addr(node) == addr:
return node
return None
2. XOR Linked List
class XORNode:
def __init__(self, val=0):
self.val = val
self.npx = None # XOR of next and prev pointers
class XORLinkedList:
def __init__(self):
self.head = None
self.tail = None
self.nodes = [] # Keep references
def insert(self, val):
new_node = XORNode(val)
self.nodes.append(new_node)
if not self.head:
self.head = new_node
self.tail = new_node
return
# Update tail's npx
self.tail.npx = self._xor(self._get_ptr(self.tail.npx),
self._get_ptr(new_node))
# Set new node's npx
new_node.npx = self._get_ptr(self.tail)
self.tail = new_node
def _xor(self, a, b):
return 0 if a is None and b is None else (
a if b is None else (
b if a is None else
a ^ b))
def _get_ptr(self, node):
return id(node) if node else 0
def traverse_forward(self):
if not self.head:
return []
result = []
curr = self.head
prev_ptr = 0
while curr:
result.append(curr.val)
next_ptr = self._xor(prev_ptr, curr.npx)
if not next_ptr:
break
prev_ptr = self._get_ptr(curr)
curr = self._find_node(next_ptr)
return result
3. Skip List (Optimized)
import random
class SkipNode:
__slots__ = ['val', 'next', 'width'] # Memory optimization
def __init__(self, val, level):
self.val = val
self.next = [None] * level
self.width = [1] * level # Distance to next node at each level
class OptimizedSkipList:
def __init__(self, max_level=16):
self.max_level = max_level
self.head = SkipNode(float('-inf'), max_level)
self.level = 0
self.size = 0
def _random_level(self):
level = 0
while random.random() < 0.5 and level < self.max_level - 1:
level += 1
return level
def insert(self, val):
update = [None] * self.max_level
width_update = [0] * self.max_level
current = self.head
# Calculate width updates
for i in range(self.level, -1, -1):
while (current.next[i] and
current.next[i].val < val):
width_update[i] += current.width[i]
current = current.next[i]
update[i] = current
level = self._random_level()
if level > self.level:
for i in range(self.level + 1, level + 1):
update[i] = self.head
width_update[i] = self.size
self.level = level
new_node = SkipNode(val, level + 1)
for i in range(level + 1):
new_node.next[i] = update[i].next[i]
update[i].next[i] = new_node
# Update widths
new_node.width[i] = (width_update[i] + 1 if update[i] == self.head
else update[i].width[i] - width_update[i])
update[i].width[i] = width_update[i] + 1
self.size += 1
def search(self, val):
current = self.head
for i in range(self.level, -1, -1):
while current.next[i] and current.next[i].val < val:
current = current.next[i]
current = current.next[0]
return current and current.val == val
4. Self-Organizing List
class SelfOrgNode:
__slots__ = ['val', 'next', 'access_count']
def __init__(self, val):
self.val = val
self.next = None
self.access_count = 0
class SelfOrganizingList:
def __init__(self, strategy='move_to_front'):
self.head = None
self.strategy = strategy
def insert(self, val):
new_node = SelfOrgNode(val)
new_node.next = self.head
self.head = new_node
def find(self, val):
if not self.head:
return None
prev = None
curr = self.head
while curr and curr.val != val:
prev = curr
curr = curr.next
if not curr:
return None
curr.access_count += 1
if self.strategy == 'move_to_front' and prev:
prev.next = curr.next
curr.next = self.head
self.head = curr
elif self.strategy == 'transpose' and prev:
if prev != self.head:
# Find previous to prev
temp = self.head
while temp.next != prev:
temp = temp.next
# Swap nodes
temp.next = curr
prev.next = curr.next
curr.next = prev
return curr.val
5. Unrolled Linked List
class UnrolledNode:
def __init__(self, max_elements=16):
self.elements = []
self.max_elements = max_elements
self.next = None
class UnrolledLinkedList:
def __init__(self, node_capacity=16):
self.head = None
self.node_capacity = node_capacity
self.size = 0
def insert(self, val, index):
if index < 0 or index > self.size:
raise IndexError("Index out of range")
if not self.head:
self.head = UnrolledNode(self.node_capacity)
self.head.elements.append(val)
self.size += 1
return
# Find the node and position
current = self.head
pos = index
while pos > len(current.elements):
pos -= len(current.elements)
current = current.next
if not current:
current = UnrolledNode(self.node_capacity)
break
current.elements.insert(pos, val)
self.size += 1
# Rebalance if necessary
self._rebalance(current)
def _rebalance(self, node):
while node and len(node.elements) > node.max_elements:
# Split node
mid = len(node.elements) // 2
new_node = UnrolledNode(self.node_capacity)
new_node.elements = node.elements[mid:]
new_node.next = node.next
node.elements = node.elements[:mid]
node.next = new_node
node = new_node
def get(self, index):
if index < 0 or index >= self.size:
raise IndexError("Index out of range")
current = self.head
pos = index
while pos >= len(current.elements):
pos -= len(current.elements)
current = current.next
return current.elements[pos]
Time Complexities
Operation | MEF DLL | XOR List | Skip List | Self-Org List | Unrolled List |
---|---|---|---|---|---|
Insert Front | O(1) | O(1) | O(log n) | O(1) | O(√n) |
Insert Middle | O(n) | O(n) | O(log n) | O(n) | O(√n) |
Delete | O(n) | O(n) | O(log n) | O(n) | O(√n) |
Search | O(n) | O(n) | O(log n) | O(n)* | O(√n) |
Space/Node | O(1) | O(1) | O(log n) | O(1) | O(√n) |
* Improves with self-organizing strategies
Space Complexities
Implementation | Space/Node | Additional Space | Total Space |
---|---|---|---|
MEF DLL | O(1) | O(n) | O(n) |
XOR List | O(1) | O(n) | O(n) |
Skip List | O(log n) | O(1) | O(n log n) |
Self-Org List | O(1) | O(1) | O(n) |
Unrolled List | O(√n) | O(1) | O(n) |
Memory Optimization Techniques
- Node Memory Management
class NodePool:
def __init__(self, size=1000):
self.pool = []
self.available = []
self.size = size
def get_node(self, node_type, *args):
if not self.available:
if len(self.pool) < self.size:
node = node_type(*args)
self.pool.append(node)
return node
else:
return self.available.pop()
return None
def return_node(self, node):
if len(self.available) < self.size:
self.available.append(node)
- Efficient Memory Layout
from array import array
class CompactList:
def __init__(self, chunk_size=16):
self.data = array('i') # integer array
self.chunk_size = chunk_size
self.chunks = []
def add_chunk(self):
new_chunk = array('i', [0] * self.chunk_size)
self.chunks.append(new_chunk)
Edge Cases to Consider
- Empty List Operations
def handle_empty_list(self):
if not self.head:
raise ValueError("Operation on empty list")
- Single Node Operations
def handle_single_node(self):
if self.head and not self.head.next:
# Special handling for single node
pass
- Boundary Conditions
def handle_boundaries(self, index):
if index < 0 or index >= self.size:
raise IndexError("Index out of range")
Performance Considerations
- Cache Optimization
def optimize_node_layout(self):
# Arrange nodes to maximize cache hits
current = self.head
cache_line = []
while current:
if len(cache_line) == 64: # Cache line size
self._rearrange_nodes(cache_line)
cache_line = []
cache_line.append(current)
current = current.next
- Batch Operations
def batch_insert(self, values, batch_size=1000):
buffer = []
for val in values:
buffer.append(val)
if len(buffer) == batch_size:
self._process_batch(buffer)
buffer = []
if buffer:
self._process_batch(buffer)
Common Pitfalls
- Memory Leaks
# Wrong
def delete_wrong(self, node):
node.next = node.next.next # Memory leak
# Correct
def delete_correct(self, node):
temp = node.next
node.next = temp.next
temp.next = None # Clean reference
- Reference Handling
# Wrong
def update_wrong(self):
self.head = self.head.next # Lost reference
# Correct
def update_correct(self):
old_head = self.head
self.head = old_head.next
old_head.next = None
- XOR Operation Safety
# Wrong
def xor_wrong(self, a, b):
return a ^ b # May cause errors with None
# Correct
def xor_correct(self, a, b):
return 0 if not (a or b) else (
a if not b else (
b if not a else a ^ b))