ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • CS50 2019 (10)recursion, 쌓이는 것
    CS50 2022. 7. 16. 16:31

    ===

    www.boostcourse.org/cs112

     

    모두를 위한 컴퓨터 과학 (CS50 2019)

    부스트코스 무료 강의

    www.boostcourse.org

    1.컴퓨팅사고

    2.C언어

    3.배열

    4.알고리즘

    5.메모리

    6.자료구조

     

    =====================

    7)재귀

    h가 3, 2, 1일때 draw(2), draw(1), draw(0)으로 실행된다. draw(0)이 실행되고 바로 return될 뿐

    최다득표자를 선거의 승자로 하거나 결선 투표로 하거나 여러명을 고르게 하거나 등의 알고리즘이 있다 생활에서도 다양하게 쓰이고 있다는 말

     

    수많은 정렬 알고리즘이 존재한다 중요한 몇가지만 볼 것, O(n^2)보다 더 나은

     

    이제부터 컴퓨터 공학을 배우면서 여러분의 생각을 흔들어 버릴 수많은 개념 중 하나를 소개할 것

    목표는 경험해보는 것 다양한 개념을 적용하다보면 점차 익숙해질 것

     

    ===

    재귀의 핵심 개념을 위해 예제를 하나 볼 것.

    첫주에 했던 것, 그땐 반복문을 강조했었다.

    이 알고리즘을 좀 다르게, 더 좋게 만들 수 있지 않을까? 물론 trade off가 있다

     

    여기서 노란색의 반복문을 지워보고 생각해보자

    남은것중에 Open to moddle of left(or right) ~ 의 이유는 남은 절반에서 찾기 위해서였음. 중요한 사실은 전화번호부의 크기를 절반으로 줄인다는 것. 알고리즘은 사실 똑같으니 의사코드를 저렇게 이것저것 다시하라고 하지 말고

    저 화면의 코드는 Search라는 함수다.

    재귀 함수는 내부에서 자기 자신을 호출합니다

    뭔말인지 모를 수도 있지만 곧 자세히 배울 것임

    저 함수 이름이 Search이고, Search left(right) half of book이라는 줄에서 Search함수를 반복적으로 호출하기 때문에 재귀

     

    특정 줄로 가라고 할 필요 없이 무엇을 할지만 알려주기. 왼편 탐색해 오른편 탐색해라고 해보자. 

    순환논리처럼 보일 수도 있다. Mike Smith를 어떻게 찾아? Mike Smith를 찾아 하는것과 같은데

    중요한 점은 단순히 같은 것을 반복시키는 게 아니라(반복문 말하는거겠지)

    Mike Smith를 이렇게 큰 전화번호부에서 찾는 데 없다면 더 작은 크기에서 다시 찾아보라고 하며

    알고리즘 다음 단계에서의 문제의 크기를 계속 절반으로 줄여나가는 겁니다. --이거를 설명하려고 저 내용으로 수업하심

    이것이 재귀라는 프로그래밍 기법의 예시이다.

    여러분의 프로그램이나 알고리듬이 스스로를 계속 호출하는 것

     

    ===

    이걸 만드려면?

    iteration.c  --반복 이라는 뜻

    *do-while은 제대로 입력하는지 확인할 때, 오늘은 안쓸게요 올바른 입력이 주어진다 가정하고

    draw함수를 만들 것

    값을 반환 안하고 출력만 하므로 void draw  --구분하라고 직관에

    입력으로 숫자 n을 받음 void draw(int h)

     

    의사코드와 비슷하게 하기 위해 i를 1로 초기화하고(0부터 시작하는 것과 똑같지만 때로는 지금처럼 1부터 시작하는 것도 좋습니다)

    i가 높이보다 작거나 같으면 계속하기  --1로 하면 작거나 '같다'를 해줘야 h만큼

     

    만약 지금처럼 2차원에서 무언가를 하려 한다면 저처럼 중첩 반복문을 쓸 것입니다

     

    h를 받으면

    h번 반복할건데(세로)

    반복마다 i가 1부터 증가하고, 그 i만큼 가로로 찍어내는  --▽보통 그 상황에서 변수들의 숫자가 뭐냐를 활용해야, 첫번째 줄일 째 한번 반복 두번째 줄일 때 두번 반복이니까 이걸 활용해야겠지

     

    잘 된다!중첩 반복문으로 마리오 피라미드를 성공적으로 구현헀다

     

    지금까지 본 문제들처럼 마리오 피라미드에는 흥미로운 점이 있어

    이게 흔한 피라미드인가? 높이 4의 피라미드를 어떻게 정의하나? 분명 높이 3의 피라미드에 한 줄을 추가한것임

    높이 3은 높이 2에서

    높이2는 높이 1에서 한줄을 추가한 것

    이것을 재귀적 정의라고 한다!!!

     

    눈에 보이는 혹은 가상의 물체의 구조를 그 물체의 자체를 이용해서 설명하는 것을 말한다

    특수한 경우 높이 1은 높이 0의 뭐야 아무것도 아니야

    -> 반환, 종료 혹은 정지 등 알고리듬에 맞게 처리를 해야함

    시작점에 대해서는 여러분들이 직접 이건 특별한 경우이므로 아무것도 하지 말고 재귀적으로 자기 자신을 호출하지 않도록 알려줘야 합니다

     : ▽시작점은 특별하게 대해야 한다는 이런 것도 다 알려주네

     

    ===

    recursion.c

    이번엔 중첩 반복문을 사용하지 않고 작은 피라미드큰 피라미드를 정의해볼 것

    (프로토타입 이따가 마지막에 적음)

     

    4짜리 그리려면 제일 먼저 뭘 해야 할까? 3짜리를 그리면 된다, 어떻게 코드로 적나??

    그리고 이러면 무한 반복호출되고 있어 3, 2, 1, 0, -1, -2.. 처럼 빼기만 하면 안끝나

    어떤 조건에서 그리지 말아야 할지(=시작점)의 조건을 줘야 한다

    하드 코딩된 조건문으로 의도치 않은 반복을 막는 것

     

    그리고

    높이4의 피라미드를 그릴 땐

    (1)높이 3의 피라미드를 그리고

    (2)한 줄 추가한다

    draw(h-1)가 피라미드를 그려주는 애라고 생각하면

    이제 여기에 한 줄을 추가로 그려주는 부분을 구현해야 한다

    h가 4 일 때 4개의 #을 그리는 반복문

     

    반복문이 필요하지만, 한 번이면 충분합니다

    (이번 강의에서 이런거 엄청 많이 알려주네 이렇게 하는거 맞아? 응 맞아 식의 중의중에 정해주는 설명)

    여기까지의 코드가 놀랍게도 다야.

     

    명시적으로 출력한건 한줄에 해당하는 벽돌뿐인데!!

    draw(h-1)이 draw(h-2)랑 한줄 출력으로 변한다

    draw가 전부 바뀌면

    h=0일때 return되고

    h=1일때의 출력

    h=2일때의 출력

    h=3일때의 출력

    h=4일때의 출력

    이 됨

    (내 생각)머릿속에 프랙탈이 되고 있는 코드 내용을 상상하자 그 안에서 계속 코드가 있는 것임 인자만 -1되면서

     

    재귀적으로 스스로를 호출하면서

    이진 탐색이나 분할 정복법에서처럼

    기존 문제보다 더 작은 크기의 문제를 풀어갑니다

     

    3개가 필요한거군

    시작점 조건

    재귀함수

    한줄추가

     

    문제를 조금씩 먹어가는것

     

    몇 주 후 스택을 배우면서 이 마법이 어떻게 가능한지 볼 것임

    우선은 함수가 초기에 주어진 입력보다 더 작은 입력을 갖고 계속해서 자신을 호출한다는 것만 이해합시다

     

    ===

    네이버 :

    재귀

    함수를 사용할 때 어디에서 호출했나요? main 안에서 프로그램을 작성하면서 필요한 순간에 호출하여 사용합니다.

    그런데 main 역시 함수라는걸 기억하시나요? main이라는 함수 안에서 또 다른 함수를 사용한 것입니다.

    이 사실을 알게 되었을 때, 우리는 함수가 본인 스스로를 호출해서 사용할 수 있는지에 대해 의문을 가질 수 있습니다.

    이에 대한 대답은 할 수 있다 이며, 이러한 것을 재귀(Recursion)라고 부릅니다.

     

    아래와 같이 피라미드 모양을 출력하기 위해 다음과 같은 코드를 작성할 수 있습니다

    #

    ##

    ###

    ####

    #include <cs50.h>
    #include <stdio.h>
    
    void draw(int h);
    
    int main(void)
    {
        // 사용자로부터 피라미드의 높이를 입력 받아 저장
        int height = get_int("Height: ");
    
        // 피라미드 그리기
        draw(height);
    }
    
    void draw(int h)
    {
        // 높이가 h인 피라미드 그리기
        for (int i = 1; i <= h; i++)
        {
            for (int j = 1; j <= i; j++)
            {
                printf("#");
            }
            printf("\n");
        }
    }

    높이를 입력 받아 중첩 루프를 통해 피라미드를 출력해주는 draw 함수를 정의한 것이죠.

    여기서 꼭 중첩 루프를 써야만 할까요? 사실 바깥 쪽 루프는 안 쪽 루프에서 수행하는 내용을 반복하도록 하는 것일 뿐입니다.

    따라서 바깥 쪽 루프를 없앤 draw함수를 만들고, 이를 ‘재귀적으로’ 호출하도록 해서 똑같은 작업을 수행할 수 있습니다.

    즉, draw 함수 안에서 draw 함수를 호출 하는 것이죠. 아래 코드와 같이 수정할 수 있습니다.

    #include <cs50.h>
    #include <stdio.h>
    
    void draw(int h);
    
    int main(void)
    {
        int height = get_int("Height: ");
    
        draw(height);
    }
    
    void draw(int h)
    {
        // 높이가 0이라면 (그릴 필요가 없다면)
        if (h == 0)
        {
            return;
        }
    
        // 높이가 h-1인 피라미드 그리기
        draw(h - 1);
    
        // 피라미드에서 폭이 h인 한 층 그리기
        for (int i = 0; i < h; i++)
        {
            printf("#");
        }
        printf("\n");
    }

    draw 함수 안에서 draw 함수를 다시 호출 하는 부분을 유의하시기 바랍니다.

    h라는 높이를 받았을 때, h-1 높이로 draw 함수를 먼저 호출하고, 그 후에 h 만큼의 #을 출력합니다.(▽재귀를 할 때마다 함수의 인자가 바뀌고 h의 조건이 바뀐다) 여기서 내부적으로 호출된 draw 함수를 따라가다 보면 h = 0인 상황이 오게 됩니다. 따라서 그 때는 아무것도 출력을 하지 않도록 하는 조건문을 추가해줘야 합니다.

     

    이렇게 재귀를 사용하면 중첩 루프를 사용하지 않고도 하나의 함수로 동일한 작업을 수행할 수 있습니다.

     

    ---

    내 직관

    어떤 조건 전 과 후를 나누고 후를 계속 쌓아놓을 수가 있어 조건이 터지면 그때부터 콸콸콸

    재귀는 이 문제에서 볼 수 있듯이 뒤에 쌓아놓을 수가 있다 

     

    쌓아놓는다는게 뭐냐면

     

    재귀가

    h=5

    1

    재귀(h-1)

    2

     

    5-1, 4-1, 3-1, 2-1, 1-1 1-2, 2-2, 3-2, 4-2, 5-2 이런식으로 동작하겠네?

     

    h=1

    1

    재귀(h+1)

    2

     

    1-1, 2-1, 3-1, 4-1, 5-1, 5-2, 4-2, 3-2, 2-2, 1-2 이렇고

     

    재귀함수 앞뒤 코드로 나눠서

    앞에것만 일단 h조건 다르게 실행하고

    뒤에것을 거꾸로 h조건 다르게 실행가능

Designed by Tistory.