ASAC 빅데이터 분석가 7기/ASAC 일일 기록

ASAC 빅데이터 분석가 과정 8일차 - 1 (24.12.13)

junslee 2024. 12. 13. 09:32

04_cotest

13_DFS

# DFS예제!!!
===> 깊이 중심!!!
         Stack으로 처리를 한다!!!


# 0) 탐색을 지도에서 해야하므로 --> 지도를 코드화!!!!
# ==> 인접리스트 방식 표현을 하자!!!!
#     1) 파이썬 리스트 vs 2) 파이썬 Dict
# ---> 선택 : 파이썬 리스트!!!!!!
#             1번 도시부터네;;;
graph_list = [
    [], # <--- 가상의 0번 도시가 있따고 가정,
    [2,3,8], # <--- 1번 도시의 연결된 도시(직접)
    [1,7],
    [1,4,5],
    [3,5],
    [3,4],
    [7],
    [2,6,8],
    [1,7]
]
graph_list
[[], [2, 3, 8], [1, 7], [1, 4, 5], [3, 5], [3, 4], [7], [2, 6, 8], [1, 7]]
graph_list[3]
# 3번 도시에 바로 연결된 도시들은 뭐에요?
# --> 구체적은 연결된 도시를 찾을려면
#     직접 찾아 해매야 합니다!!!!
[1, 4, 5]
graph_list[2]
[1, 7]


# 탐색에 DFS 함수
입력 : 지도 , 시작점(1)
       -- 필요한 변수 세팅 : 방문할 곳 리스트 To Do List
       --                               방문한 곳 리스트 Done List
    ==> 방문할 곳 리스트 : 시작점에 대한 정보를 추가...
사무실에 나와서 출발!!!!!!!
큰틀 : 방문할 곳 리스트가 하나도 없을 때 까지!!!! ==> 횟수X, 조건:while
         구현) while 리스트:빈 리스트가 될 때 까지....:물론 갯수 등으로 할 수도 있는데,,굳이....
                 --> 방문할 곳을 어떻게 처리 : Stack --> 맨 뒤 pop()
                 # 처음 방문한 곳이면 할 일 : 2가지 --> 도장/또 할일(방문할곳) 부여 받아야 함!!!
                1) 여기가 내가 처음 온 곳인가? --> 도장 받기
                     DoneList.append(뒤로 추가)
                2) 여기서 연결된 곳은 어딘가요?--> 또 방문할 곳 부여 받음!!!
                    ToDoList.append(뒤로 추가)

 

입력 : 지도, 시작점
기능 : DFS방식으로 주어진 지도를 탐색/방문하세요!!!
출력 : 방문 기록을 제출!!!!


def dfs_m1( graph, start):
    # 초기 변수들을 세팅!!!
    # - 1) 방문할 곳 리스트 : To Do List
    # - 2) 방문한 곳 리스트 : Done List
    need_visit = list() # []
    visited    = list()
    # ---> 스스로 일 할 준비를 세팅

    # --> 팀장한테 가서 어디서 출발할까요 ==> 방문할 곳에 추가!!!!
    need_visit.append(start)
    #########################

    # 출발!!!
    # ===> 언제까지 이거 하면 되지?? need_visit 비울 때 까지!!!!
    #       몇 번으로 끝나지는 아무도 몰라요;;;
    #        단, 퇴근 조건은 암!!! 리스트 비울 때 까지 ==> while
    while need_visit:
        # 1) 나 어디로 갈지 : 방문할 곳 꺼내서 : Stack-->맨 뒤
        node = need_visit.pop()
        # 2) node 내가 이미 갔던 곳인지 // 신규 방문지 체크!!!!
        #   ==> 갔던 곳이면 : pass
        #   ==> 신규 방문지 : 도장찍고 & 새로운 미션 부여 받아야 함!!
        #                                 (여기서 바로 연결된 곳들)
        if node not in visited: # <--- 신규 방문지!!!!
            # 2-1) 도장찍고
            visited.append( node )
            # 2-2) 할일부여 받음!! : 연결된 도시들 정보를 할 일에 추가..
            # graph[1] = [2,3,8]
            need_visit.extend( graph[node])
    return visited
