본문 바로가기

CS/알고리즘

Ch 1. 알고리즘의 첫걸음

알고리즘의 유래

-“알콰리즈미”라는 사람의 이름으로부터 유래

-최초의 알고리즘 = 유클리드의 최대공약수 알고리즘 (몫과 나머지의 성격을 이용하여)

알고리즘이란?

: 문제를 해결하기 위한 단계적인 절차

-효율적인 알고리즘 고안이 중요

= 최소한의 시간과 비용이 드는 것(시간 복잡도와 공간 복잡도를 최소화시키는 알고리즘)

알고리즘 기술 방법

-알고리즘이라는 것은 문제를 해결하는 방법을 고안하는 것뿐만 아니라 약속된 방법을 통해서 절차를 기술하는 것까지가 알고리즘이다.

기술 방법 = 자연어, flow chart, 의사코드 등

의사코드가 가장 일반적 (프로그래밍 코드와 매우 유사)

모든 문제를 그때 그때마다 구현까지 하려면 복잡한 문제는 구현하는데까지 드는 비용이 매우 높을 것이다. 그 전단계까지인 의사코드를 통해 표기하면 구현까지의 비용을 감소시킬 수 있다.

1.1 최대 숫자 찾기

(1) 의사코드 표현 

Alg findMax(A) -- 알고리즘 이름 == int findMax(int A[])
	input array A of size n -- 입력값으로 n사이즈의 배열이 들어간다. (자료형도 써도 된다)
	output integer max

(순차적인 절차이기 때문에 순서를 부여한다)
1. max <- A[0]  -- 처음 들어가는 input값이 max라고 가정하기 때문에 max라고 함, A의 배열 중 0번째를 max에 대입
2. for i <- 1 to n-1 -- 1`~  n-1까지 돌면서 
		 if (A[i] > max) -- max값보다 해당 index가 더 크다면
				max <- A[i] -- max에 해당 indec값을 대입한다.
3. return max
	

문제 = 정렬되지 않는 숫자들 중에서 최대값을 찾는다.

방법 = 숫자를 하나씩 비료하면서 본 숫자들 중에서 가장 큰 숫자를 기억해가며(임시 저장) 찾아보기

→ 순차탐색(이 방법밖에 없음)

1.2 임의의 숫자 찾기

Alg findKey(A, key) -- 알고리즘 이름 == int findMax(int A[])
	input array A of size n, integer key -- 배열과 key 값 입력 
	output index of key or signal of failure (-1) -- key의 위치를 return 한다.(실패하면 -1 )

1. for i <- 0 to n-1 -- 1`~  n-1까지 돌면서 
		 if (A[i] ==  key) -- key값과 같으면
				return index of key (즉, i값을 의미)
2. return -1 --key값이 없는 경우 (failure)
	

문제 = 일차원적인 데이터(= 선형적)가 있고 이 중에서 입력된 key값을 찾기

방법 = 순차탐색 ( 1.1 최대값 찾기는 무조건 탐색에 성공하여 결과를 return하지만 1.2는 탐색에 실패할 수도 있다는 차이점이 있다)

문제

만약 오름차순으로 미리 정렬되어 있는 상태였다면?

⇒ 훨씬 더 효율적인 방법이 존재 = “이진 탐색”

[ 이진탐색 ]

  1. 정가운데를 찾기
  2. 중간 숫자와 key를 비교한 후

같으면 → 탐색 끝

다르면 → 크기 비교 후 비교 대상 범위 축소

방법

  • 반복문
  • 재귀
//이진탐색트리 템플릿

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void selectionSort(int list[])
{
	int min, temp;
	for (int i = 0; i < 19; i++)
	{
		min = i;
		for (int j = i + 1; j < 20; j++)
			if (list[j] < list[min])
				min = j;
		
		temp = list[min];
		list[min] = list[i];
		list[i] = temp;
	}
}

int main()
{
    int A[20];
    srand(time(NULL)); // 임의의 숫자(난수 발생)
    for(int i = 0; i < 20; i++)
        A[i] = rand() % 100; // 여기까지 임의의 숫자 난수 발생 후 배열
        
    selectionSort(A);  // 정렬 알고리즘   
    
    for(int i = 0; i < 20; i++)
        printf("%d ", A[i]);
    
    printf("\\n");
    
    return 0;
}

이진탐색_반복문

// 이진 탐색 -> 상한과 하한을 줄여나가면서(공간을 줄이면서) key 값을 찾는다. 
//예시 20개의 배열이며 인덱스는 0 ~ 19까지 라고 가정 

