알고리즘

3. 검색 알고리즘

masxer 2025. 6. 29. 13:42

검색은 특정 항목에 주목한다는 공통점이 있다.

키(key) : 주목하는 항목

ex) 국적을 검색할 땐 국적이 키, 나이를 검색할 땐 나이가 키

 

데이터가 단일 값이면 그대로 키 값이 되겠지만 대부분의 경우에서 키는 데이터의 '일부'이다.

 

검색 과정

1. 키 값과 일치하도록 지정한다. (한국)

2. 키 값의 구간을 저장한다. (21세 이상 27세 미만)

3. 키 값과 비슷하도록 지정한다. (발음이 가장 비슷한 이름)

조건은 하나만 지정하기도 하지만 논리곱이나 논리합을 사용해 여러 조건을 복합해서 지정하기도 한다.

 

배열에서 검색

선형 리스트에서 검색

이진 트리 검색

1. 선형 검색 : 무작위로 늘어서 있는 데이터 모임에서 검색을 수행한다.

2. 이진 검색 : 일정한 규칙으로 늘어서 있는 데이터 모임에서 아주 빠른 검색을 수행한다.

3. 해시법 : 추가, 삭제가 자주 일어나는 데이터 모임에서 아주 빠른 검색을 수행한다.

        - 체인법 : 같은 해시값의 데이터를 선형 리스트로 연결하는 방법

        - 오픈 주소법 : 데이터를 위한 해시값이 충돌할 때 재해시하는 방법

해시법은 검색뿐만 아니라 추가나 삭제 등을 효율적으로 수행할 수 있는 종합적인 방법이다.

 

검색만 할 경우 계산 시간이 짧은 알고리즘을 선택하면 되지만 검색뿐만 아니라 데이터를 추가하거나 삭제하는 작업을 자주 한다면

검색 이외의 소요되는 비용을 평가해 알고리즘을 선택해야 한다.

※ 추가 비용이 큰 알고리즘은 피해야 한다.

 

선형 검색

- 검색 알고리즘 중 기본적인 알고리즘

- 요소가 직선 모양으로 늘어선 배열에서 검색을 원하는 키 값을 갖는 요소를 만날 때까지 맨 앞부터 순서대로 요소를 검색하는 방법이다.

'선형 검색(Linear Search)' 또는 '순차 검색(Sequential Search)' 라고 한다.

 

선형 알고리즘 코드

import java.util.Scanner;

public class SeqSearch {
	static int seqSearch(int[] a, int n, int key) {
		int i = 0;
		
		while(true) {
			if(i == n)
				return -1; // 검색 실패(-1을 반환)
			
			if(a[i] == key)
				return i; // 검색 성공(인덱스를 반환)
			
			i++;
		}
        
		// for문으로 구현할 경우
//		for(int i = 0; i < n; i++)
//			if(a[i] == key)
//				return i;
//		return -1;
	}
	
	
	
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		
		System.out.print("요소수 : ");
		int num = scanner.nextInt();
		int[] x = new int[num];  // 요소수가 num인 배열
		
		for(int i = 0; i < num; i++) { 
			System.out.print("x[" + i + "]: ");
			x[i] = scanner.nextInt();
		}
		
		System.out.print("검색할 값: "); // 키 값을 입력 받는다.
		int key = scanner.nextInt();
		int idx = seqSearch(x, num, key); // 배열 x에서 값이 key인 요소를 검색
		
		if(idx == -1)
			System.out.println("그 값의 요소가 없습니다.");
		else
			System.out.println("그 값은 x[" + idx + "]에 있습니다.");
	}
	
}

 

선형 검색의 종료 조건

1. i == n이 성립하는 경우

2. a[i] == key가 성립하는 경우

 

종료 조건을 검사하는 비용은 결코 무시할 수 없다.

이 비용을 반으로 줄이는 방법이 '보초법'이다. 원래 배열에 데이터를 넣어 놓고 맨 끝에 배열을 하나 추가해 검색하는 것.

만일 보초인 배열까지 검색하면 '검색할 값과 같은 요소를 발견했는가'가 성립하기 때문에 i == n이 성립하는 경우의 조건은 없어도 된다.

 

보초법 선형 알고리즘 코드

import java.util.Scanner;

public class SeqSearchSen {
	static int seqSearch(int[] a, int n, int key) {
		int i = 0;
		
		a[n] = key; // 보초를 추가
		
		while(true) {
			if(a[i] == key)
				break;
			i++;
		}
		return i == n ? -1 : i;
	}
	
	
	
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		
		System.out.print("요소수 : ");
		int num = scanner.nextInt();
		int[] x = new int[num + 1];  // 요소수가 num인 배열
		
		for(int i = 0; i < num; i++) { 
			System.out.print("x[" + i + "]: ");
			x[i] = scanner.nextInt();
		}
		
		System.out.print("검색할 값: "); // 키 값을 입력 받는다.
		int key = scanner.nextInt();
		int idx = seqSearch(x, num, key); // 배열 x에서 값이 key인 요소를 검색
		
		if(idx == -1)
			System.out.println("그 값의 요소가 없습니다.");
		else
			System.out.println("그 값은 x[" + idx + "]에 있습니다.");
	}
}

 

 