def dfs_m1( graph, start):
    need_visit = list() # []
    visited    = list()
    need_visit.append(start)
    while need_visit:
        node = need_visit.pop()
        if node not in visited: # <--- 신규 방문지!!!!
            visited.append( node )
            need_visit.extend( graph[node])
    return visited
dfs_m1(graph_list, 1)
[1, 8, 7, 6, 2, 3, 5, 4]

원래 그림상 의도 : 1 -- >2 ---> 7 --~~~ : 동일한 연결에서는
                                                               도시 번호가 작은 애들먼저..
실제 위의 코드   : 1 ---> 8 --> 7 ~~~   : 동일한 연결에서
                                                             도시 번호가 큰 친구 우선으로 방문!!!
이유 : 처음 방문지 1번에 가서...물어봐을때..
           지도가 : 리스트로 표현하는 과정에 [2,3,8]
                         정리된 상태에서 할 일에 추가를 해서...
                         stack으로 꺼내다보니 8번이 먼저 꺼내져서,,
                         동일한 조건 하에서는 숫자 큰 것이 우선 방문하게 됨!!!


def dfs_m2( graph, start):
    need_visit = list() # []
    visited    = list()
    need_visit.append(start)
    while need_visit:
        node = need_visit.pop()
        if node not in visited: # <--- 신규 방문지!!!!
            visited.append( node )
            #### 순서를 지정 : 같은 조건이면 작은 번호를 먼저.
            #    역방향 : .reverse(), reversed(), [::-1], sort/sorted
            # ==> 조건이 있다면,,,조정을 해야 함!!!
            temp = graph[node]
            temp_reverse = list(reversed(temp))
            need_visit.extend( temp_reverse )
            # need_visit.extend( graph[node][::-1])
    return visited
dfs_m2(graph_list, 1)
[1, 2, 7, 6, 8, 3, 4, 5]

14_BFS

# BFS
===> 1번 도시를 출발한다고 하면,,,
         BFS 방식 : 스케줄링 큐 방식으로 하면,,,
         어떻게 돌아갈까???


# 0) 탐색을 지도에서 해야하므로 --> 지도를 코드화!!!!
# ==> 인접리스트 방식 표현을 하자!!!!
#     1) 파이썬 리스트 vs 2) 파이썬 Dict
# ---> 선택 : 파이썬 리스트!!!!!!
#             1번 도시부터네;;;
graph_list = [
    [], # <--- 가상의 0번 도시가 있따고 가정,
    [2,3,8], # <--- 1번 도시의 연결된 도시(직접)
    [1,7],
    [1,4,5],
    [3,5],
    [3,4],
    [7],
    [2,6,8],
    [1,7]
]
graph_list
[[], [2, 3, 8], [1, 7], [1, 4, 5], [3, 5], [3, 4], [7], [2, 6, 8], [1, 7]]


# 탐색에 BFS 함수
입력 : 지도 , 시작점(1)
       -- 필요한 변수 세팅 : 방문할 곳 리스트 To Do List
       --                               방문한 곳 리스트 Done List
    ==> 방문할 곳 리스트 : 시작점에 대한 정보를 추가...
사무실에 나와서 출발!!!!!!!
큰틀 : 방문할 곳 리스트가 하나도 없을 때 까지!!!! ==> 횟수X, 조건:while
          구현) while 리스트:빈 리스트가 될 때 까지....:물론 갯수 등으로 할 수도 있는데,,굳이....
               --> **방문할 곳을 어떻게 처리 : 큐!!!! --> 맨 뒤 pop(0)
                 # 처음 방문한 곳이면 할 일 : 2가지 --> 도장/또 할일(방문할곳) 부여 받아야 함!!!
                1) 여기가 내가 처음 온 곳인가? --> 도장 받기
                     DoneList.append(뒤로 추가)
                2) 여기서 연결된 곳은 어딘가요?--> 또 방문할 곳 부여 받음!!!
                     ToDoList.append(뒤로 추가)


