너비 우선 검색에서 방문한 상태 추적


10

그래서 슬라이딩 블록 퍼즐 (숫자 유형) 에 BFS를 구현하려고했습니다 . 이제 내가 주목 한 것은 4*4보드 가 있다면 상태의 수만큼 클 수 있으므로 16!미리 모든 상태를 열거 할 수는 없다는 것입니다.

그래서 내 질문은 이미 방문한 주를 어떻게 추적합니까? (각 클래스 인스턴스에는 고유 한 보드 패턴이 포함 된 클래스 보드를 사용하고 있으며 현재 단계에서 가능한 모든 단계를 열거하여 생성됩니다).

나는 인터넷에서 검색하고 분명히 그들은 단지 완료 이전 단계로 돌아가하지 않습니다, 하지만 우리가 이전에 방문한 된 모든 단계를-열거 재 너무 후 다시 다른 경로에 의해 이전 단계로 되돌아 갈 수 있습니다. 모든 주가 이미 열거되지 않은 경우 방문한 주를 추적하는 방법은 무엇입니까? (이미 현재 상태를 현재 단계와 비교하는 것은 비용이 많이 듭니다).


1
참고 :이 질문을 게시하는 데 더 적합한 스택을 생각할 수 없었습니다. 구현 세부 사항이 일반적 으로이 스택에서 환영받지 못한다는 것을 알고 있습니다.
DuttaA

2
imo 이것은 구현에 관한 것이 아니라 개념 자체이기 때문에 SE : AI에게 큰 질문입니다. 말할 것도없이이 질문은 몇 시간 만에 4 개의 정식 답변을 이끌어 냈습니다. (검색을 위해 제목을 편집하고 BFS 태그를 작성하는 자유를
보자

답변:


8

set수학적 단어로 (즉, 중복을 포함 할 수없는 모음) 수학 식을 사용하여 이미 본 상태를 저장할 수 있습니다. 이 작업을 수행 할 수 있어야하는 작업은 다음과 같습니다.

  • 요소 삽입
  • 요소가 이미 있는지 테스트

거의 모든 프로그래밍 언어는이 두 작업을 모두 일정하게 수행 할 수있는 데이터 구조를 이미 지원해야합니다 (영형(1)) 시각. 예를 들면 다음과 같습니다.

  • set 파이썬에서
  • HashSet 자바로

언뜻보기에, 당신이 보았던 모든 상태를 세트에 추가하는 것처럼 보이는 것은 메모리면에서 비싸지 만 이미 프론티어에 필요한 메모리와 비교할 때 그리 나쁘지는 않습니다. 분기 요인이, 당신의 국경은 1 방문한 노드 당 요소 (제거) 1 국경에서 노드를 "방문"하는 노드 새로운 후계자 / 어린이), 반면 세트는 1 방문한 노드 당 추가 노드.

유사 코드에서 위키피디아의 유사 코드closed_set 와 일치 하도록 이러한 세트 (이름 은)를 다음과 같이 너비 우선 검색에서 사용할 수 있습니다.

frontier = First-In-First-Out Queue
frontier.add(initial_state)

closed_set = set()

while frontier not empty:
    current = frontier.remove_next()

    if current == goal_state:
        return something

    for each child in current.generate_children()
        if child not in closed_set:    // This operation should be supported in O(1) time regardless of closed_set's current size
            frontier.add(child)

    closed_set.add(current)    // this should also run in O(1) time

(이 유사 코드의 일부 변형도 작동 할 수 있으며 상황에 따라 다소 효율적일 수 있습니다. 예를 들어, closed_set이미 하위에 하위를 추가 한 모든 노드를 포함하고 generate_children()호출 을 완전히 피할 수도 있습니다. 경우는 current이미 closed_set.)


위에서 설명한 것은이 문제를 처리하는 표준 방법입니다. 직관적으로, 나는 다른 "솔루션"이 새로운 후계자 상태의 목록을 프론티어에 추가하기 전에 항상 무작위로 만드는 것일 수 있다고 생각합니다. 이 방법을 사용하면 이전에 이미 확장 한 상태를 때때로 추가하는 문제를 피할 수는 없지만 무한 사이클에 빠질 위험을 크게 줄여야한다고 생각합니다.