이진 검색

이 알고리즘을 적용하는 전제 조건 : 데이터가 키 값으로 이미 정렬되어 있다는 것을 만족해야 한다.

장점 : 선형 검색보다 좀 더 빠르게 검색할 수 있다.

 

요소가 오름차순, 내림차순으로 정련된 배열에서 검색하는 알고리즘

 

a[pc] < key 일 때

- key 보다 작은 것이 분명하므로 검색 대상에서 제외한다.

- a[pc]보다 뒤쪽인 a[pc + 1] ~ a[pr]로 좁힌다. 이를 위해 pl 값을 pc + 1로 수정한다.

 

a[pc] > key 일 때

- a[pc] ~ a[pr]은 key보다 큰 것이 분명하므로 검색 대상에서 제외한다.

- 검색 범위를 중앙 요소 a[pc]보다 앞쪽인 a[pl] ~ a[pc - 1]로 좁힌다. 이를 위해 pr 값을 pc - 1로 수정한다.

 

이진 검색의 종료 조건

1. a[pc]와 key가 일치한다.

2. 검색 범위가 더이상 없다.

 

이진 검색 알고리즘 코드

import java.util.Scanner;

public class BinSearch {
	// 요솟수가 n개인 배열 a에서 key와 같은 요소를 이진 검색
	static int binSearch(int [] a, int n, int key) {
		int pl = 0;	// 검색 범위의 첫 인덱스
		int pr = n - 1; // 검색 범위의 끝 인덱스
		
		do {
			int pc = (pl + pr) / 2; // 중앙 요소의 인덱스
			if(a[pc] == key)
				return pc; // 검색 성공
			else if(a[pc] < key)
				pl = pc + 1; // 검색 범위를 뒷쪽 절반으로 좁힘
			else
				pr = pc - 1; // 검색 범위를 앞쪽 절반으로 좁힘
		}while(pl <= pr);
		
		return -1; // 검색 실패
	}
	
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		
		System.out.print("요소수 : ");
		int num = sc.nextInt();
		int[] x = new int[num]; // 요소수가 num인 배열
		
		System.out.println("오름차순으로 입력하세요.");
		
		System.out.print("x[0]: "); // 첫 요소 입력받음
		x[0] = sc.nextInt();
		
		for(int i = 1; i < num; i++) {
			do {
				System.out.print("x[" + i + "]: ");
				x[i] = sc.nextInt();
			}while(x[i] < x[i - 1]); // 바로 앞의 요소보다 작으면 다시 입력받
		}
		
		System.out.print("검색할 값 : ");
		int key = sc.nextInt();
		
		int idx = binSearch(x, num, key);
		
		if(idx == -1)
			System.out.println("그 값의 요소가 없습니다.");
		else
			System.out.println("그 값은 x[" + idx + "]에 있습니다.");
	}
}

 

시간 복잡도 구하기

 

선형 검색 

O(f(n)) + O(g(n)) = O(max(f(n), g(n)))

※ max(a, b)는 a와 b 가운데 큰 쪽을 나타내는 메서드

int i = 0;  // 1
		
while(i < n) { // 2
    if(a[i] == key) // 3
        return i; // 검색 성공(인덱스를 반환) // 4

    i++; // 5
}

return -1; // 6

즉 선형 검색은 O(1) + O(n) + O(n) + O(1) + O(n) + O(1) = O(max(1, n, n, 1, n, 1)) = O(n)이다.

 

이진 검색

static int binSearch(int [] a, int n, int key) {
    int pl = 0;	// 1
    int pr = n - 1; // 2

    do { 
        int pc = (pl + pr) / 2; // 3
        if(a[pc] == key) // 4
            return pc; // 5
        else if(a[pc] < key) // 6
            pl = pc + 1; // 7
        else
            pr = pc - 1; // 8
    }while(pl <= pr); // 9

    return -1; // 10
}

이진 검색은 O(1) + O(1) + O(log n) + O(log n) + O(1) + O(log n) + O( log n ) + O( log n ) + O(log n) + O(1) = O(log n)이다.

 

자바는 배열에서 이진 검색을 하는 메서드를 표준 라이브러리로 제공한다.

java.util.Arrays 클래스의 binarySearch 메서드가 이진 검색이다.

 

binarySearch 장점 

- 이진 검색 메서드를 직접 작성할 필요가 없다.

- 배열 요소의 자료형과 관계 없이 검색할 수 있다.

 

API 문서란?

- 라이브러리를 작성한 쪽에서 사용하는 방법을 적어 놓은 것이다.

 

binarySearch를 사용해서 검색을 성공한 경우

key와 일치하는 요소의 인덱스를 반환. 만약 일치하는 요소가 여러 개 있을 경우 어느 요소의 인덱스를 반환하는지 정해져 있지 않아 맨 앞에 요소의 인덱스를 반환한다는 보증이 없다.

 

 

binarySearch를 사용해서 검색을 실패한 경우

'배열 안에 eky가 있어야할 위치(삽입 포인트)를 추정할 수 있는 값'을 반환한다.

반환 값 : -x - 1