def bfs_m1( graph, start):
    # 초기 변수들을 세팅!!!
    # - 1) 방문할 곳 리스트 : To Do List
    # - 2) 방문한 곳 리스트 : Done List
    need_visit = list() # []
    visited    = list()
    # ---> 스스로 일 할 준비를 세팅

    # --> 팀장한테 가서 어디서 출발할까요 ==> 방문할 곳에 추가!!!!
    need_visit.append(start)
    #########################

    # 출발!!!
    # ===> 언제까지 이거 하면 되지?? need_visit 비울 때 까지!!!!
    #       몇 번으로 끝나지는 아무도 몰라요;;;
    #        단, 퇴근 조건은 암!!! 리스트 비울 때 까지 ==> while
    while need_visit:
        # 1) 나 어디로 갈지 : 방문할 곳 꺼내서 : 큐!!!--> pop(0)
        node = need_visit.pop(0)
        # 2) node 내가 이미 갔던 곳인지 // 신규 방문지 체크!!!!
        #   ==> 갔던 곳이면 : pass
        #   ==> 신규 방문지 : 도장찍고 & 새로운 미션 부여 받아야 함!!
        #                                 (여기서 바로 연결된 곳들)
        if node not in visited: # <--- 신규 방문지!!!!
            # 2-1) 도장찍고
            visited.append( node )
            # 2-2) 할일부여 받음!! : 연결된 도시들 정보를 할 일에 추가..
            # graph[1] = [2,3,8]
            need_visit.extend( graph[node])
    return visited

 

bfs_m1(graph_list, 1)
[1, 2, 3, 8, 7, 4, 5, 6]

15_DFS_BFS_정리

# 중요한 내용 : 탐색이라는 것을 어떻게 코드화!!!
===> 변수들을 세팅을 해야 함!!( 해야할 목록, 한 목록)
      해야할 일을 다 할 때 까지 주구장창 : while
         - 지금 해야할 일이 신규 일인지 아닌지 체크
         - 신규 일 : 했다고 도장찍고!!!!
                     다시 새로운 미션을 부여 받아서 할 일에 추가!!!
-----------주어진 상황에 모든 할 일/방문을 다 할 수 있음!!!


할일 : 이런 기본적인 패턴/ 내용에 대해서 다양하게 가능함!!
      ==> 다양한 주어진 문제에 적용하는 연습!!!!
          기본 내용과 100프로 일치하는 경우는 거의 XXX
          기본적인 내용과 주어진 문제의 차이점&공통점!!!=-> 탐색

중요한 내용
- DFS : 해야할 일에 대한 관리 ==> Stack
                                   (할 일은 맨 뒤에 추가)
                                   (선택은 맨 뒤에서 뽑아서)
- BFS : 해야할 일에 대한 관리 ==> Queue
                                   (할 일은 맨 뒤에 추가.)
                                   (선택은 맨 앞에서 뽑아서)
==> 모든 곳들은 다 방문을 할 수 있음!!!!
but 방문하는 스타일이 달라진다!!!!
     DFS : 꼬리에 꼬리를 물면서 진행---> 깊이 중심(출발점에 일단 멀리 도망)
     BFS : 맨 앞에서 부터 하나씩 순차적인 해결 ==> 시작점 중심 방사형...너비중심


# 참고) 주어진 탐색같은 경우에 대해서 "지도형 문제"
     ==> 추상적인 지도를 어떻게 코드화 할 것인가!!!
         - 인접행렬방식 : 파이썬코드화(리스트): 가로/세로 딱 맞춰서 행렬처럼
         - 인접리스트방식 : 파이썬코드화(리스트/Dict): 정수인덱스/키 중심으로 연결성들을 리스트업!!!
     ==> 모양이 가로/세로 딱 맞춰진 케이스 : 행렬
                             값의 길이가 각양 각색 : 리스트

중요!!!!!) 지도만 탐색을 하는 것이 아니라 다양한 곳에 적용이 됨!!!


# 참고) 파이썬으로 Queue은 운영을 하면 속도이슈 무조건 발생!!!
===> 파이썬 자체의 자료구조 리슽가 태생적인 속도 한계!!!!!!
===> 코테에서는 주로 deque 패키지/자료형을 사용하면 해결이 됨!!!
         코테/알고리즘 대회 : deque 패키지