int BinarySearchIter(int A[], int left, int right, int key )
{ // left = 하한, right = 상한

		int mid, count = 0; // 중간값을 기억하기 위해 mid, 몇번만에 찾았는지 기록하기 위해 count
		while (left <= right) // "탐색할 수 있는 공간이 있으면" -> 이에 만족하지 않는 경우는 상한과 하한이 크로스가 된 상황이기 때문에 찾을 공간이 없음
		{
				count++; // 탐색시도 횟수 증가 
				mid = (left + right) / 2; // 중간값 찾기 (예시 = 9)
				if (key == A[mid]) //찾는 숫자가 중간 위치의 숫자와 같다면 
					return count;
				else if (key < A[mid])// mid 위쪽에는 key값이 없을 것이므로 버린다
					right = mid - 1; // 상한선을 줄이자 ( 0~8 인덱스에서 찾자)
				else
					left = mid + 1; //하한값을 증가한다 (10 ~ 19 사이)
				
		}
	return 0;
}

int main()
{
    int A[20], k;
    srand(time(NULL)); // 임의의 숫자(난수 발생)
    for(int i = 0; i < 20; i++)
        A[i] = rand() % 100; // 여기까지 임의의 숫자 난수 발생 후 배열
        
    selectionSort(A);  // 정렬 알고리즘   
    
    for(int i = 0; i < 20; i++)
        printf("%d ", A[i]);
    
    printf("\\n");

		printf("key : ");
		scanf("%d", %k); // key 값 입력받기 
		printf("%d\\n", binarySrearchIter(A, 0, 19, k)); //몇번만에 찾았는지 
    
    return 0;
}

이진탐색_재귀함수

// 이진 탐색 -> 상한과 하한을 줄여나가면서(공간을 줄이면서) key 값을 찾는다. 
//예시 20개의 배열이며 인덱스는 0 ~ 19까지 라고 가정 

int BinarySearchRecur(int A[], int left, int right, int key )
{
    int mid = 0; 
    static int count = 0; // 재귀 함수할 때는 계속 함수를 호출하는 것이므로 지역함수일 경우 
    // 계속 초기화가 된다. -> 누적 불가, 따라서 정적 변수로 선언될 수 있기 때문에 함수가 계속 호출되어도 누적된 결과를 알 수 있음 

    if (left <= right) // 재귀 함수 빠져나가기 위해 필요한 조건
    {
        count ++;
        mid = (left + right) / 2;
        if (key == A[mid]){
            return count;
        }
        else if (key < A[mid]){
            return BinarySearchRecur(A, left, mid - 1, key) // 자신을 호출할 때 매개변수를 바꿔서 호출
        }
        else{
            BinarySearchRecur(A, mid + 1, right, key)

        }
        return 0;
    }

}

int main()
{
    int A[20], k;
    srand(time(NULL)); // 임의의 숫자(난수 발생)
    for(int i = 0; i < 20; i++)
        A[i] = rand() % 100; // 여기까지 임의의 숫자 난수 발생 후 배열
        
    selectionSort(A);  // 정렬 알고리즘   
    
    for(int i = 0; i < 20; i++)
        printf("%d ", A[i]);
    
    printf("\\n");

		printf("key : ");
		scanf("%d", %k); // key 값 입력받기 
		printf("%d\\n", binarySrearchIter(A, 0, 19, k)); //몇번만에 찾았는지 
    
    return 0;
}

1.3 동전 거스름돈

상황 = 거스름돈을 받을 때 “가장 적은 수"의 동전을 원한다. 현시점에서 가장 이득인 순으로 한다.

[ 그리디 알고리즘 (탐욕기법) ]

가장 이득인 것을 취하면서 결과를 낸다.

이 알고리즘을 통해 얻은 결과는 최상의 답이다. (하지만 문제에 따라서는 최선과 유사하지만 정확한 결과가 아닌 값인 경우도 있음)

1.4 한붓 그리기

상황 = 종이에서 연필을 떼지 않고 그래프를 그린다.

조건 = 모든 간선을 한 번만 지나야 함 + 종점이 출발점

-오일러 순환 = 정점과 정점 사이의 간선을 한 번만 지남, 시작점 = 종점

-오일러 경로 = 정점과 정점 사이의 간선을 한 번만 지난다(그래프 이론), 시작점 ≠ 종점( 오일러 순환을 포함)

-헤밀턴 = 간선은 몇번 지나도 상관없는데 모든 정점을 한 번씩만 가는 문제(외판원 문제, 51개주를 한 번만 방문하는 방법)

-참고 : 프로그래밍을 짤 때 오일러 순환이 되기 위해서는 모든 정점이 짝수여야 한다. 는 것을 반영하여 먼저 정점이 짝수인지 확인하고 그 다음 경로를 탐색하게 한다.

