λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
πŸ”Ž Algorithm/πŸ“ κ°œλ… 정리

[κ°œλ… 정리] μˆœμ—΄, μ‘°ν•©, 뢀뢄집합

by Seongpyo Hong 2021. 2. 19.

이번 κΈ€μ—μ„œλŠ” λͺ¨λ“  경우의 수λ₯Ό νƒμƒ‰ν•˜λŠ” 완전탐색 κΈ°λ²•μ—μ„œ μˆœμ—΄κ³Ό μ‘°ν•© 그리고 뢀뢄집합에 λŒ€ν•œ κ΅¬ν˜„ 방법을 μ •λ¦¬ν•΄λ³΄κ³ μž ν•©λ‹ˆλ‹€.

μˆœμ—΄

μˆœμ—΄μ€ N개의 μ›μ†Œ μ€‘μ—μ„œ R개의 μ›μ†Œλ₯Ό 톡해 μˆœμ„œλ₯Ό 가진 뢀뢄집합을 λ§Œλ“œλŠ” 경우의 μˆ˜μž…λ‹ˆλ‹€. μˆœμ—΄μ€ μž¬κ·€ ν•¨μˆ˜λ₯Ό 톡해 λͺ¨λ“  경우의 수λ₯Ό ꡬ할 수 μžˆμŠ΅λ‹ˆλ‹€. 

μž¬κ·€ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜κ²Œ 되면 이번 μˆœμ„œμ˜ μ›μ†Œλ₯Ό μ‚¬μš©ν•˜λŠ”μ§€ μ²΄ν¬ν•˜λŠ” 과정이 ν•„μš”ν•œλ°, 이 과정은 boolean 배열을 톡해 μ²΄ν¬ν•˜κ±°λ‚˜ λΉ„νŠΈ λ§ˆμŠ€ν‚Ήμ„ 톡해 κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Boolean λ°°μ—΄ μ‚¬μš©

permutationByBoolean(0);

static void permutationByBoolean(int cnt) {
		
	if (cnt == N) {
		printPermutation();
		return;
	}
	for (int i = 0; i < arr.length; i++) {
		if (check[i]) continue;
		check[i] = true;
		perm[cnt] = arr[i];
		permutationByBoolean(cnt + 1);
		check[i] = false;
	}
}

 

λΉ„νŠΈ λ§ˆμŠ€ν‚Ή μ‚¬μš©

permutationByBitmasking(0, 0);

static void permutationByBitmasking(int cnt, int flag) {
	if (cnt == N) {
		printPermutation();
		return;
	}
	for (int i = 0; i < arr.length; i++) {
		if ((flag & 1 << i) > 0)
			continue;
		perm[cnt] = arr[i];
		permutationByBitmasking(cnt + 1, flag | 1 << i);
	}
}

 

λ§Œμ•½ μ˜€λ¦„μ°¨μˆœμœΌλ‘œ μ •λ ¬λœ λ°°μ—΄μ—μ„œ μ‚¬μ „μˆœμœΌλ‘œ μˆœμ—΄μ„ κ΅¬ν•˜λŠ” κ²½μš°μ—λŠ” λ‹€μŒ μˆœμ„œμ˜ μˆœμ—΄μ„ κ΅¬ν•˜λŠ” 방법도 μ‘΄μž¬ν•©λ‹ˆλ‹€. Python, C++μ—μ„œλŠ” Next Permutationμ΄λΌλŠ” 라이브러리 ν•¨μˆ˜λ₯Ό 톡해 이λ₯Ό μ œκ³΅ν•˜μ§€λ§Œ μžλ°”λŠ” μ‘΄μž¬ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— 직접 κ΅¬ν˜„ν•΄ λ³Έ μ½”λ“œλŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

do {
	System.out.println(Arrays.toString(arr));
} while(nextPermutation());

