Memory-bounded search ( Memory Bounded Heuristic Search ) in AI

Last Updated : 21 Aug, 2025

Memory-bounded search is a heuristic search technique in artificial intelligence that solves problems using a strict, fixed amount of memory. By dynamically managing and pruning its search data, it efficiently finds solutions even on devices with limited resources, making it ideal for real-time and embedded systems.

Let's implement the Memory-Bound Search for 8-Puzzle,

Step 1: Define the Node Class

Each node in the search tree keeps track of the current state, its parent (for reconstructing the solution path) and the action taken to reach it.
Each node keeps:

  • Current puzzle state
  • Parent node (for reconstructing solution path)
  • Action leading to this state
Python
class Node:
    def __init__(self, state, parent=None, action=None):
        self.state = state
        self.parent = parent
        self.action = action

Step 2: Manhattan Distance Heuristic

This function estimates the cost to goal (lower = closer).

Python
def heuristic(state):
    distance = 0
    for i in range(9):
        if state[i] != 0:
            distance += abs(i // 3 - (state[i] - 1) //
                            3) + abs(i % 3 - (state[i] - 1) % 3)
    return distance

Step 3: Memory Usage Accounting

  • Counts nodes in both open & closed lists.
  • Ensures search stays within memory limits.
Python
def memory_usage(open_list, closed_list):
    return len(open_list) + len(closed_list)

Step 4: Prune Open List When Memory Limit Exceeded

When memory exceeded:

  • Sort open list by heuristic (worst first)
  • Remove bottom half (least promising nodes)
Python
def prune_memory(open_list, closed_list):
    open_list.sort(key=lambda x: heuristic(x.state), reverse=True)
    open_list[:] = open_list[:len(open_list) // 2]

Step 5: Choose the Current Best Node

Pick the node that looks most promising to expand next.

Python
def select_best_node(open_list):
    return min(open_list, key=lambda x: heuristic(x.state))

Step 6: Define the Goal Test

Check if a node’s state matches the desired goal and return True.

Python
def is_goal(node):
    return node.state == goal_state

Step 7: Generate Successor States

  • Produces new nodes for each valid move (sliding the blank).
  • Each move results in a new puzzle state.
Python
def generate_successors(node):
    successors = []
    zero_index = node.state.index(0)
    for move in moves[zero_index]:
        new_state = list(node.state)
        new_state[zero_index], new_state[move] = new_state[move], new_state[zero_index]
        successors.append(Node(tuple(new_state), parent=node, action=move))
    return successors

Step 8: Avoid Redundant States

Skip nodes that have already been seen.

Python
def redundant(successor, open_list, closed_list):
    for node in open_list + closed_list:
        if node.state == successor.state:
            return True
    return False

Step 9: Memory-Bounded A-like Search Routine

Implements the memory management and pruning logic.

Repeat these steps in main loop:

  • Prune if above memory limit.
  • Stop if open list empty.
  • Expand best node.
  • If goal, return node.
  • Otherwise, move node to closed, add new successors if not redundant.
Python
def MemoryBoundedSearch(initial_state, memory_limit):
    node = Node(initial_state)
    open_list = [node]
    closed_list = []

    while open_list:
        if memory_usage(open_list, closed_list) > memory_limit:
            prune_memory(open_list, closed_list)
        if not open_list:
            return None
        current_node = select_best_node(open_list)
        if is_goal(current_node):
            return current_node
        open_list.remove(current_node)
        closed_list.append(current_node)
        for successor in generate_successors(current_node):
            if not redundant(successor, open_list, closed_list):
                open_list.append(successor)
    return None

Step 10: Set Up Puzzle Definitions

Define the goal state and legal moves.

Python
goal_state = (1, 2, 3, 4, 5, 6, 7, 8, 0)

moves = {
    0: [1, 3],
    1: [0, 2, 4],
    2: [1, 5],
    3: [0, 4, 6],
    4: [1, 3, 5, 7],
    5: [2, 4, 8],
    6: [3, 7],
    7: [4, 6, 8],
    8: [5, 7]
}

Step 11: Test and Get Result

  • Define moves for 8-puzzle and goal state.
  • Run search with specified memory limit.
  • Print solution path if found.
Python
initial_state = (1, 2, 3, 4, 5, 6, 0, 7, 8)

print("Case: Memory Limit = 1")
goal_node = MemoryBoundedSearch(initial_state, memory_limit=1)
if goal_node:
    print("Solution found!")
    while goal_node.parent:
        print("Action:", goal_node.action)
        print("State:", goal_node.state)
        goal_node = goal_node.parent
else:
    print("Memory limit exceeded. No solution found.")

print("\nCase: Memory Limit = 10")
goal_node = MemoryBoundedSearch(initial_state, memory_limit=10)
if goal_node:
    print("Solution found!")
    while goal_node.parent:
        print("Action:", goal_node.action)
        print("State:", goal_node.state)
        goal_node = goal_node.parent
else:
    print("Memory limit exceeded. No solution found.")

Output:

Case: Memory Limit = 1
Memory limit exceeded. No solution found.

Case: Memory Limit = 10 Solution found!

Action: 8
State: (1, 2, 3, 4, 5, 6, 7, 8, 0)

Action: 7
State: (1, 2, 3, 4, 5, 6, 7, 0, 8)

Applications of Memory-Bound Search

  • Robotics: Helps robots navigate efficiently and avoid obstacles with limited memory.
  • Autonomous Vehicles: Enables real-time route planning and obstacle avoidance given onboard memory constraints.
  • Game AI: Allows AI agents in games like Chess or Go to make strong decisions within a fixed memory budget.
  • NLP Tasks: Supports tasks like translation or summarization on memory-constrained devices.
  • IoT & Embedded Devices: Facilitates complex tasks, such as image recognition, on devices with minimal memory.
  • Memory Efficiency: Works well even when available memory is tight.
  • Real-World Ready: Ideal for practical systems with hardware constraints.
  • Quality Results: Delivers good solutions using limited resources.
  • Adaptive Memory Use: Dynamically manages what to keep and what to prune, using memory wisely.

Limitations

  • Potential for Suboptimal Solutions: May overlook the best solutions due to aggressive memory pruning.
  • Added Computational Overhead: Frequent memory checks and pruning can slow down the search process.
  • Complex Implementation Requirements: Necessitates sophisticated strategies for selective memory retention and pruning.
  • Balancing Challenges: Maintaining both high solution quality and low memory usage often involves trade-offs.
Comment

Explore