주의 : 나는 항상 무한 사이클을 피한다는 것을 증명하는이 솔루션의 공식 분석을 모른다. 직관적으로 내 머리를 통해 이것을 "실행"하려고하면 일종의 작업이 필요하다고 생각되며 추가 메모리가 필요하지 않습니다. 지금 당장 생각하지 않는 엣지 사례가있을 수 있으므로 단순히 작동하지 않을 수도 있습니다. 위에서 설명한 표준 솔루션이 더 안전한 내기입니다 (더 많은 메모리 비용).


1
나는 그것을 할 수 있지만 비교 시간은 기하 급수적으로 증가하기 시작합니다
DuttaA

3
@DuttaA 비교 시간이 기하 급수적으로 증가해서는 안됩니다. 이 세트, 해시 세트 또는 선택한 언어로 호출되는 모든 것이 주어진 상태를 포함하는지 여부를 테스트 할 수 있어야합니다.에스 지속적인 계산 복잡성 영형(1), 상관없이 이미 포함 얼마나 많은 요소 . 그들은 목록이 아니며 이미 포함되어 있는지 테스트하지 않습니다.에스현재 포함 된 모든 요소와 비교하여
Dennis Soemers

1
@DuttaA 나는 세트가 어떻게 사용되는지 정확하게 설명하기 위해 의사 코드를 추가했다. 우리는 결코 전체를 반복하지 않으며 closed_set그 크기는 (점근 적) 계산 시간에 영향을 미치지 않아야합니다.
Dennis Soemers

1
실제로 나는 C ++을 사용하여 그것을하고있었습니다. 해싱에 대한 아이디어가 없습니다 ... 지금 파이썬을 사용할 것입니다 ... 답변 주셔서 감사합니다
DuttaA

3
@DuttaA C ++에서는 아마도 std :: unordered_set
Dennis Soemers

16

Dennis Soemers의 답변은 정확합니다. BFS 그래프 검색에서 방문한 상태를 추적하려면 HashSet 또는 유사한 구조를 사용해야합니다.

그러나 귀하의 질문에 대한 답변은 아닙니다. 당신이 맞습니다, 최악의 경우, BFS는 16을 저장할 것을 요구할 것입니다! 노드. 세트의 삽입 및 검사 시간이 O (1)이더라도 여전히 불합리한 양의 메모리가 필요합니다.

이 문제를 해결하려면 BFS를 사용하지 마십시오 . 가장 단순한 목표를 제외하고는 시간 과 메모리 가 모두 필요하기 때문에 가장 간단한 문제를 제외하고는 다루기 힘들다 .

훨씬 더 메모리 효율적인 알고리즘은 반복 심화 입니다. BFS의 모든 바람직한 특성을 갖지만 O (n) 메모리 만 사용합니다. 여기서 n은 가장 가까운 솔루션에 도달하기위한 이동 횟수입니다. 여전히 시간이 걸릴 수 있지만 CPU 관련 제한보다 오래 전에 메모리 제한에 도달합니다.

더 나은 방법으로, 도메인 특정 휴리스틱을 개발하고 A * 검색을 사용하십시오 . 이를 위해서는 매우 적은 수의 노드 만 검사해야하며 선형 시간에 훨씬 가까운 것으로 검색을 완료해야합니다.


2
예, 이것은 퍼즐을 효율적으로 해결하려는 사람들에게 더 실질적으로 유용한 답변입니다. 내 대답은 BFS 사용을 주장하는 사람들을위한 것입니다 (BFS를 실제로 보거나 구현하는 방법 또는 다른 이유를 배우고 싶기 때문에). BFS는 저장하지 않아도되기를 바랍니다.16!그런데 노드; 그것은 최악의 경우 일뿐이며 그 전에 솔루션을 찾을 수 있습니다.
Dennis Soemers