static boolean nextPermutation() {
	int srcIdx = arr.length;
	while(--srcIdx > 0) {
		if (arr[srcIdx] >  arr[srcIdx - 1]) break;
	}
	if (--srcIdx == -1) 
		return false;
	
	int destIdx = arr.length;
	while(--destIdx >= 0) {
      if (arr[srcIdx] >= arr[destIdx])  continue;
          swap(srcIdx, destIdx);
          break;
		}
	} 
	
	destIdx = arr.length;
	while (++srcIdx < --destIdx) {
		swap(srcIdx, destIdx);
	}
	return true;
}

κ°„λ‹¨ν•˜κ²Œ 과정을 μ„€λͺ…ν•˜λ©΄ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

1. 맨 λ’€μ—μ„œλΆ€ν„° 탐색을 μ‹œμž‘ν•΄μ„œ 값이 κ°μ†Œν•˜λŠ” 지점을 srcIdx둜 지정
2. 맨 λ’€μ—μ„œλΆ€ν„° 탐색을 μ‹œμž‘ν•΄μ„œ srcIdx보닀 값이 첫 번째 μ›μ†Œμ™€ swap
3. srcIdx λ’€μ˜ μ›μ†Œλ₯Ό μ˜€λ¦„μ°¨μˆœμœΌλ‘œ μ •λ ¬


μ‘°ν•©

쑰합은 N개의 μ›μ†Œ μ€‘μ—μ„œ R개의 μ›μ†Œλ₯Ό 톡해 뢀뢄집합을 λ§Œλ“œλŠ” 경우의 μˆ˜μž…λ‹ˆλ‹€. μˆœμ—΄κ³Ό λ‹€λ₯Έ 점은 μˆœμ„œμ— 상관없이 쀑볡 μ—¬λΆ€λ₯Ό 따지기 떄문에 1,2,3 / 3,2,1κ³Ό 같은 κ²½μš°λŠ” μ‘°ν•©μ—μ„œλŠ” 같은 κ²½μš°κ°€ λ©λ‹ˆλ‹€. 쑰합도 μž¬κ·€ ν•¨μˆ˜λ₯Ό 톡해 λͺ¨λ“  경우의 수λ₯Ό ꡬ할 수 μžˆμ§€λ§Œ μˆœμ—΄κ³Ό λ‹€λ₯Έ 점은 μž¬κ·€ ν•¨μˆ˜ λ‚΄λΆ€μ—μ„œ forλ₯Ό ν˜ΈμΆœν•  ν•„μš”μ—†μ΄ λ‹€μŒ λ‹¨κ³„λ‘œ λ„˜μ–΄κ°€μ„œ 검사λ₯Ό μ§„ν–‰ν•©λ‹ˆλ‹€.

combination(0, 0);

static void combination(int idx, int cnt) {
	if (cnt == N) {
		printArray();
		return;
	}
	if (idx == arr.length) return;
	
	check[idx] = true;
	combination(idx + 1, cnt + 1);
	check[idx] = false;
	combination(idx + 1, cnt);
}

 

boolean λ°°μ—΄λ₯Ό 톡해 μ‚¬μš©μ—¬λΆ€λ₯Ό μ²΄ν¬ν•˜λŠ” 방법 이외에도 직접 배열을 μœ μ§€ν•˜λ©΄μ„œ 뽑은 쑰합을 확인할 μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

combinationUsingList(0, new ArrayList<Integer>());
static void combinationUsingList(int idx, ArrayList<Integer> comb) {
	if (comb.size() == N) {
		comb.forEach(e -> System.out.print(e + " "));
		System.out.println();
		return;
	}
	if (idx == arr.length) return;
	
	comb.add(arr[idx]);
	combinationUsingList(idx + 1, comb);
	comb.remove(Integer.valueOf(arr[idx]));
	combinationUsingList(idx + 1, comb);
}

 

