---
# System prepended metadata

title: N-queens problem

---

Đây là bài toán tìm vị trí và đặt N quân hậu trên bàn cờ NxN để các quân cờ không tấn công nhau.
# Find path
Tự đặt các quân cờ đến khi thỏa đề bài.
Idea: 
result sẽ có dạng:[1, 6, 4, 7, 0, 3, 5, 2] với index là vị trí dòng, số tại index đó là vị trí cột.
1. Ta sẽ bắt đầu từ row 0 tới row N.
2. Node có cấu trúc bao gồm chi phí và kết quả hiện tại.
3. Tại mỗi row sẽ kiểm tra các cột, các cột nào valid thì đều add(node chứa cột ấy) vào hàng đợi, trong hàng đợi sẽ sắp xếp lại theo thứ tự chi phí thấp nhất đến chi phí cao nhất.
4. Cứ cho loop đến khi ra kết quả hoặc hàng đợi rỗng thì trả về False.


Source:
```
from queue import PriorityQueue
class Node:
    def __init__(self,state,cost):
        self.state=state
        self.cost=cost

    def __lt__(self, other):
        return self.cost < other.cost

def check_valid(board, row, col):
    # Kiểm tra xem vị trí (row, col) có hợp lệ để đặt quân hậu không
    for i in range(row):
        #same column
        #2 hang cheo
        if board[i] == col or \
           board[i] - i == col - row or \
           board[i] + i == col + row:
            return False
    return True

def ucs(N):
   initial_state = [-1] * N
   count=0
   node=Node(initial_state,0)
   frontier=PriorityQueue()
   frontier.put(node)

   while 1:
       if frontier.empty(): 
           return False
       node = frontier.get()
       state=node.state
       cost=node.cost

       #found solution
       if state.count(-1)==0: 
           return state
       
       row = state.index(-1)
       #explored.append()
       for col in range(N):
            if check_valid(state, row, col):
                new_state = state[:]
                new_state[row] = col
                new_cost = cost + 1
                new_node = Node(new_state, new_cost)
                frontier.put(new_node)

n = 100
solution = ucs(n)

if solution is None:
    print("No solution.")
else:
    print("Solution:")
    for row in range(n):
        line = ""
        for col in range(n):
            if solution[row] == col:
                line += "x "
            else:
                line += "- "
        print(line)
        
Solution:
[1, 6, 4, 7, 0, 3, 5, 2]
- x - - - - - - 
- - - - - - x - 
- - - - x - - - 
- - - - - - - x 
x - - - - - - - 
- - - x - - - - 
- - - - - x - - 
- - x - - - - - 
```
# Uniform-cost search (UCS)
Các quân cờ được đặt sẵn trên bàn (initial state), di chuyển quân cờ (change state) chi phí là 1, sao cho thỏa đề với chi phí thấp nhất.