참고) 파이썬 리스트/numpy array 등 쓰는데,,조금이라도 빠르게 핬겠다
        : deque!!!!


# collections 패키지의 여러 모듈 중 하나1!!
from collections import deque

 

a_list = [1,2,3]
a_list
[1,2,3]
a_dq = deque([1,2,3])
a_dq
deque([1,2,3])
# 원소 추가 : 무조건 맨 뒤 append
a_dq.append(4)
a_dq
deque([1,2,3,4])
 # 리스트 : 원소 빼기 : pop() --> 맨 뒤에서 제거
a_list
[1,2,3]
a_list.pop()
3
a_list
[1,2]
a_dq
deque([1,2,3,4])
a_dq.pop()
4
a_dq
deque([1,2,3])

# 맨 앞에서 원서 뺄 때 : pop(0)

a_list
[1,2]
a_list.pop(0)
1
a_list
[2]
a_dq,pop(0) # --> 에러 발생!!

// TypeError

a_dq.popleft()
1
a_dq
deque([2,3])

# deque로 하면,,
 stack처럼 사용하려면 처음 세팅만 deque하면 끝!!!!
 Queue처럼 사용하려면 처음 세팅 deque로 하고 pop(0)-->popleft()

 

현실적으로
코테에서는 내가 stack으로 하는 것은 굳이 deque 변경안 해도 됨!!!
                           Queue로 하는 것은 무조건!! deque로 해야 함!!!!!!


# 실험 : list vs deque : stack
from collections import deque
import time

N = 1000 # 초기에 넣어둘 원소의 갯수
TIMES = 100 # 실험 횟수
M = 1000 # M 번 pop, M번 append

# 일단 2개의 자료형에 N 개의 원소들을 넣어두겠습니다.
list_stack = list([i  for i in range(0,N)])
deque_stack = deque([i  for i in range(0,N)])

# List--> Stack
s_time = time.time()
for _ in range(0, TIMES):
    # M개의 원소들을 append, pop
    for i in range(0,M):
        list_stack.append(i)
        list_stack.pop()
print("list",time.time()- s_time)

# deque--> Stack
s_time = time.time()
for _ in range(0, TIMES):
    # M개의 원소들을 append, pop
    for i in range(0,M):
        deque_stack.append(i)
        deque_stack.pop()
print("list",time.time()- s_time)
list 0.019702672958374023
list 0.01571798324584961

N = 1000 # 초기에 넣어둘 원소의 갯수
TIMES = 100 # 실험 횟수
M = 1000 # M 번 pop, M번 append

# 일단 2개의 자료형에 N 개의 원소들을 넣어두겠습니다.
list_queue = list([i  for i in range(0,N)])
deque_queue = deque([i  for i in range(0,N)])

# List--> Queue
s_time = time.time()
for _ in range(0, TIMES):
    # M개의 원소들을 append, pop
    for i in range(0,M):
        list_queue.append(i)
        list_queue.pop(0)
print("list",time.time()- s_time)

# deque--> Queue
s_time = time.time()
for _ in range(0, TIMES):
    # M개의 원소들을 append, pop
    for i in range(0,M):
        deque_queue.append(i)
        deque_queue.popleft()
print("list",time.time()- s_time)
list 0.03551363945007324
list 0.01670551300048828

# BFS 수정

def bfs_list( graph, start):
    need_visit = list() # []
    visited    = list()
    need_visit.append(start)
    while need_visit:
        node = need_visit.pop(0)
        if node not in visited: # <--- 신규 방문지!!!!
            visited.append( node )
            need_visit.extend( graph[node])
    return visited
def bfs_deque( graph, start):
    need_visit = deque([]) # ****
    visited    = deque([]) # ****
    need_visit.append(start)
    while need_visit:
        #node = need_visit.pop(0)
        node = need_visit.popleft() # ***********
        if node not in visited: # <--- 신규 방문지!!!!
            visited.append( node )
            need_visit.extend( graph[node])
    return visited