@DennisSoemers는 정확합니다 .. 당신도 정확합니다 .. 방금 기술을 연마하려고했습니다 ... 나중에 더 고급 검색 방법으로 이동합니다
DuttaA

BFS가 수용 가능한 로컬 솔루션을 반환 할 수있는 경우가 있습니까? (나는 81과 같은 것을 다루고 있습니다! 예측할 수없는 게임 보드 토폴로지에 비해 약한 "일반 성능.)
DukeZhou

2
@DukeZhou BFS는 일반적으로 완전한 솔루션을 찾고자 할 때만 사용됩니다. 조기에 멈추려면 다른 부분 솔루션의 상대적인 품질을 추정하는 함수가 필요하지만 그러한 함수가 있다면 A *를 대신 사용할 수 있습니다!
John Doucette

"게임 내 이동 횟수"라고 말하는 대신 "목표에 도달하기위한 최소 이동 횟수"를 권장합니다. 게임의 움직임 수는 16 가지 중 하나에서 당신을 데려다 줄 때마다 생각합니다! 반복 심화 사용보다 훨씬 많은 메모리 인 다른 상태를 나타냅니다.
NotThatGuy

7

주어진 답변은 일반적으로 사실이지만 15- 퍼즐의 BFS는 실현 가능할뿐만 아니라 2005 년에 이루어졌습니다! 접근 방식을 설명하는 논문은 여기에서 찾을 수 있습니다.

http://www.aaai.org/Papers/AAAI/2005/AAAI05-219.pdf

몇 가지 핵심 사항 :

  • 이를 위해 외부 메모리가 필요했습니다. 즉, BFS는 RAM 대신 스토리지에 하드 드라이브를 사용했습니다.
  • 상태 공간에는 서로 연결할 수없는 두 개의 구성 요소가 있으므로 실제로 15! / 2 상태 만 있습니다.
  • 상태 공간이 레벨에서 레벨로 느리게 커지기 때문에 슬라이딩 타일 퍼즐에서 작동합니다. 이는 모든 레벨에 필요한 총 메모리가 상태 공간의 전체 크기보다 훨씬 작음을 의미합니다. (이것은 상태 공간이 훨씬 빠르게 커지는 Rubik 's Cube와 같은 상태 공간과 대조됩니다.)
  • 슬라이딩 타일 퍼즐은 방향이 지정되어 있지 않으므로 현재 또는 이전 레이어의 복제본 만 걱정하면됩니다. 지정된 공간에서는 검색의 이전 레이어에서 중복을 생성하여 훨씬 더 복잡하게 만들 수 있습니다.
  • Korf의 원래 작업 (위에 링크)에서는 실제로 검색 결과를 저장하지 않았습니다. 검색은 각 레벨에 몇 개의 상태가 있는지 계산했습니다. 첫 번째 결과를 저장하려면 WMBFS와 같은 것이 필요합니다 ( http://www.cs.du.edu/~sturtevant/papers/bfs_min_write.pdf )
  • 상태가 디스크에 저장 될 때 이전 계층의 상태를 비교하는 세 가지 주요 접근 방식이 있습니다.
    • 첫 번째는 정렬 기반입니다. 후속 작업 파일 두 개를 정렬하면 파일을 선형 순서로 스캔하여 중복 항목을 찾을 수 있습니다.
    • 두 번째는 해시 기반입니다. 해시 함수를 사용하여 후속 작업을 파일로 그룹화하는 경우 전체 상태 공간보다 작은 파일을로드하여 중복을 확인할 수 있습니다. (여기에 두 가지 해시 함수가 있습니다. 하나는 상태를 파일로 보내고 다른 하나는 파일 내에서 상태를 구별하는 것입니다.)
    • 세 번째는 구조적 중복 탐지입니다. 이것은 해시 기반 감지의 한 형태이지만 중복이 모두 생성 된 후가 아니라 생성 된 즉시 복제를 확인할 수있는 방식으로 수행됩니다.

여기에 더 많은 내용이 있지만, 위의 논문은 더 자세한 내용을 제공합니다.


그것은 좋은 대답입니다 ..하지만 나와 같은 멍청한 놈에게는 아닙니다 :) ... 나는 프로그래머의 전문가가 아닙니다 ..
DuttaA

무 방향으로 다른 계층에서 중복을 피하는 데 어떻게 도움이됩니까? 확실히 원에서 3 개의 타일을 이동하여 다른 레이어의 노드로 돌아갈 수 있습니다. 지시 사항이 있으면 복제가 더 제한적이므로 복제를 피하는 데 도움이됩니다. 링크 된 논문은 중복 감지에 대해 이야기하지만 방향이 지정되지 않았거나 지시되지 않았으며 다른 수준에서 중복을 피하는 것에 대해서는 언급하지 않는 것 같습니다 (그러나 나는 간단한 스캔에서 그것을 놓칠 수는 없었습니다).
NotThatGuy

@NotThatGuy 무 방향 그래프에서 부모와 자식은 BFS에서 발견되는 깊이에서 1만큼 떨어져 있습니다. 하나를 찾으면 방향이 지정되지 않은 가장자리가 다른 쪽이 즉시 발견 될 수 있기 때문입니다. 그러나 방향 그래프에서 깊이 10의 상태는 깊이 2의 하위 항목을 생성 할 수 있습니다. 심도 2의 하위 항목은 다른 상태의 가장자리를 다시 가질 필요가 없기 때문입니다 (이로 인해 깊이 10 대신 깊이 3이 됨) .
Nathan S.

@NotThatGuy 원에서 3 개의 타일을 이동하면 사이클이 생성되지만 BFS는 동시에 양방향으로 탐색하므로 실제로는 더 얕은 깊이로 되돌아 가지 않습니다. 이 데모에는 전체 3x2 슬라이딩 타일이 표시되며주기를 추적하여주기를 추적 할 수 있습니다. movingai.com/SAS/IDA
Nathan S.

1
굉장함. SE : AI에 오신 것을 환영합니다!
DukeZhou

3

아이러니하게 대답은 "원하는 시스템을 사용하는 것"입니다. hashSet은 좋은 생각입니다. 그러나 메모리 사용에 대한 우려는 근거가 없습니다. BFS는 이러한 종류의 문제에 너무 나빠서이 문제를 해결합니다.

BFS에서는 처리되지 않은 상태의 스택을 유지해야합니다. 퍼즐을 진행함에 따라 처리하는 상태가 점점 더 달라 지므로 BFS의 각 플라이에는 표시 할 상태의 수가 대략 3으로 곱해지는 것을 볼 수 있습니다.

즉, BFS의 마지막 플라이를 처리 할 때 메모리에 적어도 16! / 3 상태가 있어야합니다. 메모리에 맞는지 확인하기 위해 사용한 방법은 이전에 방문한 목록도 메모리에 맞도록하기에 충분합니다.

다른 사람들이 지적했듯이, 이것은 사용하기 가장 좋은 알고리즘이 아닙니다. 문제에 더 적합한 알고리즘을 사용하십시오.


2

15- 퍼즐 문제는 4x4 보드에서 재생됩니다. 소스 코드에서이를 구현하는 것은 단계적으로 수행됩니다. 처음에는 게임 엔진 자체를 프로그래밍해야합니다. 이를 통해 인간 조작자가 게임을 할 수 있습니다. 15- 퍼즐 게임에는 하나의 자유 요소 만 있으며이 요소에서 동작이 실행됩니다. 게임 엔진은 왼쪽, 오른쪽, 위, 아래의 네 가지 명령을 허용합니다. 다른 조치는 허용되지 않으며이 지시 사항만으로 게임을 제어 할 수 있습니다.

게임을위한 다음 레이어는 GUI입니다. 게임 엔진을 테스트하고 손으로 게임을 해결할 수 있기 때문에 이것은 매우 중요합니다. 또한 잠재적 휴리스틱을 파악해야하기 때문에 GUI가 중요합니다. 이제 AI 자체에 대해 이야기 할 수 있습니다. AI는 게임 엔진에 명령을 보내야합니다 (왼쪽, 오른쪽, 위 및 아래). 솔버에 대한 순진한 접근 방식은 무차별 강제 검색 알고리즘입니다. 즉, AI가 목표 상태에 도달 할 때까지 임의의 명령을 전송합니다. 보다 고급 아이디어는 상태 공간을 줄이는 일종의 패턴 데이터베이스를 구현하는 것입니다. 너비 우선 검색은 직접 휴리스틱이 아니지만 시작입니다. 가능한 움직임을 시간순으로 테스트하기위한 그래프를 만드는 것과 같습니다.

기존 상태 추적은 그래프로 수행 할 수 있습니다. 각 상태는 노드이며 ID와 부모 ID가 있습니다. AI는 그래프에서 노드를 추가 및 삭제할 수 있으며 계획자는 목표에 대한 경로를 찾기 위해 그래프를 해결할 수 있습니다. 프로그래밍 관점에서 15 퍼즐의 게임 엔진은 객체이며 많은 객체의 목록은 배열 목록입니다. 그것들은 그래프 클래스에 저장됩니다. 소스 코드에서 이것을 실현하는 것은 약간 까다 롭습니다. 일반적으로 첫 번째 시도는 실패하고 프로젝트는 많은 오류를 생성합니다. 복잡성을 관리하기 위해 이러한 프로젝트는 일반적으로 학술 프로젝트에서 수행됩니다. 즉, 100 페이지 이상을 가질 수있는 논문을 작성하는 것이 주제입니다.


1

게임에 대한 접근

이사회가 가지고있는 것은 사실입니다 16!가능한 상태. 해시 세트를 사용하는 것이 그래프주기를 포함 할 수있는 그래프를 검색 할 때 중복성과 끝없는 반복을 피하기 위해 첫 해 알고리즘 과정에서 학생들이 배우는 것입니다.

그러나 최소한의 컴퓨팅주기에서 퍼즐을 완성하는 것이 목표라면 이러한 사소한 사실은 적절하지 않습니다. 너비 우선 탐색은 직교 이동 퍼즐을 완성하는 실용적인 방법이 아닙니다. 광범위한 첫 번째 검색의 매우 높은 비용은 어떤 이유로 이동 횟수가 가장 중요한 경우에만 필요합니다.

하위 시퀀스 하강

상태를 나타내는 대부분의 정점은 절대 방문하지 않으며 방문한 각 상태는 2-4 개의 발신 에지를 가질 수 있습니다. 각 블록은 초기 위치와 최종 위치를 가지며 보드는 대칭입니다. 열린 공간이 4 개의 중간 위치 중 하나 일 때 가장 큰 선택의 자유가 존재합니다. 열린 공간이 네 모서리 위치 중 하나 일 때가 가장 적습니다.

합리적인 불일치 (오류) 기능은 모든 x 불일치의 합에 모든 y 불일치의 합과 열린 공간 (중간, 모서리 , 모서리).

일련의 이동이 필요한 완료 전략을 지원하기 위해 블록이 목적지에서 일시적으로 벗어날 수 있지만, 이러한 전략이 8 개의 이동을 초과하여 평균적으로 최종 상태를 비교할 수있는 5,184 개의 순열을 생성하는 경우는 거의 없습니다. 위의 디스 패리티 기능을 사용합니다.

블록 1부터 15까지의 빈 공간과 위치가 니블 배열로 인코딩 된 경우 더하기, 빼기 및 비트 단위 연산 만 있으면 알고리즘이 빨라집니다. 시차가 0이 될 때까지 8 가지 무차별 대입 전략을 반복 할 수 있습니다.

요약

이 알고리즘은 이미 완료된 시작 상태를 제외하고 초기 상태에 관계없이 시차를 감소시키는 8 개 이동의 순열 중 하나 이상이 항상 있기 때문에 순환 할 수 없습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.