```
import tracemalloc
import time
import random
class Node:
    def __init__(self,state,cost,nextAction):
        self.state=state
        self.cost=cost
        self.nextAction=nextAction
def check_valid(board, row, col):
    # Kiểm tra xem vị trí (row, col) có hợp lệ để đặt quân hậu không
    for i in range(row):
        #same column
        #2 hang cheo
        if board[i] == col or \
           board[i] - i == col - row or \
           board[i] + i == col + row:
            return False
    return True
def insert(ls,a):
    for i in range(len(ls)):
        if ls[i].cost>a.cost:
            ls.insert(i,a)
            return ls
    ls.append(a)
def check_exist_and_add(ls,a):
    for i in ls:
        if i.state == a.state:
            if i.cost != a.cost:
                ls.insert(i,a)
                ls.pop(i+1)
    insert(ls,a)
def check_solution(ls):
    for row in range(len(ls)):
        if(check_valid(ls,row,ls[row])==False):return False
    return True
def ucs(N):
   initial_state = []
   for i in range(N):
    initial_state.append(random.randint(0,N-1))
   count=0
   node=Node(initial_state,0,0)
   frontier=[]
   insert(frontier,node)

   while 1:
       if len(frontier)==0: 
           return False
       node = frontier[0]
       frontier.pop(0)
       state=node.state
       cost=node.cost
       action=node.nextAction

       #found solution
       if check_solution(state): 
           return state
       if action==N-1:
        continue
    #    print(state)

       row=action
       #explored.append()
       for col in range(N):
                new_state = state[:]
                new_cost=cost+abs(col-state[row])
                new_state[row] = col
                new_node = Node(new_state, new_cost,action+1)
                check_exist_and_add(frontier,new_node)                 
n=7
solution = ucs(n)
print(solution)
# print(check_solution([2, 0, 3, 1]))
if solution is None:
    print("No solution.")
else:
    print("Solution:")
    print(solution)
    for row in range(n):
        line = ""
        for col in range(n):
            if solution[row] == col:
                line += "x "
            else:
                line += "- "
        print(line)
```
# A*
Giống ucs nhưng có thêm hàm heuristic làm tăng tốc độ của thuật toán. Với heuristic cost là số các quân hậu tấn công nhau (attacking_pairs).
```
import tracemalloc
import time
import random
class Node:
    def __init__(self,state,cost,nextAction):
        self.state=state
        self.cost=cost
        self.nextAction=nextAction

def check_valid(board, row, col):
    # Kiểm tra xem vị trí (row, col) có hợp lệ để đặt quân hậu không
    for i in range(row):
        #same column
        #2 hang cheo
        if board[i] == col or \
           board[i] - i == col - row or \
           board[i] + i == col + row:
            return False
    return True
def insert(ls,a):
    for i in range(len(ls)):
        if ls[i].cost>a.cost:
            ls.insert(i,a)
            return ls
    ls.append(a)
def check_exist_and_add(ls,a):
    for i in ls:
        if i.state == a.state:
            if i.cost != a.cost:
                ls.insert(i,a)
                ls.pop(i+1)
    insert(ls,a)
def check_solution(ls):
    for row in range(len(ls)):
        if(check_valid(ls,row,ls[row])==False):return False
    return True
def heuristic(board,row,col):
    # Hàm heuristic: Số cặp quân hậu tấn công nhau
    n = len(board)
    attacking_pairs = 0
    for i in range(n):
      if i==row: continue
      if board[i] == col or \
         board[i] - i == col - row or \
         board[i] + i == col + row:
          attacking_pairs += 1
    return attacking_pairs
def A_star(N):
   initial_state = []
   for i in range(N):
    initial_state.append(random.randint(0,N-1))
   count=0
   node=Node(initial_state,0,0)
   frontier=[]
   insert(frontier,node)

   while 1:
       if len(frontier)==0: 
           return False
       node = frontier[0]
       frontier.pop(0)
       state=node.state
       cost=node.cost
       action=node.nextAction

       #found solution
       if check_solution(state): 
           return state
       if action==N-1:
        continue
    #    print(state)

       row=action
       #explored.append()
       for col in range(N):
                new_state = state[:]
                new_cost=cost+abs(col-state[row])
                new_state[row] = col
                heuristic_cost=heuristic(new_state,row,col)
                new_node = Node(new_state, new_cost+heuristic_cost,action+1)
                check_exist_and_add(frontier,new_node)
                     
n=7
solution = A_star(n)
if solution is None:
    print("No solution.")
else:
    print("Solution:")
    print(solution)
    for row in range(n):
        line = ""
        for col in range(n):
            if solution[row] == col:
                line += "x "
            else:
                line += "- "
        print(line)
```

# Genetic
### Comment
* Random exploration find solutions that local search does not: Can solve “hard” problem
* Rely on very little domain knowledge
### Idea
![](https://hackmd.io/_uploads/r1KR0eT_3.png)
![](https://hackmd.io/_uploads/ryvzJb6dn.png)
* Population: a set of k randomly generated states to begin with
* Fitness: The number of non-attacking pairs of queens (min =0, max = 8×7/2 = 28)
* Crossover: combine 2 gen of parent
* Mutation: Each location is subject to random mutation with a small independent probability.

```
import random

class Individual:
    def __init__(self, state):
        self.state = state
        self.fitness = self.calculate_fitness()

    def calculate_fitness(self):
        # Tính fitness của cá thể
        n = len(self.state)
        attacking_pairs = 0
        for i in range(n - 1):
            for j in range(i + 1, n):
                if self.state[i] == self.state[j] or \
                   self.state[i] - i == self.state[j] - j or \
                   self.state[i] + i == self.state[j] + j:
                    attacking_pairs += 1
        return attacking_pairs

def create_individual(n):
    state = random.sample(range(n), n)
    return Individual(state)

def create_population(n, population_size):
    population = []
    for i in range(population_size):
        individual = create_individual(n)
        population.append(individual)
    return population

def crossover(parent1, parent2):
    n = len(parent1.state)
    cut = random.randint(0, n - 1)
    child_state = parent1.state[:cut] + parent2.state[cut:]
    child = Individual(child_state)
    return child

def mutate(individual):
    n = len(individual.state)
    swap_indices = random.sample(range(n), 2)
    individual.state[swap_indices[0]], individual.state[swap_indices[1]] = \
        individual.state[swap_indices[1]], individual.state[swap_indices[0]]
    individual.fitness = individual.calculate_fitness()

def genetic_algorithm(n, population_size, max_generations):
    population = create_population(n, population_size)
    generation = 0

    while generation < max_generations:
        # Sort population based on fitness
        population.sort(key=lambda individual: individual.fitness)

        if population[0].fitness == 0:
            # found solution
            return population[0].state

        new_population = []

        #Add the best individual to new population
        new_population.append(population[0])

        while len(new_population) < population_size:
            # choose parent
            parent1 = random.choice(population[:population_size // 2])
            parent2 = random.choice(population[:population_size // 2])
            child = crossover(parent1, parent2)
            mutate(child)
            new_population.append(child)

        population = new_population
        generation += 1

    return None

n = 8
population_size = 100
max_generations = 1000
solution = genetic_algorithm(n, population_size, max_generations)

if solution is None:
    print("No solution.")
else:
    print("Solution:")
    print(solution)
    for row in range(n):
        line = ""
        for col in range(n):
            if solution[row] == col:
                line += "x "
            else:
                line += "- "
        print(line)
```