no image
[JAVA] 선형 검색(Linear Search), 보초법(Sentinel Method)
Do it! 자료구조와 함께 배우는 알고리즘 입문[자바편] 연습문제와 실습문제입니다. 선형 검색(순차 검색) 선형 검색은 직선 모양으로 늘어선 배열에서 원하는 키 값을 갖는 요소를 만날 때까지 맨 앞부터 순서대로 요소를 검색하는 것을 말한다. 선형 검색에서 검색의 종료 조건은 아래의 2개와 같다. 검색할 값을 발견하지 못하고 배열의 끝을 지나간 경우 검색할 값과 같은 요소를 발견한 경우 첫 번째 조건이 성립하면 검색 실패, 두 번째 조건이 성립하면 검색 성공이다. 배열의 요솟수가 n개이면 조건 1, 2를 판단하는 횟수는 평균 n/2회이다. public class Main{ static int seqSearch(int[] arr, int key) { for(int i = 0; i < arr.length; ..
2023.01.26
no image
[JAVA] 날짜 계산기 알고리즘
Do it! 자료구조와 함께 배우는 알고리즘 입문[자바편] 연습문제와 실습문제입니다. 2023.01.01을 기준으로 100일 전은2022년 9월 23일(기준날 미포함)이고, 100일 후는2023년 4월 11일이다. 이러한 알고리즘을 자바로 구현하면 아래와 같다. 클래스 선언부 static class YMD { int year; int month; int day; YMD(int y, int m, int d) //생성자 { this.year = y; this.month = m; this.day = d; } int[][] arr = { {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, //평년 arr[0][] {31, 29, 31, 30, 31, 30, 31, 31, 3..
2023.01.26
no image
[JAVA] 클래스 배열
Do it! 자료구조와 함께 배우는 알고리즘 입문[자바편] 연습문제와 실습문제입니다. 클래스 배열이란 클래스로 부터 만들어진 객체로 이루어진 배열을 뜻한다. 클래스 자체가 자료형이되어서 데이터를 더 쉽게 다룰 수 있다. 아래는 이름, 키, 시력을 저장하는 클래스의 객체로 이루어진 배열을 활용하여 평균 키와 시력 분포를 출력하는 예제 코드이다. public class Main{ static class PhyscData { //이름, 키, 시력을 저장하는 클래스 String name; int height; double vision; PhyscData(String name, int height, double vision) { //생성자 this.name = name; this.height = height; t..
2023.01.25
no image
[JAVA] 한 해의 경과 일 수 / 남은 일 수를 계산하는 알고리즘
Do it! 자료구조와 함께 배우는 알고리즘 입문[자바편] 연습문제와 실습문제입니다. 4월 15일을 예로 들면 그 해의 경과 일수를 구하면 아래와 같다. 1월의 일 수 + 2월의 일 수 + 3월의 일 수 + 15 m월 d일의 그 해 경과 일수는 아래와 같다. 1, 2, ..., m-1월의 일 수의 합 + d 그런데 2월의 일 수는 평년은 28일, 윤년은 29일로 해에 따라 달라진다. 윤년, 평년 지구가 태양 둘레를 한 바퀴 도는 일수는 정확히 365일이 아니다. 이를 조정하기 위해 4로 나누어 떨어지는 해를 윤년으로 하여 1년을 366일로 한다. 하지만 그래도 정확하기 않으므로 아래와 같은 규칙을 적용한다. -해당 연도를 4로 나누어 떨어지면 우선 윤년으로 하고 -윤년 중에서 100으로 나누어 떨어지면 ..
2023.01.25
no image
[JAVA] n이하의 소수를 구하는 알고리즘
Do it! 자료구조와 함께 배우는 알고리즘 입문[자바편] 연습문제와 실습문제입니다. 소수 소수는 자신과 1 이외의 정수로 나누어떨어지지 않는 정수이다. 예를 들어 소수 13은 2, 3, ..., 12 가운데 어떤 정수로도 나누어 떨어지지 않는다 그러므로 어떤 정수 n에 대하여 아래의 조건을 만족하면 소수임을 알 수 있다. "소수 n은 2부터 n-1까지의 어떤 정수로도 나누어 떨어지지 않는다." 만약 나누어 떨어지는 정수가 하나 이상 존재하면 그 수는 합성수이다. n 이하의 소수를 나열하는 알고리즘 (시간복잡도 높음, 공간복잡도 낮음) static void PrimeNumber(int n) { for(int i = 2; i
2023.01.24
no image
[JAVA] n진수 변환 알고리즘
Do it! 자료구조와 함께 배우는 알고리즘 입문[자바편] 연습문제와 실습문제입니다. 10진수를 n진수로 변환하는 방법을 모른다면 아래의 포스팅에서[10진수-n진수 변환] 부분을 읽고 오시는 것을 추천합니다. 2022.11.29 - [Math/이산수학] - 진수, 진법 변환, 보수 진수, 진법 변환, 보수 [진수] [10진수] 기수가 10인 수 0, 1, 2 ,3, 4, 5, 6 ,7, 8, 9 -> 10개 수로 표현 [2진수] 기수가 2인 수 0, 1 두개의 수로 표현 [8진수와 16진수] [8진수] 0~7까지 8개의 수로 표현 2진수 3자리는 8진수 1자리 2진수 rebugs.tistory.com 10진수를 2~36진수로 변환하는 알고리즘 static void cardConvR(int x, int r..
2023.01.23
no image
[JAVA] 배열 비교, 복사, 역순으로 복사 알고리즘
Do it! 자료구조와 함께 배우는 알고리즘 입문[자바편] 연습문제와 실습문제입니다. 배열 비교 두 배열의 모든 요소의 값이 같은지를 판단하는 알고리즘 static boolean equals(int[] a, int[] b) { if(a.length != b.length) return false; //배열의 길이가 다르면 false 리턴 for(int i = 0; i < a.length; ++i) if(a[i] != b[i]) return false; //요소의 값이 다르면 false 리턴 return true; //배열의 길이가 같고, 모든 요소의 값이 같으면 true 리턴 } 아래는 실행예제 public class Main{ static boolean equals(int[] a, int[] b) { i..
2023.01.22
no image
[JAVA] 배열 요소를 역순으로 정렬하는 알고리즘
Do it! 자료구조와 함께 배우는 알고리즘 입문[자바편] 연습문제와 실습문제입니다. 배열의 요소가 1, 2, 3, 4, 5, 6, 7 이렇게 7개 있다고 하면 역순으로 정렬하면 7, 6, 5, 4, 3, 2, 1이다. 그림에서 보는 것과 같이 요소들을 서로 바꿔주면 된다. 요소들을 바꿔주려면 먼저 swap함수를 정의해야한다. static void swap(int[] arr, int a, int b) //배열의 요소 값을 스왑 { int temp; temp = arr[a]; arr[a]= arr[b]; arr[b] = temp; } 매개변수 a와 b에 교환할 배열의 인덱스를 받고, 인덱스 a의 값과 인덱스 b의 값을 바꾼다.(swap) 이 swap 메소드를 응용해서 요소를 역순으로 정렬하는 알고리즘을 구..
2023.01.21

Do it! 자료구조와 함께 배우는 알고리즘 입문[자바편] 연습문제와 실습문제입니다.


선형 검색(순차 검색)

선형 검색은 직선 모양으로 늘어선 배열에서 원하는 키 값을 갖는 요소를 만날 때까지 맨 앞부터 순서대로 요소를 검색하는 것을 말한다.

 

선형 검색에서 검색의 종료 조건은 아래의 2개와 같다.

  1. 검색할 값을 발견하지 못하고 배열의 끝을 지나간 경우
  2. 검색할 값과 같은 요소를 발견한 경우

 

첫 번째 조건이 성립하면 검색 실패, 두 번째 조건이 성립하면 검색 성공이다.

배열의 요솟수가 n개이면 조건 1, 2를 판단하는 횟수는 평균 n/2회이다.

public class Main{
	static int seqSearch(int[] arr, int key)
	{
		for(int i = 0; i < arr.length; ++i) {
			if(arr[i] == key) return i;
		}
		return -1;
	}
	
	public static void main(String[] args) {
		int[] arr = {22,8,55,32,120,55,70};
		System.out.println("key의 인덱스는 " + seqSearch(arr, 55) + " 입니다.(-1이 나왔다면 찾는값이 없는 것입니다.)");
	}
}
/*
key의 인덱스는 2 입니다.(-1이 나왔다면 찾는값이 없는 것입니다.)
*/

값이 key인 요소가 여러 개 존재할 경우 반환값은 검색 과정에서 처음 발견한 요소의 인덱스가 된다.(55를 검색하면 arr[2]와 arr[5]의 두 곳에 존재하지만 가장 먼저 찾음 arr[2]의 인덱스 값인 2를 반환한다.)

 

key와 일치하는 모든 요소의 수 리턴

배열의 원소 개수가 n인 배열에서 key와 일치하는 모든 원소의 인덱스를 배열 idx의 맨 앞부터 순서대로 저장하고, 일치한 원소의 수를 리턴하는 메소드를 작성

( 예를 들어 배열의 원소가 {1,9,2,9,4,6,7,9}이고 key가 9면 배열 idx에 {1,3,7}을 저장하고 3을 리턴)

static int searchIdx(int[] arr, int key) //while문으로 구현
{
    int []idx = new int[arr.length];
    int count = 0;
    for(int i = 0; i < arr.length; ++i)
    {
        if(key == arr[i]) idx[count++] = i;
    }
    return count;
}

 

실행 예제

public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    System.out.print("입력할 데이터의 개수 입력 : ");
    int num = sc.nextInt();
    int []arr = new int[num];
    for(int i = 0; i < arr.length; ++i)
    {
        System.out.print("데이터 입력 : ");
        arr[i] = sc.nextInt();
    }
    System.out.print("찾을 데이터 입력 : ");
    num = sc.nextInt();
    System.out.println("key는 " + searchIdx(arr, num) + "개 있습니다.");
}
/*
입력할 데이터의 개수 입력 : 11
데이터 입력 : 54654
데이터 입력 : 231768
데이터 입력 : 15
데이터 입력 : 5
데이터 입력 : 9
데이터 입력 : 7
데이터 입력 : 7
데이터 입력 : 7
데이터 입력 : 6
데이터 입력 : 6
데이터 입력 : 897654
찾을 데이터 입력 : 7
key는 3개 있습니다.
*/

 

보초법(Sentinel Method)

선형 검색은 반복할 때마다 

  1. 검색할 값을 발견하지 못하고 배열의 끝을 지나간 경우
  2. 검색할 값과 같은 요소를 발견한 경우

위 두 종료 조건을 모두 판단한다. 단순한 판단이라고 생각할 수 있지만 티끌모아 태산이란 말이 있듯이 종료 조건을 검사하는 비용은 결코 무시할 수 없다.

이 비용을 절반으로 줄이는 방법이 보초법이다.

위 그림에서 배열 인덱스 0~6은 초기에 준비해 놓은 데이터이고, 검색하기 전에 검색하고자 하는 키 값을 맨 인덱스 7에 저장한다. 이때 저장하는 값을 보초라고 한다.

원하는 값이 원래의 데이터에 존재하지 않아도 보초인 인덱스 7까지 검색하면 종료 조건 2가 성립한다 이렇게 하면 원하는 키 값을 찾지 못했을 때를 판단하는 종료 조건인 1이 없어도 된다.

보초는 반복문에서 종료 판단 횟수를 2회에서 1회로 줄이는 역할을 한다.

public class Main{
	static int seqSearch(int[] arr, int key)
	{
		arr[arr.length-1] = key; //보초 추가
		int i;
		for(i = 0; i < arr.length; ++i) {
			if(arr[i] == key) break;
		}
		return i == arr.length-1 ? -1 : i;
	}
	
	public static void main(String[] args) {
		int arr[] = new int[8];
		arr[0] = 22; arr[1] = 8; arr[2] = 55; arr[3] = 32; arr[4] = 120; arr[5] = 55; arr[6] = 70;
		
		System.out.println("key의 인덱스는 " + seqSearch(arr, 120) + " 입니다.(-1이 나왔다면 찾는값이 없는 것입니다.)");
	}
}
/*
key의 인덱스는 4 입니다.(-1이 나왔다면 찾는값이 없는 것입니다.)
*/

선형 검색에선 return문이 두 개였는데, 보초법에선 return문이 한 개다.  이유는 종료 조건 1이 필요하지 않기 때문이다. 

따라서 반복 종료에 대한 판단 횟수는 절반으로 줄어든다.

반복문에 의한 반복이 완료되면 찾은 값이 배열의 원래 데이터인지 아니면 보초인지 판단해야 한다. 변수 i의 값이 배열의 길이를 -1을 한 값과 같다면 찾은 값이 보초이므로 -1을 리턴한다.

Do it! 자료구조와 함께 배우는 알고리즘 입문[자바편] 연습문제와 실습문제입니다.


2023.01.01을 기준으로 100일 전은2022년 9월 23일(기준날 미포함)이고,

100일 후는2023년 4월 11일이다.

이러한 알고리즘을 자바로 구현하면 아래와 같다.

클래스 선언부

static class YMD { 
		int year;
		int month;
		int day;

		YMD(int y, int m, int d) //생성자
		{
			this.year = y;
			this.month = m;
			this.day = d;
		}
		
		int[][] arr = {
		        {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, //평년 arr[0][]
		        {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} //윤년 arr[1][]
		};
	
		static int isLeap(int year) { //윤년이면 1을 리턴, 평년이면 0을 리턴
			return (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) ? 1 : 0;
		}
		
		YMD after(int n) //n일 후 날짜를 리턴하는 메소드
		{
			YMD temp = new YMD(this.year, this.month, this.day);
			if (n < 0)
				return (before(-n));

			temp.day += n;

			while (temp.day > arr[isLeap(temp.year)][temp.month - 1]) {
				temp.day -= arr[isLeap(temp.year)][temp.month - 1];
				if (++temp.month > 12) {
					temp.year++;
					temp.month = 1;
				}
			}
			return temp;
		}
		
		YMD before(int n) //n일 전 날짜를 리턴하는 메소드
		{
			YMD temp = new YMD(this.year, this.month, this.day);
			if (n < 0)
				return (after(-n));

			temp.day -= n;

			while (temp.day < 1) {
				if (--temp.month < 1) {
					temp.year--;
					temp.month = 12;
				}
				temp.day += arr[isLeap(temp.year)][temp.month - 1];
			}
			return temp;
		}
	}

 

실행예제

public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);

    System.out.print("날짜를 입력하세요. 입력 예 : 2023 1 18\n");
    int y, m, d;
    y = sc.nextInt();
    m = sc.nextInt();
    d = sc.nextInt();
    YMD date = new YMD(y, m, d);

    System.out.print("몇 일 앞/뒤의 날짜를 구할까요?:");
    int n = sc.nextInt();

    YMD d1 = date.after(n);
    System.out.printf("%d일 뒤의 날짜는 %d년 %d월 %d일입니다.\n", n, d1.year, d1.month, d1.day);

    YMD d2 = date.before(n);
    System.out.printf("%d일 앞의 날짜는 %d년 %d월 %d일입니다.\n", n, d2.year, d2.month, d2.day);
}
/*
날짜를 입력하세요. 입력 예 : 2023 1 18
2023 1 18
몇 일 앞/뒤의 날짜를 구할까요?:1000
1000일 뒤의 날짜는 2025년 10월 14일입니다.
1000일 앞의 날짜는 2020년 4월 23일입니다.
*/

네이버 날짜 계산기는 기준일을 1일로 치지만 위 알고리즘은 기준일을 0일로 삼는다.

네이버는 내일을 2일째 되는날이라고 하지만, 위 알고리즘은 1일째 되는날이라고 한다.

설명
arr의 행에 오는 isLeap()메소드는 temp의 연도가 윤년이면 1, 평년이면 0을 리턴한다.
열에오는 temp의 month에 -1을 한 이유는 배열의 인덱스는 0부터 시작하기 때문이다.
2023.01.18에 1000일을 한 temp객체는 초기에 2023.01.1018로 되어있다.
while루프는 temp의 day가 달의 일수보다 같거나 작을 때 까지 반복한다.
day는 루프를 돌면서 계속 달의 일수만큼 감소된다.
달의 일수가 감소할 때, 달은 계속 증가하게 되고, 달이 12를 넘어가면 연도를 +1해준다.

2024년은 윤년이므로 2월이 29일 인 것을 볼 수 있다.

 

 

Do it! 자료구조와 함께 배우는 알고리즘 입문[자바편] 연습문제와 실습문제입니다.


클래스 배열이란 클래스로 부터 만들어진 객체로 이루어진 배열을 뜻한다.

클래스 자체가 자료형이되어서 데이터를 더 쉽게 다룰 수 있다.

아래는 이름, 키, 시력을 저장하는 클래스의 객체로 이루어진 배열을 활용하여

평균 키와 시력 분포를 출력하는 예제 코드이다.

public class Main{
	static class PhyscData { //이름, 키, 시력을 저장하는 클래스
		String name;
		int    height;
		double vision;

		PhyscData(String name, int height, double vision) { //생성자
			this.name 	= name;
			this.height = height;
			this.vision = vision;
		}
	}

	static double aveHeight(PhyscData[] arr) { //평균 키를 리턴하는 메소드
		double sum = 0;

		for (int i = 0; i < arr.length; i++)
			sum += arr[i].height;

		return sum / arr.length;
	}

	static void distVision(PhyscData[] arr) { //시력 분포를 출력하는 메소드
		int[] distribution = new int[5];
		for (int i = 0; i < arr.length; i++)
		{
			if(0.0 <= arr[i].vision && arr[i].vision < 0.5) ++distribution[0];
			else if(arr[i].vision < 1.0) ++distribution[1];
			else if(arr[i].vision < 1.5) ++distribution[2];
			else if(arr[i].vision < 2.0) ++distribution[3];
			else if(2.0 <= arr[i].vision) ++distribution[4];
		}
		
			System.out.println("0.5 미만 " + distribution[0] + "명");
			System.out.println("1.0 미만 " + distribution[1] + "명");
			System.out.println("1.5 미만 " + distribution[2] + "명");
			System.out.println("2.0 미만 " + distribution[3] + "명");
			System.out.println("2.0 이상 " + distribution[4] + "명");
	}
	
	public static void main(String[] args) {
			PhyscData[] arr = { //PhyscData타입 객체를 저장하는 배열
			new PhyscData("박현규", 162, 0.3),
			new PhyscData("함진아", 173, 0.7),
			new PhyscData("최윤미", 175, 2.0),
			new PhyscData("홍연의", 171, 1.5),
			new PhyscData("이수진", 168, 0.4),
			new PhyscData("김영준", 174, 1.2),
			new PhyscData("박용규", 169, 0.8),
		};
		System.out.println("이름\t키\t시력");
		for(int i = 0; i < arr.length; ++i) System.out.println(arr[i].name + "\t" + arr[i].height + "\t" + arr[i].vision);
		System.out.printf("평균 키 : %.2fCM\n", aveHeight(arr));

		distVision(arr);

	}
}
/*
이름	키	시력
박현규	162	0.3
함진아	173	0.7
최윤미	175	2.0
홍연의	171	1.5
이수진	168	0.4
김영준	174	1.2
박용규	169	0.8
평균 키 : 170.29CM
0.5 미만 2명
1.0 미만 2명
1.5 미만 1명
2.0 미만 1명
2.0 이상 1명
*/

시력 분포를 위 사진처럼 기호 문자 *를 사람 수만큼 반복하여 출력

static void distVision(PhyscData[] arr) { //시력 분포를 출력하는 메소드
    int[] distribution = new int[5];
    for (int i = 0; i < arr.length; i++)
    {
        if(0.0 <= arr[i].vision && arr[i].vision < 0.5) ++distribution[0];
        else if(arr[i].vision < 1.0) ++distribution[1];
        else if(arr[i].vision < 1.5) ++distribution[2];
        else if(arr[i].vision < 2.0) ++distribution[3];
        else if(2.0 <= arr[i].vision) ++distribution[4];
    }

    for(int i = 0; i < distribution.length; ++i)
    {
        if(i == 0) System.out.print("0.5 미만 : ");
        else if(i == 1) System.out.print("1.0 미만 : ");
        else if(i == 2) System.out.print("1.5 미만 : ");
        else if(i == 3) System.out.print("2.0 미만 : ");
        else if(i == 4) System.out.print("2.0 이상 : ");
        for(int j = 0; j < distribution[i]; ++j)
        {
            System.out.print("*");
        }
        System.out.println();
    }
}

Do it! 자료구조와 함께 배우는 알고리즘 입문[자바편] 연습문제와 실습문제입니다.


4월 15일을 예로 들면 그 해의 경과 일수를 구하면 아래와 같다.

  • 1월의 일 수 + 2월의 일 수 + 3월의 일 수 + 15

m월 d일의 그 해 경과 일수는 아래와 같다.

  • 1, 2, ..., m-1월의 일 수의 합 + d

그런데 2월의 일 수는 평년은 28일, 윤년은 29일로 해에 따라 달라진다.

윤년, 평년
지구가 태양 둘레를 한 바퀴 도는 일수는 정확히 365일이 아니다.
이를 조정하기 위해 4로 나누어 떨어지는 해를 윤년으로 하여 1년을 366일로 한다.
하지만 그래도 정확하기 않으므로 아래와 같은 규칙을 적용한다.
-해당 연도를 4로 나누어 떨어지면 우선 윤년으로 하고
-윤년 중에서 100으로 나누어 떨어지면 다시 평년으로 한다.
-하지만 평년 중에서 400으로 나누어 떨어지면 다시 윤년으로 한다.

윤년인지 평년인지 구분하는 조건문은 아래와 같다.
if(y % 4 == 0 && y % 100 != 0 || y % 400 == 0) //이 조건식이 참이면 윤년이다.

else //위 조건식이 참이 아니면 평년이다.​

 

int[][] arr = {
        {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, //평년 arr[0][]
        {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} //윤년 arr[1][]
};

평년이면 위 코드처럼 2차원 배열을 선언하고 arr[0][]을 이용하고, 윤년이면 arr[1][]을 이용하면 된다.

 

한 해의 경과일 수를 계산하는 알고리즘

static int dayOfYear(int y, int m, int d)
{
    int[][] arr = {
            {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
            {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
    };
    int days = d;
    if(y % 4 == 0 && y % 100 != 0 || y % 400 == 0) for(int i = 1; i < m; ++i) days += arr[1][i-1]; //윤년일 때
    else for(int i = 1; i < m; ++i) days += arr[0][i-1]; //평년일 때

    return days;
}

 

실행 예제

public class Main{
	static int dayOfYear(int y, int m, int d)
	{
		int[][] arr = {
				{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
				{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
		};
		int days = d;
		if(y % 4 == 0 && y % 100 != 0 || y % 400 == 0) for(int i = 1; i < m; ++i) days += arr[1][i-1]; //윤년일 때
		else for(int i = 1; i < m; ++i) days += arr[0][i-1]; //평년일 때
		
		return days;
	}
	public static void main(String[] args) {
		System.out.println(dayOfYear(2022, 3, 1)); //평년
		System.out.println(dayOfYear(2023, 3, 1)); //평년
		System.out.println(dayOfYear(2024, 3, 1)); //윤년
	}
}
/*
60
60
61
*/

 

while문으로 작성

while 문으로 변수 i와 days 없이 구현

static int dayOfYear(int y, int m, int d)
{
    int[][] arr = {
            {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
            {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
    };

    if(y % 4 == 0 && y % 100 != 0 || y % 400 == 0) while(--m != 0) d += arr[1][m-1];
    else while(--m != 0) d += arr[0][m-1];

    return d;
}

 

 

한 해의 남은 일 수를 계산하는 알고리즘

경과한 일 수를 구할 수 있다면 남은 일 수를 계산하는 것은 간단하다.

  • 윤년이면 366일에서 경과일 수를 빼준다.
  • 평년이면 365일에서 경과일 수를 빼준다.
static int leftDayOfYear(int y, int m, int d)
{
    int[][] arr = {
            {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
            {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
    };
    int days = d;
    if(y % 4 == 0 && y % 100 != 0 || y % 400 == 0) //윤년일 때
    {
        for(int i = 1; i < m; ++i) days += arr[1][i-1];
        return 366 - days;
    }
    else //평년일 때
    {
        for(int i = 1; i < m; ++i) days += arr[0][i-1];
        return 365 - days;
    }
}

 

실행 예제

public class Main{
	static int leftDayOfYear(int y, int m, int d)
	{
	    int[][] arr = {
	            {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
	            {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
	    };
	    int days = d;
	    if(y % 4 == 0 && y % 100 != 0 || y % 400 == 0) //윤년일 때
	    {
	    	for(int i = 1; i < m; ++i) days += arr[1][i-1];
	    	return 366 - days;
	    }
	    else //평년일 때
	    {
	    	for(int i = 1; i < m; ++i) days += arr[0][i-1];
	    	return 365 - days;
	    }
	}
	public static void main(String[] args) {
		System.out.println(leftDayOfYear(2022, 2, 28)); //평년
		System.out.println(leftDayOfYear(2023, 2, 28)); //평년
		System.out.println(leftDayOfYear(2024, 2, 28)); //윤년
	}
}
/*
306
306
307
*/

Do it! 자료구조와 함께 배우는 알고리즘 입문[자바편] 연습문제와 실습문제입니다.


소수
소수는 자신과 1 이외의 정수로 나누어떨어지지 않는 정수이다.
예를 들어 소수 13은 2, 3, ..., 12 가운데 어떤 정수로도 나누어 떨어지지 않는다
그러므로 어떤 정수 n에 대하여 아래의 조건을 만족하면 소수임을 알 수 있다.
"소수 n은 2부터 n-1까지의 어떤 정수로도 나누어 떨어지지 않는다."
만약 나누어 떨어지는 정수가 하나 이상 존재하면 그 수는 합성수이다.

 

n 이하의 소수를 나열하는 알고리즘 (시간복잡도 높음, 공간복잡도 낮음)

static void PrimeNumber(int n)
{
    for(int i = 2; i <= n; ++i)
    {
        for(int j = 2; j <= i; ++j)
        {
            if(i == j) System.out.println(i);
            if(i % j == 0) break;
        }
    }
}
설명
바깥쪽 루프는 2부터 n까지 반복을 하고, 안쪽 루프는 2부터 i까지 반복한다.
i와 j가 같다면(안쪽 루프와 바깥쪽 루프를 모두 돌았다면) 소수인 것이므로 출력한다.
i를 j로 나눈 나머지가 0이라면 소수가 아니므로 루프(안쪽)를 탈출한다.

 

실행 예제

public class Main{
	static void PrimeNumber(int n)
	{
		for(int i = 2; i <= n; ++i)
		{
			for(int j = 2; j <= i; ++j)
			{
				if(i == j) System.out.println(i);
				if(i % j == 0) break;
			}
		}
	}
	public static void main(String[] args) {
		PrimeNumber(55);
	}
}
/*
2
3
5
7
11
13
17
19
23
29
31
37
41
43
47
53
*/

 

 

알고리즘 개선1 (시간복잡도 중간, 공간복잡도 높음)

처음 제시한 알고리즘은 불필요한 연산을 하고 있다.

어떤 수가 2 또는 3으로 나누어 떨어지지 않으면 2X2인 4 또는 2X3인 6으로도 나누어 떨어지지 않는다.

따라서 어떤 수가 소수인지 여부는 아래의 조건을 만족하는지 조사하면 된다.

"소수 n은 2부터 n-1까지 어떤 소수로도 나누어 떨어지지 않는다."

예를 들어 7이 소수인지는 7보다 작은 소수(2, 3, 5)로 나눗셈을 하면 충분하다. 이렇게 하면 성능을 향상할 수 있다.

static void PrimeNumber(int n)
{
    int []arr = new int[n]; //소수를 저장하기 위한 배열을 동적할당
    int count = 0; //소수의 개수를 저장
    arr[count++] = 2; //arr[0]에 2를 저장하고 count를 +1 증가시킴
    for(int i = 3; i <= n; i += 2) //짝수는 검사하지 않음
    {
        int j;
        for(j = 1; j < count; ++j) if(i % arr[j] == 0) break;
        if(j == count) arr[count++] = i;
    }
    for(int i = 0; i < count; ++i) System.out.println(arr[i]);
}
설명
2는 소수이므로 arr[0]에는 2를 넣고
바깥 루프는 3부터 n까지 반복을 진행하고, 안쪽 루프는 1부터 count -1까지 반복한다.
안쪽 루프에서 i를 arr[j]로 나누고 나머지가 0이면 루프를 탈출한다.
안쪽 루프를 다 돌고 j와 count가 같다면 arr[count]에 i를 대입한다.


위 알고리즘은 시간복잡도는 감소하지만, 공간복잡도는 증가한다. (연산횟수는 감소하지만, 메모리 사용량은 증가한다)

 

실행예제

public class Main{
	static void PrimeNumber(int n)
	{
		int []arr = new int[n]; //소수를 저장하기 위한 배열을 동적할당
		int count = 0; //소수의 개수를 저장
		arr[count++] = 2; //arr[0]에 2를 저장하고 count를 +1 증가시킴
		for(int i = 3; i <= n; i += 2) //짝수는 검사하지 않음
		{
			int j;
			for(j = 1; j < count; ++j) if(i % arr[j] == 0) break;
			if(j == count) arr[count++] = i;
		}
		for(int i = 0; i < count; ++i) System.out.println(arr[i]);
	}
	public static void main(String[] args) {
		PrimeNumber(55);
	}
}
/*
2
3
5
7
11
13
17
19
23
29
31
37
41
43
47
53
*/

 

알고리즘 개선2 (시간복잡도 낮음, 공간복잡도 높음)

100의 약수를 구하는 과정은 아래와 같다.

  • 2 X 50
  • 4 X 25
  • 5 X 20
  • 10 X 10
  • 20 X 5
  • 25 X 4
  • 50 X 2

10 X 10을 기준으로 위아래는 똑같다.

기준을 잡고 기준을 포함해서 위나 아래만 하나를 골라서 소수로 나눗셈을 시도하고, 그 과정에서 한 번도 나누어 떨어지지 않으면 소수라고 판단한다는 것이다.

(예를 들어 100이 5로 나누어떨어지지 않는다면 20으로도 나눠어 떨어지지 않는다.)

따라서 어떤 수가 소수인지 여부는 아래의 조건을 만족하는지 조사하면 된다.

"소수 n은 n의 제곱근 이하의 어떤 소수로도 나누어 떨어지지 않는다."

static void PrimeNumber(int k)
{
    int count = 0;
    int[] arr = new int[k];

    arr[count++] = 2;
    arr[count++] = 3;

    for (int i = 5 ; i <= k; i += 2) {
        boolean flag = false;
        for (int j = 1; arr[j] * arr[j] <= i; j++) {
            if (i % arr[j] == 0) flag = true; break;
        }
        if (!flag) arr[count++] = i;
    }
    for (int i = 0; i < count; i++)System.out.println(arr[i]);
}

 

실행예제

public class Main{
	static void PrimeNumber(int k)
	{
		int count = 0;
		int[] arr = new int[k];

		arr[count++] = 2;
		arr[count++] = 3;

		for (int i = 5 ; i <= k; i += 2) {
			boolean flag = false;
			for (int j = 1; arr[j] * arr[j] <= i; j++) {
				if (i % arr[j] == 0) flag = true; break;
			}
			if (!flag) arr[count++] = i;
		}
		for (int i = 0; i < count; i++)System.out.println(arr[i]);
	}
	public static void main(String[] args) {
		PrimeNumber(1000);
	}
}
/*
2
3
5
7
11
13
17
19
23
29
31
37
41
43
47
53
*/

 

첫번째 알고리즘의 연산 횟수는 78022

두 번째 알고리즘의 연산 횟수는 14622

세 번째 알고리즘의 연산 횟수는 3774

Do it! 자료구조와 함께 배우는 알고리즘 입문[자바편] 연습문제와 실습문제입니다.

 

10진수를 n진수로 변환하는 방법을 모른다면 아래의 포스팅에서[10진수-n진수 변환] 부분을 읽고 오시는 것을 추천합니다.

2022.11.29 - [Math/이산수학] - 진수, 진법 변환, 보수

 

진수, 진법 변환, 보수

[진수] [10진수] 기수가 10인 수 0, 1, 2 ,3, 4, 5, 6 ,7, 8, 9 -> 10개 수로 표현 [2진수] 기수가 2인 수 0, 1 두개의 수로 표현 [8진수와 16진수] [8진수] 0~7까지 8개의 수로 표현 2진수 3자리는 8진수 1자리 2진수

rebugs.tistory.com


10진수를 2~36진수로 변환하는 알고리즘

static void cardConvR(int x, int r)
{
    char []d = new char[32];
    int digits = 0; //변환된 진수의 자릿수를 저장
    String dchar = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    do {
        d[digits++] = dchar.charAt(x % r); // r로 나눈 나머지를 저장
        x /= r;
    } while (x != 0);
    for(int i = digits-1; i>=0; i--) System.out.print(d[i]);
}
설명
위와 같은 결과로 이루어진 배열을 역순으로 출력하면
진수변환이 되는 것이다.
문자 추출 charAt() 메소드
charAt() 메소드는 매개값으로 주어진 인덱스의 문자를 리턴한다.
dchar.charAt(x % r)에서 x는 59 r이 16이라고 가정하면 59를 16로 나눈 후 나머지는 11이다
dchar배열의 인덱스 11은 B이므로 B가 리턴된다.

 

실행 예제

import java.util.Scanner;
public class Main{
	static void cardConvR(int x, int r)
	{
	    char []d = new char[32];
	    int digits = 0; //변환된 진수의 자릿수를 저장
	    String dchar = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
	    do {
	        d[digits++] = dchar.charAt(x % r); // r로 나눈 나머지를 저장
	        x /= r;
	    } while (x != 0);
	    for(int i = digits-1; i>=0; i--) System.out.print(d[i]);
	}
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		System.out.print("변환할 정수 입력 : ");
		int num = sc.nextInt();
		System.out.print("진수 입력 : ");
		int n = sc.nextInt();
		cardConvR(num,n);
	}
}
/*
변환할 정수 입력 : 59
진수 입력 : 2
111011
*/

 

 

자세한 계산과정을 출력하는 알고리즘

static void cardConvR(int x, int r)
{
    char []d = new char[32];
    int digits = 0; //변환된 진수의 자릿수를 저장
    String dchar = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    System.out.printf("%2d | %6d\n",r,x);
    System.out.println("   + -------");
    do {
        d[digits++] = dchar.charAt(x % r); // r로 나눈 나머지를 저장
        if((x/r) == 0) System.out.printf("     %6d ... %2c\n", x/r, dchar.charAt(x % r));
        else System.out.printf("%2d | %6d ... %2c\n", r, x/r, dchar.charAt(x % r));
        x /= r;
        if(x != 0) System.out.println("   + -------");

    } while (x != 0);
    System.out.print("변환된 값 : ");
    for(int i = digits-1; i>=0; i--) System.out.print(d[i]);
}

 

실행예제

import java.util.Scanner;
public class Main{
	 static void cardConvR(int x, int r)
    {
        char []d = new char[32];
        int digits = 0; //변환된 진수의 자릿수를 저장
        String dchar = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        System.out.printf("%2d | %6d\n",r,x);
        System.out.println("   + -------");
        do {
            d[digits++] = dchar.charAt(x % r); // r로 나눈 나머지를 저장
            if((x/r) == 0) System.out.printf("     %6d ... %2c\n", x/r, dchar.charAt(x % r));
            else System.out.printf("%2d | %6d ... %2c\n", r, x/r, dchar.charAt(x % r));
            x /= r;
            if(x != 0) System.out.println("   + -------");

        } while (x != 0);
        System.out.print("변환된 값 : ");
        for(int i = digits-1; i>=0; i--) System.out.print(d[i]);
    }
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		System.out.print("변환할 정수 입력 : ");
		int num = sc.nextInt();
		System.out.print("진수 입력 : ");
		int n = sc.nextInt();
		cardConvR(num,n);
	}
}
/*
변환할 정수 입력 : 59
진수 입력 : 8
 8 |     59
   + -------
 8 |      7 ...  3
   + -------
          0 ...  7
변환된 값 : 73
*/

Do it! 자료구조와 함께 배우는 알고리즘 입문[자바편] 연습문제와 실습문제입니다.


배열 비교

두 배열의 모든 요소의 값이 같은지를 판단하는 알고리즘

static boolean equals(int[] a, int[] b)
	{
		if(a.length != b.length) return false; //배열의 길이가 다르면 false 리턴
		
		for(int i = 0; i < a.length; ++i) if(a[i] != b[i]) return false; //요소의 값이 다르면 false 리턴
		
		return true; //배열의 길이가 같고, 모든 요소의 값이 같으면 true 리턴
	}

 

아래는 실행예제

public class Main{
	static boolean equals(int[] a, int[] b)
	{
		if(a.length != b.length) return false; //배열의 길이가 다르면 false 리턴
		
		for(int i = 0; i < a.length; ++i) if(a[i] != b[i]) return false; //요소의 값이 다르면 false 리턴
		
		return true; //배열의 길이가 같고, 모든 요소의 값이 같으면 true 리턴
	}
	
	public static void main(String[] args) {
		int[] array = {1,2,3,4,5,6,7};
		int[] array2 = {1,2,3,4,5,6};
		int[] array3 = {2,5,3,6,7,7,8};
		int[] array4 = {1,2,3,4,5,6,7};
		
		System.out.println(equals(array, array2));
		System.out.println(equals(array, array3));
		System.out.println(equals(array, array4));
		
	}
}
/*
false
false
true
*/

 

 

배열 복사

배열 a와 b를 매개값으로 받아서 배열b의 요소를 a배열로 복사하는 메소드를 작성

static void copy(int[] a, int[] b)
	{
		if(a.length < b.length) //b의 길이가 a보다 크면
		{
			System.out.println("배열의 길이가 모자릅니다.");
			return;
		}
		for(int i = 0; i < b.length; ++i) a[i] = b[i]; //배열 요소 복사
	}
아래의 코드는 a배열의 길이가 더 작아도 작은 길이 만큼만 복사하는 알고리즘
static void copy(int[] a, int[] b)
	{
		int num = a.length <= b.length ? a.length : b.length;
		for (int i = 0; i < num; i++) a[i] = b[i];
	}​

 

실행 예제

public class Main{
	static void copy(int[] a, int[] b)
	{
		if(a.length < b.length) //b의 길이가 a보다 크면
		{
			System.out.println("배열의 길이가 모자릅니다.");
			return;
		}
		for(int i = 0; i < b.length; ++i) a[i] = b[i]; //배열 요소 복사
	}
	static void printArray(int[] array) //배열 요소 출력
	{
		for(int i = 0; i < array.length; ++i)
		{
			if(i == array.length-1) {System.out.println(array[i]); break;}
			System.out.print(array[i] + ", ");
		}
	}
	
	public static void main(String[] args) {
		int[] array = {5,3,32,9,6,3,8};
		int[] array2 = {1,2,3,4,5,6,7};
		
		printArray(array);
		copy(array,array2); //array2를 array에 복사
		printArray(array);
	}
}
/*
5, 3, 32, 9, 6, 3, 8
1, 2, 3, 4, 5, 6, 7
*/

 

 

배열 역순으로 복사

배열 a와 b를 매개값으로 받아서 배열 b의 요소를 a배열로 역순으로 복사하는 메소드를 작성

static void rcopy(int[] a, int[] b)
	{
		if(a.length < b.length) //배열 b의 길이가 더 크면
		{
			System.out.println("배열의 길이가 모자릅니다.");
			return;
		}
		for(int i = 0; i < b.length; ++i) a[i] = b[b.length-i -1]; //배열 요소 복사
	}
아래의 코드는 a배열의 길이가 더 작아도 작은 길이 만큼만 복사하는 알고리즘
static void rcopy(int[] a, int[] b) {
		int num = a.length <= b.length ? a.length : b.length;
		for (int i = 0; i < num; i++)a[i] = b[b.length - i - 1];
	}​

 

실행예제

public class Main{
	static void rcopy(int[] a, int[] b)
	{
		if(a.length < b.length) //배열의 길이가 서로 다르면
		{
			System.out.println("배열의 길이가 모자릅니다.");
			return;
		}
		for(int i = 0; i < b.length; ++i) a[i] = b[b.length-i -1]; //배열 요소 복사
	}
	static void printArray(int[] array) //배열 요소 출력
	{
		for(int i = 0; i < array.length; ++i)
		{
			if(i == array.length-1) {System.out.println(array[i]); break;}
			System.out.print(array[i] + ", ");
		}
	}
	
	public static void main(String[] args) {
		int[] array = {1,2,3,4,5,6,7};
		int[] array2 = {10,11,12,13,14,15,16};
		
		printArray(array);
		rcopy(array,array2);
		printArray(array);
	}
}
/*
1, 2, 3, 4, 5, 6, 7
16, 15, 14, 13, 12, 11, 10
*/

 

위 알고리즘이 이해가 안간다면 아래의 글을 읽는 것을 추천합니다.

2023.01.21 - [Data Structure & Algorithm] - [JAVA] 배열 요소를 역순으로 정렬하는 알고리즘

Do it! 자료구조와 함께 배우는 알고리즘 입문[자바편] 연습문제와 실습문제입니다.


배열의 요소가 1, 2, 3, 4, 5, 6, 7 이렇게 7개 있다고 하면 역순으로 정렬하면 7, 6, 5, 4, 3, 2, 1이다.

 

 

그림에서 보는 것과 같이 요소들을 서로 바꿔주면 된다.

요소들을 바꿔주려면 먼저 swap함수를 정의해야한다.

static void swap(int[] arr, int a, int b) //배열의 요소 값을 스왑
	{
		int temp;
		temp = arr[a];
		arr[a]= arr[b];
		arr[b] = temp;
	}

매개변수 a와 b에 교환할 배열의 인덱스를 받고, 인덱스 a의 값과 인덱스 b의 값을 바꾼다.(swap)

 

이 swap 메소드를 응용해서 요소를 역순으로 정렬하는 알고리즘을 구현할 수 있다.

static void reverseArray(int[] arr) //배열 요소를 역순으로 정렬
	{
		for(int i = 0; i < arr.length/2; ++i)
		{
			swap(arr, i, arr.length - i-1);
		}
	}

arr.length(배열의 길이)가 홀수여도 상관없다. arr.length가 7이라고 가정하면 arr.length / 2 는 3이기 때문이다.(i는 int형 타입이기 때문에 3.5가 아니다.)

(arr.length(배열의 길이)가 짝수면 당연히 상관없음)

0부터 arr.length까지 반복을 진행하고, swap 메소드의 매개값에 배열, i, arr.length -i -1을 넘겨준다.

왜 이러한 매개값을 넘기는지는 아래와 같다.

  • 배열(arr) : 매개값으로 참조된 배열의 값을 정렬하기 위해
  • i : 배열(arr)의 i번째 인덱스의 값과 arr.length- i -1번째 인덱스의 값을 바꾸기 위해
  • arr.length -i -1 : 위와 동일

배열의 인덱스는 0부터 시작함을 염두하고 잘 생각해 보면 이해가 될 것이다.

 

위의 swap메소드와 reverseArray메소드를 합치면 아래와 같다.

static void reverseArray(int[] arr) //배열 요소를 역순으로 정렬
{
    for(int i = 0; i < arr.length/2; ++i)
    {
        int temp = arr[i];
        arr[i]= arr[arr.length - i-1];
        arr[arr.length - i-1] = temp;
    }
}

 

아래의 코드는 실행 예제이다.

public class Main{
	static void reverseArray(int[] arr) //배열 요소를 역순으로 정렬
	{
			
		for(int i = 0; i < arr.length/2; ++i)
		{
			int temp = arr[i];
			arr[i]= arr[arr.length - i-1];
			arr[arr.length - i-1] = temp;
		}
	}
	
	static void printArray(int[] array) //배열 요소 출력
	{
		for(int i = 0; i < array.length; ++i)
		{
			if(i == array.length-1) {System.out.println(array[i]); break;}
			System.out.print(array[i] + ", ");
		}
	}
	
	public static void main(String[] args) {
		int[] array = {1,2,3,4,5,6,7};
		printArray(array);
		reverseArray(array);
		printArray(array);
	}
}
/*
1, 2, 3, 4, 5, 6, 7
7, 6, 5, 4, 3, 2, 1
*/

 

좀 더 자세히 스왑과정을 보여주는 예제
public class Main{
	static void swap(int[] arr, int a, int b) //배열의 요소 값을 스왑
	{
		int temp;
		temp = arr[a];
		arr[a]= arr[b];
		arr[b] = temp;
		System.out.println("Index " + a + "와(과) " + b + "를 교환");
		printArray(arr);
	}
	
	static void reverseArray(int[] arr) //배열 요소를 역순으로 정렬
	{
		for(int i = 0; i < arr.length/2; ++i)
		{
			swap(arr, i, arr.length - i-1);
		}
	}
	
	static void printArray(int[] array) //배열 요소 출력
	{
		for(int i = 0; i < array.length; ++i)
		{
			if(i == array.length-1) {System.out.println(array[i]); break;}
			System.out.print(array[i] + ", ");
		}
	}
	
	public static void main(String[] args) {
		int[] array = {1,2,3,4,5,6,7};
		printArray(array);
		reverseArray(array);
		//printArray(array);
	}
}
/*
1, 2, 3, 4, 5, 6, 7
Index 0와(과) 6를 교환
7, 2, 3, 4, 5, 6, 1
Index 1와(과) 5를 교환
7, 6, 3, 4, 5, 2, 1
Index 2와(과) 4를 교환
7, 6, 5, 4, 3, 2, 1
*/​