[ 그림 ]

6과 9의 특징을 찾아서 고안해내는 것이 알고리즘이며 그래프 이론과 관계된 알고리즘이다.

방법 : 현재 점으로 돌아오는 사이클이 있으면 진행한다.

각각 정점에서 선택을 할 때 자신의 선택 후 다시 자신을 지나서 원래의 정점으로 돌아갈 수 있어야 한다. 그렇지 못한다면 경우의 수에서 제거해 나가는 것이다.

1.5 미로 찾기

상황 = 미로 , 어두워서 표시를 볼 수 없음

해결 방안 = 오른손 법칙

출구가 있는 미로라면 무조건 오른손 법칙을 통해 출구를 찾을 수 있다.

벽에 오른손을 대고 걷기만하면 막히더라도 다시 오른쪽 벽을 타고 나오면 빠져나갈 수 있음

오른손 법칙이 해결 가능한 이유 = 오른손으로 대고 가는 과정 자체는 유연한 끈으로 이을 수 있고 이 벽들을 당긴다는 것이고 이것이 일자로 만들어질 수 있다.

백트래킹 기법의 하나의 예이다.

1.6 가짜 동전 찾기

이진 탐색에서 재귀호출했던 문제와 비슷하게 문제 해결 (분할정복 기법)

가정 = 동전의 개수 2의 거듭제곱(홀짝이 바뀌게 되면 프로그램으로 구현시 어려울 수 있음)

상황 = 동전 중 하나의 동전만 가짜이다.

조건 = 양팔 저울을 이용해서 찾는데 횟수를 최소화하여 찾아라

(1) 철수의 아이디어

한 개를 기준으로 나머지를 비교 (예를 들어 총 16개라면 1개를 올려놓고 15개를 비교)

최선 = 1번만에 가짜 동전 찾음

최악 = n -1 번째에 가짜 동전 찾음

(2) 영희 아이디어

n개를 두개씩 짝을 지어 쌍끼리 비교한다

최선 = 1번만에 가짜 동전 찾음

최악 = 마지막 “쌍"에서 가짜 동전 찾음 → 철수의 방법에 비해 1/2 로 저울 사용이 감소한다.

항상 철수의 방법보단 적음

(3) 광수 아이디어

방법 = 동전 전체를 2묶음으로 나누어 저울에 양편에 놓는다. 그 중 하나의 묶음에는 반드시 가짜 동전이 있다.

해당 묶음을 다시 2묶으로 나누어서 비교

비교횟수 = 2의 거듭제곱꼴에서 지수만큼만 하면된다(예를 들어 동전이 2의 10승개가 있다면 10번만 비교하면 된다)

장점 = 동전이 많아지면 많아질수록 더 효율적인 알고리즘이다. (빠르게 찾기 가능)

<광수 아이디어 직접해보기 → 이진탐색 재귀함수 버전과 비슷 , sum 함수를 이용해서 양쪽 무개를 비교 >

//동전이 전체 16개 

int cheolsu(int A[])
{
    int a = A[0];
    int count = 0;
    
    for(int i = 1; i < 16; i++)
    {
        count++;
        if(a != A[i])
            return count;
    }    
}

int yeonghui(int A[])
{
    int count = 0;
    for(int i = 0; i < 16; i += 2)
    {
        count++;
        if(A[i] != A[i + 1])
            return count;
    }
}

int main()
{
		int A[16]; 
    srand(time(NULL));
    for(int i = 0; i < 16; i++)
        A[i] = 10;
        
    A[rand() % 16] = 9;    
    
    for(int i = 0; i < 16; i++)
        printf("%d ", A[i]);
    printf("\\n");
    
		printf("%d %d\\n", cheolsu(A), yeonghui(A));  
}

int sum(int A[], int left, int right) //sum 함수를 통해 오른쪽과 왼쪽의 무개를 비교한다(무개의 합을 이용해서 비교 계산한다)
{
    int result = 0;
    for(int i = left; i <= right; i++)
        result += A[i];
    return result;    
}

int kwangsu(int A[], int left, int right)
{

}

'CS > 알고리즘' 카테고리의 다른 글

Chap3-4. 최근접 점의 쌍 찾기(Closest Pair)  (0) 2022.04.08
Chap3-2 퀵정렬(QuickSort)  (0) 2022.04.05
Chap3-1. 합병 정렬(MergeSort)  (0) 2022.04.04
Chap 3. 분할 정복 알고리즘  (0) 2022.04.02
Ch2. 알고리즘  (0) 2022.03.14