[Algorithm] 순차 탐색(Sequential Search)와 이진 탐색(Binary Search)
순차 탐색(Sequential Search)와 이진 탐색(Binary Search)
문제
data = {1, 12, 8, 7, 2, 11, 5, 81, 29, 10}
이란 배열이 있습니다.
여기에서 숫자 7이 몇번째에 있는지 알아내고자 합니다.
순차 탐색(Sequential Search)
순차탐색은 말그대로 순차적으로 비교해가면서 찾는 것입니다.
data[0]부터 값을 살펴봐서 7인지 아닌지 하나하나 확인하는 것이죠.
data[0], data[1], data[2] 는 계속 아니다가
data[3] == 7 이므로 여기서 탐색을 멈춥니다.
복잡도(Time Complexity)
만약 data 배열에서 1을 탐색하면 1번만에 찾아낼 것입니다.
12를 탐색하고자 한다면 2번만에 찾아낼 것이고,
8을 탐색하고자 한다면 3번만에 찾아낼 것이고,
…
10을 탐색하고자 한다면 10번만에 찾아낼 것입니다.
그럼 탐색하는게 걸리는 평균 연산 횟수는
\[\frac{1+2+3+\cdots+9+10}{10}\]이 되겠죠.
그럼 data 배열의 크기가 n이라면 탐색에 걸리는 평균 연산횟수는
\[\frac{1+2+3+\cdots+(n-1)+n}{n} = \frac{n+1}{2}\]즉, 복잡도는 O(n)이 됩니다.
예제 코드
public class SequentialSearch {
public static void main(String[] ar){
SequentialSearch ex = new SequentialSearch();
int[] data = {1, 12, 8, 7, 2, 11, 5, 81, 29, 10};
int key = 7;
int dataLength = data.length;
for(int i=0; i<dataLength; i++){
if(data[i] == key){
System.out.println(key + " is in the " + i + ex.ordianlNumber(i) + " position");
}
}
// 7 is in the 3rd position
}
public String ordianlNumber(int num){
if(num == 1){
return "st";
}else if(num == 2){
return "nd";
}else if(num == 3){
return "rd";
}else{
return "th";
}
}
}
이진 탐색(Binary Search)
이진 탐색을 위해서는 data값이 오름차순으로 정렬이 되어있어야합니다.
그 이유는 이진탐색의 탐색 방법에 있습니다.
위의 data 배열을 오름차순으로 정렬하면
data = {1, 2, 5, 7, 8, 10, 11, 12, 29, 81}
입니다. 여기서 7을 찾는다 하면
일단 이 배열의 가운데 값을 봅니다.
배열의 크기가 홀수면 딱 가운데가 존재하겠지만 여기선 짝수네요.
별 상관 없습니다. 배열 전체를 봤을 때
시작은 0번째, 맨 마지막은 9번째 이므로
\(\frac{0+9}{2} == 4.5\) 이므로 4번째 수인 8을 봅니다.
8은 7보다 크죠. 그럼 8을 포함한 위의 값은 탐색할 필요가 없어집니다.
{1, 2, 5, 7}에서만 검색하면 되죠.
즉, 이제 시작은 0번째, 맨 마지막은 3번째 가 되고,
가운데 값은 1번째 값인 2입니다.
2는 7보다 작으므로 {1, 2}는 이제 탐색할 필요가 없습니다.
즉, {5, 7}만 탐색하면 되죠.
시작은 2번째, 끝은 3번째이고
가운데 값은 (2+3)/2 == 2, 즉 2번째 값인 5입니다.
5는 7보다 작죠.
즉 5는 탐색할 필요가 없고 나머지 7만 탐색하면 됩니다.
시작은 3번째, 끝도 3번째. 즉 가운데 값은 3번째인 7입니다.
찾으려고 하는 숫자를 찾았습니다!
복잡도(Time Complexity)
탐색하는 배열의 크기가 n이고, k번의 연산을 통해
원하는 값을 찾았다고 가정을 하겠습니다.
한번 연산을 하면 탐색해야 할 n개의 숫자가 반으로 줄어듭니다. 즉,
\[(\frac{1}{2})^1 n\]두번 연산을 하면 탐색해야 할 n개의 숫자가 1/4이 되겠죠. 즉,
\[(\frac{1}{2})^2 n\]k번 연산 후, 원하는 값을 찾았으므로,
k번 연산 후 탐색해야 할 숫자의 개수는 한개입니다. 이를 식으로 나타내면
\[(\frac{1}{2})^k n = 1\]양변을 n으로 나눠주고, 양변의 분모 분자를 뒤집어주면
\[2^k = n\]입니다. 양변에 \(log_2\)를 씌워주면,
\[k = log_2 n\]입니다. k가 연산 횟수이기 때문에, 이진탐색의 복잡도는
\(O(log_2 n)\) 이 됩니다.
예제 코드
import java.util.Random;
import java.util.Arrays;
public class BinarySearch {
public static void main(String[] ar){
BinarySearch ex = new BinarySearch();
Random rand = new Random();
int arrayLength = 10;
int[] intArray = new int[arrayLength];
for(int i=0; i<arrayLength; i++){
intArray[i] = rand.nextInt(20) + 1;
}
Arrays.sort(intArray);
System.out.print("array = ");
ex.printArray(intArray);
int key = rand.nextInt(20) + 1;
System.out.println("key = " + key);
ex.doBinarySearch(intArray, key);
}
public void doBinarySearch(int[] inputArray, int key){
int inputArraySize = inputArray.length;
int upperBound = inputArraySize-1;
int lowerBound = 0;
int median;
while(true){
if(upperBound < lowerBound){
System.out.println(key + " is not in the inputArray");
break;
}
median = (upperBound+lowerBound)/2;
if(inputArray[median] == key){
System.out.println(key + " is in the " + median + " position");
break;
}else if(inputArray[median] > key){
upperBound = median-1;
}else{
lowerBound = median+1;
}
}
}
public void printArray(int[] array){
for(int item: array){
System.out.print(item + " ");
}
System.out.println();
}
}
1 ~ 20 사이의 숫자를 랜덤으로 받아
크기 10인 배열을 만들고, 오름차순으로 정렬한 후에
doBianrySearch()에서 key값을 찾습니다.
이진 탐색은 Recursive하게도 많이 구현합니다.
import java.util.Random;
import java.util.Arrays;
public class BinarySearchRecursive {
public static void main(String[] ar){
BinarySearchRecursive ex = new BinarySearchRecursive();
Random rand = new Random();
int arrayLength = 10;
int[] intArray = new int[arrayLength];
for(int i=0; i<arrayLength; i++){
intArray[i] = rand.nextInt(20) + 1;
}
Arrays.sort(intArray);
System.out.print("array = ");
ex.printArray(intArray);
int key = rand.nextInt(20) + 1;
System.out.println("key = " + key);
int result = ex.doBinarySearchRecursive(intArray, key, 0, arrayLength-1);
if(result == -1){
System.out.println(key + " is not in the array");
}else{
System.out.println(key + " is in the " + result + " position");
}
}
public int doBinarySearchRecursive(int[] inputArray, int key, int start, int end){
int upperBound = end;
int lowerBound = start;
int median;
while(true){
if(upperBound < lowerBound){
return -1;
}
median = (upperBound+lowerBound)/2;
if(inputArray[median] == key){
return median;
}else if(inputArray[median] > key){
return doBinarySearchRecursive(inputArray, key, lowerBound, median-1);
}else{
return doBinarySearchRecursive(inputArray, key, median+1, upperBound);
}
}
}
public void printArray(int[] array){
for(int item: array){
System.out.print(item + " ");
}
System.out.println();
}
}
이진탐색을 recursive하게 구현할 때는 받는 매개변수 값으로
탐색의 시작위치와 마지막 위치도 받아야합니다.