재귀호출

재귀호출

Posted by 동식이 블로그 on February 25, 2019

재귀호출

재귀호출이 두번인 경우의 기본
1
2
3
4
5
6
7
8
9
10
11
12
13
# 완전탐색 / 부분집합의 합
## L은 len이 3인 1차원배열
### n 은 len(L)
def f(n, k):
    {
        if(n == k):
        
        else:
        	L[n] = 0
        	f(n+1, k)
        	L[n] = 1
        	f(n+1, k)
    }

깊이 = 다뤄야할 배열의 크기

옆으로 = 각 칸을 채울수 있는 숫자의 개수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 주어진 배열의 부분집합

def f(n, k):
    if(n == k):
        for i in range(k):
            if(b[i] == 1):
                print(a[i], end =" ")
        print()
        return

    else:
        #b[n]에 0을 채우고
        b[n] = 0
        # 모든 경우의 수를 만들어봐
        f(n+1, k)
        ## b[n]에 1을 채우고
        b[n] = 1
        ## 모든 경우의 수를 만들어봐
        f(n+1, k)

k = 3
a = [10, 20, 30]
b = [0]*k # 원소의 포함여부를 
f(0, k)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def f(n, k, m):
    global cnt
    if(n == k):
        s = 0
        for i in range(k): # 부분집합의 합 계산
            if(b[i] == 1):
                s += a[i]
        if(s == m): # 문제에 주어지 조건
            cnt += 1
            for i in range(k):  # 부분집합의 합 계산
                if (b[i] == 1):
                    print(a[i], end=" ")
            print()
        return
    else:
        #b[n]에 0을 채우고
        b[n] = 0
        # 모든 경우의 수를 만들어봐
        f(n+1, k,m)
        ## b[n]에 1을 채우고
        b[n] = 1
        ## 모든 경우의 수를 만들어봐
        f(n+1, k,m)

k = 10
m = 10 # 찾고자 하는 부분집합의 합
a = [1,2,3,4,5,6,7,8,9,10]
b = [0]*k # 원소의 포함여부를 나타내는 배열
cnt = 0
f(0, k,m)
print(cnt)
백트래킹(많이 날리기)

1 2 3 4 5 6 7 8 9 10

n = 4 일때

  • 1 ~~~4가 고려한 구간 (결정된 구간) : 포함여부 결정
    • 고려한 구간의 합 S일때 고려한 구간에서 합이 X와 같거나 크다면 아직 고려하지 않은 구간에서 어떤 원소를 더해도 안되기 때문에 고려하지 않아도 된다.
      • 이 경우 백트래킹
    • 고려한 구간의 합이 S일때, 고려한 구간에서 합이 X보다 작을때만 아지 고려하지 않은 구간에서 연산을 고려해야 한다.
  • 5 ~~~10은 아직 고려하지 않은 구간 : 포함여부 미결정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def f(n, k, m, s): # s는 n-1까지(이전에) 포함된 원소의 합
    global cnt
    global cnt2 # 호출회수
    cnt2 += 1
    if(s == m):
        cnt += 1
    elif(n == k): # 한개의 부분집합 완성
        return
    elif(m < s): # 이부분의 코드가 없다면 재귀 호출의 수가 늘어난다.
                ## 단 합의 수가 커질수록 효과는 미미하다
                ### 하지만 재귀호출을 줄이는게 목표 ( 백트래킹 )
                #### 그렇다면 합이 수가 커질때는 어떻게 줄일 수 있을까?
        return
    else:
        b[n] = 0
        f(n+1, k, m, s) # a[n]이 포함되지 않는 경우
        b[n] = 1
        f(n+1, k, m, s+a[n]) # a[n]이 포함되는 경우

k = 10
m = 27 # 찾고자 하는 부분집합의 합 
a = [1,2,3,4,5,6,7,8,9,10]
b = [0]*k # 원소의 포함여부를 나타내는 배열
cnt = 0
cnt2 = 0
f(0, k,m, 0)
print(cnt, cnt2)
합이 커질 때 줄이기
  • 찾고자 하는 합이 m = 50

  • 고려구간중 포함된 원소의 합 = s
  • 고려하지 않은 구간 = rs
    • s + rs < m인 경우가 생긴다 : 남은애들을 다 더해도 m이 안될 경우 제외
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def f(n, k, m, s, rs): # s는 n-1까지(이전에) 포함된 원소의 합
	elif(m < s): # 백트래킹을 위한 조건 1
        return
    
    elif(s + rs < m): 	# 백트래킹을 위한 조건 2
        				## 추가된 코드
        				### 앞, 뒤 몰려있는 애들 제거해주는걸로 확 줄일수 있음
        return
    else:
        b[n] = 0
        f(n+1, k, m, s, rs-a[n]) # a[n]이 포함되지 않는 경우
        b[n] = 1
        f(n+1, k, m, s+a[n], rs-a[n]) # a[n]이 포함되는 경우

k = 10
m = 27 # 찾고자 하는 부분집합의 합
a = [1,2,3,4,5,6,7,8,9,10]
b = [0]*k # 원소의 포함여부를 나타내는 배열
cnt = 0
cnt2 = 0
f(0, k,m, 0, sum(a))
print(cnt, cnt2)

중복순열

1,2,3을 중복을 이용해 3자리 숫자 만들기
1
2
3
4
5
6
7
8
9
10
11
12
def f(n, k):
    if(n == k):
        print(p)
    else:
        for i in range(1, k+1):
            p[n] = i
            f(n+1, k)

k = 3
p = [0]*k
f(0,k)
1,2,3,4,5중 3개의 숫자를 중복 사용해 3자리 숫자 만들기
1
2
3
4
5
6
7
8
9
10
11
12
def f(n, k, m):
    if(n == k):
        print(p)
    else:
        for i in range(1, m+1):
            p[n] = i
            f(n+1, k, m )

k = 3
m = 5
p = [0]*k
f(0, k, m)

분할 정복 알고리즘

  • 분할 / 정복 / 통합으로 나누어 설계
  • 대표적인 예 : 퀵 정렬

백트래킹