λ˜ν•œ, μˆœμ—΄μ„ κ΅¬ν•˜λŠ” ν•œ 방법인 Next Permutation을 톡해 쑰합을 ꡬ할 수 μžˆμŠ΅λ‹ˆλ‹€. 

flag = new int[5];
int idx = arr.length;
while (N-- > 0)
	flag[--idx] = 1;

do {
	printCombination();
} while (nextPermutation());

static boolean nextPermutation() {
	int srcIdx = arr.length;
	while (--srcIdx > 0) {
		if (flag[srcIdx] > flag[srcIdx - 1])
			break;
	}
	srcIdx -= 1;
	if (srcIdx == -1)
		return false;

	int destIdx = arr.length;
	while (--destIdx >= 0) {
		if (flag[srcIdx] >= flag[destIdx])
			continue;
		swap(srcIdx, destIdx);
		break;
	}

	destIdx = arr.length;
	while (++srcIdx < --destIdx) {
		swap(srcIdx, destIdx);
	}

	return true;
}

private static void printCombination() {
	for (int i = 0; i < flag.length; i++) {
		if (flag[i] == 0)
			continue;
		System.out.print(arr[i] + " ");
	}
	System.out.println();
}

과정을 κ°„λ‹¨νžˆ μ„€λͺ…ν•˜λ©΄ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

1. 전체 μ›μ†Œ(N) 만큼 배열을 λ§Œλ“€μ–΄ 0으둜 μ΄ˆκΈ°ν™” ν•œ ν›„, 뽑을 개수(R) 만큼 λ’€μ—μ„œλΆ€ν„° 1둜 λ³€κ²½
2. Next Permutation을 톡해 μƒμ„±λœ λ°°μ—΄μ—μ„œ 1의 μœ„μΉ˜λŠ” 뽑을 μ›μ†Œμ˜ μœ„μΉ˜μ™€ 동일 


λΆ€λΆ„ 집합

λΆ€λΆ„ 집합은 N개의 μ›μ†Œλ₯Ό κ°€μ§€λŠ” μ§‘ν•©μ—μ„œ μ›μ†Œλ₯Ό μ‚¬μš©ν•΄ λ§Œλ“€ 수 μžˆλŠ” λͺ¨λ“  집합을 λ‚˜νƒ€λ‚΄λŠ” 경우의 μˆ˜μž…λ‹ˆλ‹€. λΆ€λΆ„ 집합을 κ΅¬ν•˜λŠ” 방법은 μž¬κ·€λ₯Ό 톡해 κ΅¬ν˜„ν•˜λŠ” 방법과 nC1 ~ nCr을 톡해 κ΅¬ν•˜λŠ” 방법이 μ‘΄μž¬ν•©λ‹ˆλ‹€.

subset(0, 0);
private static void subset(int i, int cnt) {
	if (i == 5) {
		System.out.println(Arrays.toString(check));
		return;
	}

	check[i] = true;
	subset(i + 1, cnt + 1);
	check[i] = false;
	subset(i + 1, cnt);
}

μ‘°ν•©κ³Ό λ‹€λ₯Έ 점은 쑰합은 R개λ₯Ό 뽑지 λͺ»ν•œ κ²½μš°λŠ” μ œμ™Έν•˜μ˜€μ§€λ§Œ 뢀뢄집합은 idxκ°€ 끝에 λ„λ‹¬ν•˜λ©΄ 경우의 μˆ˜μ— μΆ”κ°€ν•œλ‹€λŠ” λΆ€λΆ„μž…λ‹ˆλ‹€.

쑰합을 톡해 κ΅¬ν˜„

private static void combination(int idx, int cnt, int R) {
	if (cnt == R) {
		System.out.println(Arrays.toString(check));
		return;
	}
	if (idx == 5) return;

	check[idx] = true;
	combination(idx + 1, cnt + 1, R);
	check[idx] = false;
	combination(idx + 1, cnt, R);
}

 

λŒ“κΈ€