정의 및 특징

운영체제란(Operating System)

  • 컴퓨터의 시스템 자원들을 효율적으로 관리함으로써 성능을 높이고, 사용자에게 편의성을 제공하기 위해 사용됨
  • 사용자와 컴퓨터 하드웨어 간의 인터페이스로서 동작하는 시스템 소프트웨어의 일종
  • 운영체제와 유사하나 소프트웨어를 추가로 설치할 수 없는 것을 펌웨어(firmware)라고 함

 

운영체제의 역할

  1. 자원 관리
    • 프로세스 관리
      • 프로세스 스케줄링을 통한 CPU 소유권 할당 및 동기화
      • 프로세스 생성과 제거, 시작과 정지, 자원 할당 및 반환
    • 메모리/주변장치 관리
      • 한정된 메모리를 어떤 프로세스에 얼마나 할당할지
      • 디스크 파일을 어떻게 보관할지
      • 입출력 장치 스케줄링 및 전반적인 관리
    • 파일 관리
      • 파일의 생성과 삭제, 변경, 유지 등
  2. 자원 보호
    • 프로그램이나 다른 사용자가 중요 데이터에 접근하지 못하게 컴퓨터 자원들을 보호

 

운영체제의 목적

  • 처리능력, 사용 가능도, 신뢰도 향상
  • 응답 시간 감소
응답시간 (Turn Around Time) 작업 의뢰 후 시스템에서 결과가 얻어질 때까지의 시간
처리능력 (Throughput) 시스템의 생산성을 나타내는 단위. 일정기간 동안 처리하는 일의 양
사용가능도 (Availability) 시스템을 얼마나 빠르게 사용할 수 있는가의 정도
신뢰도 (Reliability) 주어진 문제를 얼마나 정확하게 처리하는가의 정도

 


운영체제의 구조

전체적인 구조

  • 유저 프로그램이 맨 위에 있고 가장 밑에 하드웨어가 위치
  • 가운데 인터페이스, 시스템콜, 커널, 드라이버 부분이 운영체제를 지칭함

 

인터페이스

https://youtu.be/onMSNY45giw

  • 사용자는 커널에 직접 접근할 수 없기 때문에 OS가 제공하는 인터페이스를 사용해야 함
  • 대표적으로 GUI(Graphical User Interface)CLI(Command Line Interface)가 있음
  • 사용자와 애플리케이션은 인터페이스를 통해 커널에 명령을 전달하고, 실행결과를 전달받음
  • 참고로 GUI가 없고 CUI만 있는 리눅스 서버도 있음

 

커널과 쉘

  • 커널
    • 운영체제의 핵심으로, SW와 HW간의 커뮤니케이션을 관리하는 프로그램
    • SW로부터의 요청을 HW가 처리할 수 있도록 요청을 반환하는 역할을 함
    • 프로세스, 메모리, 저장장치를 관리하는 핵심적인 기능을 수행
    • 알맹이, 핵심이라는 뜻을 가짐
    • 사용자와 OS 간에 대화를 가능하게 해주는 명령어 해석기 역할
    • 커널과 애플리케이션의 인터페이스 역할
    • 껍데기, 주변이라는 뜻을 가짐

 

시스템 호출(system call)

  • 운영체제가 커널에 접근하기 위한 인터페이스로, 커널이 자신을 보호하기 위해서 만든 인터페이스
  • 애플리케이션이 직접 하드웨어 자원에 접근하거나 수정하여 중요 데이터가 소실되는 실수를 방지하기 위해 시스템콜을 사용
  • 즉 애플리케이션이 하드웨어에 접근해야 하거나 OS가 제공하는 서비스를 이용하기 위해서는 커널 함수를 호출하는 시스템 콜을 사용해야 함
  • 시스템 콜을 제공함으로써 OS는 컴퓨터 자원을 보호하게 됨

 

드라이버

  • 커널과 하드웨어의 인터페이스를 드라이버라고 함
  • 보통 커널은 입출력의 기본적인 부분만 제작하여 마우스, 키보드같은 하드웨어는 별다른 설치 없이 작동하게 됨
  • 프린터나 스캐너같이 복잡한 하드웨어의 경우 제작사가 만든 SW를 따로 설치해서 사용해야 하며, 이때 이 SW를 디바이스 드라이버라고 함

 


시스템 콜

동작 방식

  • 시스템 콜은 인터럽트 중 하나인 소프트웨어 인터럽트(Trap)의 한 종류
  • 유저 프로그램이 운영체제의 서비스를 받기 위해 커널 함수를 호출할 때 사용
  • 유저 프로그램이 I/O 요청으로 트랩(trap)을 발동하면 올바른 I/O 요청인지 확인한 후 유저 모드가 시스템 콜을 통해 커널 모드로 변환되어 실행

 

예시

  • readFile()이라는 함수가 발동했다고 가정하자
  • 유저 모드에서 파일을 바로 읽지 않고 커널 모드로 들어가 읽음
  • 다시 유저 모드로 돌아가 그 뒤에 있는 유저 프로그램의 로직을 수행함
  • 이 과정을 통해 컴퓨터 자원에 대한 직접 접근을 차단할 수 있고 프로그램을 다른 프로그램으로부터 보호할 수 있음

 

전체적인 흐름

  • 프로세스나 스레드에서 운영체제로 어떠한 요청을 보낼 때 시스템콜이라는 인터페이스와 커널을 거쳐 운영체제에 전달됨
  • 시스템콜은 하나의 추상화 계층
  • 따라서 네트워크 통신이나 데이터베이스와 같은 낮은 단계의 영역 처리에 대한 부분을 크게 신경쓰지 않고 프로그램을 구현할 수 있음

 

modebit의 역할

  • modebit은 1또는 0 값을 가지는 플래그 변수(0은 커널 모드, 1은 유저 모드)
  • 시스템콜이 작동될 때 이를 통해 유저 모드와 커널 모드를 구분
  • 카메라, 키보드 등 I/O 디바이스는 운영체제를 통해서만 작동해야 함
  • 커널 모드를 거쳐 운영체제를 통해 작동한다고 해도 외부에 의해 악용되는 경우를 100% 막을 수는 없지만, 막기가 더 쉬움

 

  • 유저 프로그램이 카메라를 이용하려고 할 때 시스템 콜을 호출하고 modebit을 1에서 0으로 바꾸며 커널 모드로 변경
  • 그 후 카메라 자원을 이용한 로직을 수행
  • modebit을 0에서 1로 바꿔 유저 모드로 변경하고 이후 로직을 수행

 


운영체제의 주요 과정

부트스트래핑(bootstraping)

  • 운영체제를 메인 메모리에 적재하는 과정을 부팅, 또는 부트스트래핑이라고 함
  • ROM에는 부트로더(부트스트랩 로더)라는 소규모 프로그램이 있으며, 하드디스크와 같은 보조기억장치에 저장된 OS를 메인 메모리에 적재하는 기능을 함
    • ROM: 비휘발성으로 메모리에서 극히 일부를 차지(수 KB)
    • RAM: 휘발성으로 메모리의 대부분을 차지하며 실제 프로그램이 할당되는 곳(수 MB~GB)

 

  • 컴퓨터의 전원이 켜지면 부트 로더의 과정, 즉 “부팅”이 일어남
    1. 프로세서(CPU)에서 ROM에 있는 내용을 읽음
      ROM 안에는 POST(Power-On Self-Test)와 부트로더가 저장되어 있음
    2. POST는 전원이 켜지면 가장 처음에 실행되는 프로그램으로 현재 컴퓨터의 상태를 검사함
    3. POST 작업이 끝나면 부트 로더가 실행됨
    4. 부트 로더는 하드디스크에 저장되어 있는 OS를 찾아 메인 메모리(RAM)에 가져옴
  • OS는 컴퓨터의 전원이 꺼지는 시점에 종료됨

 

인터럽트(Interrupt)

  • 현재 실행 중인 프로그램을 중단하고 다른 프로그램의 실행을 요구하기 위해 발생하는 명령
  • 다중 프로그래밍을 위해 없어서는 안될 기능
  • 키보드, 마우스 등 IO 디바이스로 인한 인터럽트, 0으로 숫자를 나누는 산술 연산에서의 인터럽트, 프로세스 오류 등으로 발생
  • 인터럽트 간에는 우선순위가 있고 우선순위에 따라 실행

 

https://velog.io/@adam2/%EC%9D%B8%ED%84%B0%EB%9F%BD%ED%8A%B8

  • 하드웨어의 인터럽트
    • Timer, 키보드/마우스 입력, DMA 등
    • 시스템 버스를 통해 물리적 신호가 CPU로 전달됨
  • 소프트웨어의 인터럽트
    • 시스템 콜로써 구현될 수 있음
    • 인터럽트 요청 신호에 따라 인터럽트 함수(인터럽트 서비스 루틴)가 실행됨
    • 인터럽트 핸들러 함수가 모여있는 인터럽트 벡터를 통해 인터럽트 핸들러 함수가 실행됨
    • 인터럽트 발생 시 이전 실행 정보를 스택 영역에 저장하고 있어야 이후 다시 프로그램을 이어서 실행할 수 있음

 


References

스타트업에 부는 챗GPT 열풍…활용 서비스 속속 도입

  • 세금 신고·환급 도움 서비스 '삼쩜삼’
    • 챗GPT 기반 세금 챗봇 ‘인공지능(AI) 점삼이’ 베타 버전을 공개
    • 처음으로 세무 플랫폼에서 챗GPT를 도입
    • 이용자들의 연말 정산과 관련된 질문에 즉시 맞춤형 답변을 제공하는 챗봇 서비스
    • 정리된 답변엔 내용의 출처 콘텐츠도 제공한다는 게 특징
  • 온라인 코딩 강의 ‘스파르타코딩클럽’
    • 수강생들이 빠르게 오류를 잡고, 다음 단계로 이어 나갈 수 있는 최적의 환경을 만들기 위함
    • 별도 프로그램을 설치하지 않아도 되며, 실시간으로 코드를 분석해 오류 원인을 알려줌
  • 여행 플랫폼 스타트업 ‘마이리얼트립’
    • 국내 여행 업계 최초로 챗GPT를 활용한 ‘AI 여행플래너’를 출시’
    • 여행 관련 다양한 주제에 대해 실시간으로 대화가 가능할 뿐 아니라 여행 일정을 계획하는 것까지 도와줌
    • AI 여행플래너로 계획한 일정은 마이리얼트립 상품페이지로 곧바로 연동

 

가상자산 시장에 재생 금융 ‘리파이’가 뜬다

  • 디파이와 리파이
    • 디파이(DeFi, 탈중앙화 금융): 은행이 제공하지 못한 투명하고 포용적인 금융 시스템을 제공하기 위해 등장
    • 리파이(ReFi, 재생 금융): 암호화폐를 활용해 더 포용적이고 지속 가능한 방식으로 경제를 재건하기 위해 등장
  • 탄소배출권 거래
    • 최근 리파이는 환경 분야에 특화해 탄소배출권 거래 촉진 등에 초점을 맞추고 있음
    • 웹 3.0 탄소 플랫폼 “노리”에서는 수요자와 공급자 사이에 토근(NRT)이 거래됨
    • 공급자는 탄소저감이나 제거를 통해 토큰을 발행, 수요자는 크레딧 구매를 통해 순배출량을 줄임
  • ESG 경영
    • 한 블록체인 기업은 동식물 다양성 시장, 깨끗한 물을 마실 권리와 같은 분야의 인프라 구축에도 투자 중
    • 리파이는 앞으로 지속 가능한 저탄소 경제로 나아가는 윤활유 역할이 될 것
    • 대표는 “블록체인을 활용해 인류의 번영, 교육, 평등을 이루고자 하는게 리파이의 취지” 라며
      “결국에는 재생 시장의 대차대조표(재정 상태 정보)를 블록체인으로 가져오는게 우리의 목표”

 

2023년, 인공지능의 발전 방향은 클라우드가 결정한다

  • 현재 트렌드
    • 하이브리드 클라우드와 멀티클라우드 환경이 확대되는 동시에 인공지능의 발전에 속도를 붙이고 있음
    • 중소기업들도 가장 알맞은 기술을 가장 효율적으로 활용하기 위해 클라우드와 인공지능을 검토하기 시작
  • 클라우드로 인해 인공지능의 성능 향상이 촉진될 것
    • 클라우드가 보급됨에 따라 컴퓨터 성능이 안좋아도 안공지능을 경험할 수 있게 됨
    • 클라우드 컴퓨팅과 인공지능의 발전과 활용을 가장 직관적으로 보여주는 건 게임과 엔터테인먼트 산업
  • 클라우드로 인해 인공지능 관련 규정이 늘어날 것
    • 보안 사고들이 전 세계적으로 꾸준히 증가하고 있음
    • 클라우드를 통해 제공되는 인공지능도 덩달아 많아진 규정 아래 놓일 것
    • 적어도 데이터가 클라우드 상의 인공지능 알고리즘에서 어떻게 처리되는지는 투명하게 밝혀야 할 것으로 보임
  • 클라우드 산업도 인공지능을 적극 도입할 것
    • 경제적 상황 때문에 클라우드 비용을 마냥 늘릴 수만은 없는 게 대부분 기업들의 현실
    • 상황에 따라 유연하게 비용을 늘렸다 줄였다 할 수 있는 유형의 클라우드 서비스가 인기를 끌 것
    • 클라우드를 사용하는 것 자체가 비용을 절감하는 방법으로 인식되고 있기 때문에, 더 많은 기업이 클라우드를 도입하되, 다양한 사항들을 보다 꼼꼼하게 검토할 것

⇒ 이제 클라우드와 인공지능은 하나의 서비스로 봐야 한다

 

'모바일'은 없었다…인공지능이 삼킨 MWC

  • 동영상 서비스의 인기
    • 지난해 전 세계 인터넷 트래픽의 절반이 구글과 넷플릭스 등 단 6개의 빅테크에서 나왔음
    • 전체 트래픽의 60%를 동영상 서비스가 차지했음
  • KT 초거대 AI “믿음”
    • KT는 국내 최초로 초거대 AI 상용화를 앞두고 있음
    • AI 반도체와 소프트웨어 기업까지 모두 참여한 인공지능 “풀스택” 라인업을 선보임
    • 상반기 내 “믿음”을 활용한 시니어 케어 서비스를 출시할 것
    • AI B2B 사업에 집중하여 연내 금융고객 상담서비스 등을 선보일 계획
  • 모바일은 없었다
    • 세계 최대 통신기술 전시회지만 이제 더이상 그 누구도 '모바일'을 얘기하지 않음
    • 다가오는 인공지능 시대의 인프라를 선점하기 위한 새로운 경쟁을 시작

 

KB국민카드, 악성앱 탐지 솔루션 … 5천명 정보 지켰다

  • 'KB페이'는 악성 앱을 자동으로 탐지해 대면/비대면 거래를 자동으로 차단, 고객에게 즉시 해당 사실을 안내
  • 해당 서비스를 통해 지난해 추가 피해를 예방한 고객은 5300여 명
  • 지속적으로 FDS를 고도화하고 인공지능을 접목한 모니터링 시스템을 구축할 계획
  • 특히 가상 PC 환경 구축, 인터넷 망 분리, 내부통제 모니터링 및 외부 이상징후 탐지 시스템 구축 등 지속적으로 정보보호 체계를 강화하고 있음
  • 정기적으로 이메일 모의훈련, 침해사고 대응 훈련, 임직원 맞춤형 정보보호 교육 등을 통해 정보보호 역량을 강화하고 있음

 

챗GPT, 사이버 공격에 적극 활용 ‘대책 시급’

  • IT·사이버 보안 전문가 1500명을 대상 설문
    • 51%는 1년 안에 사이버 공격에 동원돼 관련 피해가 발생할 것이라 응답
    • 78%는 2년 내에 챗GPT를 통한 사이버 공격이 본격적으로 시작될 것이라 응답
  • 사이버 공격의 유형
    • 정교한 피싱 이메일을 만드는 일
    • 해커들의 코딩 능력 향상
    • 챗GPT가 거짓 정보를 퍼트릴 수 있음
    • 새로운 유형의 악성 코드를 만들어 낼 수 있음

⇒ 기업 차원이든 국가 정책 차원이든, 고도화된 AI를 악용한 사이버 공격에 대한 대비책이 중요해질 전망

접근

알고리즘 분류에는 문자열만 있지만, 나는 투포인터로 접근했다. 다른 사람들의 풀이를 보니, 한 칸씩 이동하며 연속된 IOI들의 개수를 세고 그 값이 N과 같아지면 answer를 카운팅하는 방식이다. 내가 실버 문제를 너무 복잡하게 생각한 것 같다. 아무튼 내 풀이 방법은 다음과 같다. 

 

left와 right 포인터를 두고, 두 포인터의 거리가 2*n가 될 때까지 움직이며 조건을 확인한다. 기본적으로 left는 고정하고 right를 움직이는데, 한 칸 전의 문자와 현재 문자가 같다면 조건을 만족하지 않기 때문에 left 위치를 right로 확 옮겨준다. 

 

만약 길이가 2*n을 만족하는 경우에는 left, 즉 시작 위치가 I로 시작하는 게 맞는지 확인한다. 아니라면 한 칸을 이동시켜 준다. 만약 맞다면 Pn의 조건을 만족하는 경우가 되는 것이다. answer의 값을 1 늘려주고, 이미 left~right 사이의 문자들은 IOI...IOI 를 만족하는 상태이기 때문에 바로 다음 I의 위치에서 시작할 수 있도록 left에 2를 더해줬다. 

 

반례 모음은 이쪽 → https://www.acmicpc.net/board/view/88287

 

코드

package week8;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class BJ5525 {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        int m = Integer.parseInt(br.readLine());
        String input = br.readLine();

        int left = 0, right = 1, answer = 0;
        char pre, cur = input.charAt(left);

        while(right < m){
            pre = cur;
            cur = input.charAt(right);

            // 동일한 문자가 연속됨
            if(pre == cur) left = right;

            // n 조건을 만족하는 길이가 됨
            if(right - left == 2*n){
                if(input.charAt(left) == 'O'){
                    left++;
                }
                else {
                    answer++;
                    left += 2;
                }
            }

            right++;
        }

        System.out.println(answer);
    }
}

접근

이 문제는 연산을 전부 기억할 필요 없이 단순히 뺄셈의 유무만 확인하면 된다. 만약 '-'가 한번이라도 나온다면 그 뒤는 모두 음수로 처리가 가능하기 때문이다. 그런데 내 코드는 자바의 문자열 처리 기능을 제대로 활용하지 못한 풀이인 것 같다. 자바 문법으로 c++ 코드를 짠듯한 느낌..

 

그래서 다른 분들의 코드를 확인해보니 https://www.acmicpc.net/source/56477572 ← 이 풀이가 훨씬 깔끔하다고 느꼈다. 참고해보면 좋을 것 같다. 

 

코드

package week8;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class BJ1451 {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String input = br.readLine();

        int idx = 0, inputLen = input.length();
        int answer = 0, sum = 0;
        boolean existMinus = false;

        while(idx < inputLen){
            char c = input.charAt(idx++);

            // +나 -인 경우
            if(!Character.isDigit(c)){
                // -가 한번이라도 나왔으면 뒤는 모두 음수 처리 가능
                if(existMinus) answer -= sum;
                else answer += sum;

                if(c == '-') existMinus = true;

                sum = 0;
            }
            // 숫자인 경우
            else {
                sum = sum * 10 + (c - '0');
            }
        }

        if(existMinus) answer -= sum;
        else answer += sum;

        System.out.println(answer);
    }
}

접근

여러 알고리즘이 합쳐져 있어서 구현 난이도가 꽤 빡센 문제였다. (170줄...) 크게 나눠보면

1. bfs로 서로 땅이 연결된 섬들을 구함

2. 각 섬들 간에 연결 가능한 다리를 찾음

3. 크루스칼로 다리 길이의 최소 합을 구함

의 과정으로 진행된다. 이제 하나씩 자세히 들여다보자.

 

먼저 1번에서는, 지도가 최대 10x10으로 작으므로 한 칸씩 이동하며 1을 발견한다면 연결된 땅들을 하나의 섬으로 표시해준다. 0은 바다, 1은 땅이므로 섬의 번호는 2번부터 시작하도록 했다. 나중에 크루스칼 알고리즘을 돌리며 모든 섬이 연결되었는지를 확인하기 위해서는 섬의 개수가 필요하기 때문에, 섬의 번호를 표시함과 동시에 총 섬의 개수를 리턴해준다. 이 때, 섬의 번호는 2부터 시작했기 때문에 2를 뺀 값을 리턴했다.

 

2번에서도 마찬가지로, 지도가 작기 때문에 한 칸씩 이동하며 섬이 나온다면 근처 섬을 찾도록 한다. 다만 상하좌우 4방향을 모두 볼 필요는 없다. A섬에서 아래로 내려가 B섬과 만나는 다리를 찾았는데, B섬에서 위로 올라가 A섬과 만나는 다리를 또 찾게된다면 동일한 다리를 두번 탐색하게 되기 때문이다. 

0이면 바다이므로 다리의 길이를 +1해주고, 시작 지점의 번호와 같다면 내륙을 통한 것이기 때문에 넘어간다. 아예 다른 번호가 나온다면 근처 섬을 찾은 것이다. 그렇게 Edge를 만들어 우선순위 큐에 넣어준다. 다리를 찾았더라도 그 길이가 1이라면 넘어가야 한다.

 

이제 복잡한 전처리는 모두 끝났으니, 3번에서는 익숙한 크루스칼 알고리즘을 돌려주면 된다. 그런데 모든 섬이 연결되지 않는 경우가 존재할 수 있으므로, 연결된 다리의 수와 섬의 수를 비교해야 한다. bridgeCnt = islandCnt -1이라면 모든 섬이 연결된 것이고, 아니라면 -1을 출력해야 한다. 

 

코드

package week7;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class BJ17472 {
    static class Pos{
        int x, y;

        public Pos(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }

    static class Edge implements Comparable<Edge>{
        int from, to, dist;

        public Edge(int from, int to, int dist) {
            this.from = from;
            this.to = to;
            this.dist = dist;
        }

        @Override
        public int compareTo(Edge o) {
            return this.dist - o.dist;
        }
    }

    static int[] dx = {-1, 0, 1, 0};
    static int[] dy = {0, 1, 0, -1};

    static int[][] islandMap;
    static int[] root;
    static int n, m;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = null;

        // 변수 초기화
        st = new StringTokenizer(br.readLine());
        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());

        islandMap = new int[n][m];

        // 지도 정보 저장
        for (int i = 0; i < n; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j < m; j++)
                islandMap[i][j] = Integer.parseInt(st.nextToken());
        }
        
        // 섬 찾기
        int islandCnt = findIsland();

        // 섬들 간의 거리 계산
        PriorityQueue<Edge> pq = findBridge();

        // 크루스칼 알고리즘
        System.out.println(kruskal(pq, islandCnt));
    }

    private static int kruskal(PriorityQueue<Edge> pq, int islandCnt) {
        int bridgeCnt = 0;
        int bridgeLen = 0;

        root = new int[islandCnt+2];
        for (int i = 0; i < islandCnt+2; i++)
            root[i] = i;

        while(!pq.isEmpty()){
            if(bridgeCnt == islandCnt - 1) break;

            Edge e = pq.poll();

            int r1 = findRoot(e.from);
            int r2 = findRoot(e.to);

            if(r1 == r2) continue;

            union(r1, r2);
            bridgeCnt++;
            bridgeLen += e.dist;
        }

        return bridgeCnt == islandCnt-1 ? bridgeLen : -1;
    }

    private static PriorityQueue<Edge> findBridge() {
        PriorityQueue<Edge> pq = new PriorityQueue<>();

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if(islandMap[i][j] == 0) continue;

                for (int d = 0; d < 2; d++) { // 다리가 중복될 수 있으므로 4방향을 다 볼 필요가 없음
                    int x = i, y = j;
                    int dist = 0;

                    // d 방향으로 쭉 이동하다가 다른 섬을 만나면 다리 연결
                    while(true){
                        x += dx[d];
                        y += dy[d];
                        // 범위를 벗어나거나 같은 섬이라면 그만 본다
                        if(!inBound(x, y) || islandMap[x][y] == islandMap[i][j]) break;

                        if(islandMap[x][y] == 0) dist++;
                        else if(islandMap[x][y] != islandMap[i][j]){
                            // 다른 섬을 찾으면 길이가 2 이상인 다리만 연결
                            if(dist > 1) pq.add(new Edge(islandMap[i][j], islandMap[x][y], dist));
                            break;
                        }
                    }
                }
            }
        }

        return pq;
    }

    private static int findIsland() {
        int islandNum = 2;

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if(islandMap[i][j] != 1) continue;

                checkIsland(i, j, islandNum++);
            }
        }
        return islandNum - 2;
    }

    private static void checkIsland(int x, int y, int islandNum) {
        Queue<Pos> q = new LinkedList<>();
        q.add(new Pos(x, y));

        while(!q.isEmpty()){
            Pos p = q.poll();
            if(islandMap[p.x][p.y] == islandNum) continue;
            islandMap[p.x][p.y] = islandNum;

            for (int d = 0; d < 4; d++) {
                int nx = p.x + dx[d];
                int ny = p.y + dy[d];

                if(!inBound(nx, ny) || islandMap[nx][ny] == 0) continue;
                q.add(new Pos(nx, ny));
            }
        }
    }

    static boolean inBound(int x, int y){
        return !(x < 0 || y < 0 || x >= n || y >= m);
    }

    static int findRoot(int n){
        if(root[n] == n) return n;
        return root[n] = findRoot(root[n]);
    }

    static void union(int r1, int r2){
        root[r1] = r2;
    }
}

'Algorithm' 카테고리의 다른 글

[백준] 5525: IOIOI JAVA  (0) 2023.03.04
[백준] 1541: 잃어버린 괄호 JAVA  (0) 2023.03.03
[백준] 10423: 전기가 부족해 JAVA  (0) 2023.02.27
[백준] 4386: 별자리 만들기 JAVA  (0) 2023.02.26
[백준] 6497: 전력난 JAVA  (0) 2023.02.26

접근

기본적으로는 크루스칼 알고리즘으로 푸는데, 이제 같은 트리라는 것만 확안하는 것이 아니라 한 트리 안에 발전소가 1개만 있어야 한다는 조건이 추가된다. 그래서 union을 할 때에는 루트값을 무조건 발전소가 있는 번호가 저장되도록 설정했고, find로 찾은 루트가 모두 발전소라면 넘어가도록 처리해주었다. 

 

그리고 모든 도시가 하나의 발전소에 연결되기 위해서 필요한 케이블의 개수는 n-k개이다. 설치된 케이블의 개수를 확인하여 n-k가 되면 while문을 종료하도록 했다. 

 

코드

package week7;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

public class BJ10423 {
    static class Edge implements Comparable<Edge> {
        int from, to, cost;

        public Edge(int from, int to, int cost) {
            this.from = from;
            this.to = to;
            this.cost = cost;
        }

        @Override
        public int compareTo(Edge o) {
            return this.cost - o.cost;
        }
    }

    static int[] root;
    static ArrayList<Integer> powerPlant;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = null;

        st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());
        int m = Integer.parseInt(st.nextToken());
        int k = Integer.parseInt(st.nextToken());

        // 발전소가 설치된 도시의 번호
        powerPlant = new ArrayList<>();
        PriorityQueue<Edge> pq = new PriorityQueue<>();

        st = new StringTokenizer(br.readLine());
        while(st.hasMoreTokens()){
            int val = Integer.parseInt(st.nextToken());
            powerPlant.add(val);
        }

        for (int i = 0; i < m; i++) {
            st = new StringTokenizer(br.readLine());

            int u = Integer.parseInt(st.nextToken());
            int v = Integer.parseInt(st.nextToken());
            int w = Integer.parseInt(st.nextToken());
            pq.add(new Edge(u, v, w));
        }

        // 크루스칼 알고리즘
        root = new int[n+1];
        for (int i = 0; i <= n; i++)
            root[i] = i;

        int answer = 0;
        int cnt = 0;

        while(!pq.isEmpty()){
            if(cnt == n-k) break;

            Edge e = pq.poll();

            int r1 = find(e.from);
            int r2 = find(e.to);

            // 같은 트리 안에 있거나, 둘 다 발전소에 연결되어 있을 경우 넘어감
            if(r1 == r2 || (powerPlant.contains(r1) && powerPlant.contains(r2))) continue;

            union(r1, r2);
            answer += e.cost;
            cnt++;
        }

        System.out.println(answer);
    }

    static int find(int n){
        if(root[n] == n) return n;
        return root[n] = find(root[n]);
    }

    static void union(int r1, int r2){
        if(powerPlant.contains(r1)){
            int tmp = r1;
            r1 = r2;
            r2 = tmp;
        }

        root[r1] = r2;
    }
}

접근

다른 크루스칼 문제들과 핵심 알고리즘은 동일하고, 그 이외의 전처리할 것들이 추가되어 단순히 구현 난이도만 조금 높아진 문제같다. 

 

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

public class BJ4386 {
    static class Pos{
        double x, y;

        public Pos(double x, double y) {
            this.x = x;
            this.y = y;
        }
    }

    static class Edge implements Comparable<Edge> {
        int from, to;
        double dist;

        public Edge(int from, int to, double dist) {
            this.from = from;
            this.to = to;
            this.dist = dist;
        }

        @Override
        public int compareTo(Edge o) {
            return (int)(this.dist - o.dist);
        }
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = null;

        int n = Integer.parseInt(br.readLine());
        ArrayList<Pos> stars = new ArrayList<>();

        // 입력값 저장
        for (int i = 0; i < n; i++) {
            st = new StringTokenizer(br.readLine());
            double x = Double.parseDouble(st.nextToken());
            double y = Double.parseDouble(st.nextToken());
            stars.add(new Pos(x, y));
        }

        // 별들 간의 거리 계산
        PriorityQueue<Edge> pq = addEdge(stars);

        double answer = kruskal(n, pq);
        System.out.printf("%.2f", answer);
    }

    static double kruskal(int n, PriorityQueue<Edge> pq){
        double res = 0;
        int[] root = new int[n];

        for (int i = 0; i < n; i++)
            root[i] = i;

        int cnt = 0;
        while(!pq.isEmpty()){
            if(cnt == n-1) break;
            Edge e = pq.poll();

            int r1 = find(root, e.from);
            int r2 = find(root, e.to);

            if(r1 == r2) continue;

            union(r1, r2, root);
            res += e.dist;
            cnt++;
        }

        return res;
    }

    static int find(int[] root, int n){
        if(root[n] == n) return n;
        return root[n] = find(root, root[n]);
    }

    static void union(int r1, int r2, int[] root){
        root[r1] = r2;
    }

    private static PriorityQueue<Edge> addEdge(ArrayList<Pos> stars) {
        PriorityQueue<Edge> pq = new PriorityQueue<>();
        int starCnt = stars.size();

        for (int i = 0; i < starCnt; i++) {
            for (int j = i+1; j < starCnt; j++) {
                double dist = calcDist(stars.get(i), stars.get(j));
                pq.add(new Edge(i, j, dist));
            }
        }

        return pq;
    }

    private static double calcDist(Pos p1, Pos p2) {
        return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
    }
}

'Algorithm' 카테고리의 다른 글

[백준] 17472: 다리 만들기 2 JAVA  (0) 2023.02.27
[백준] 10423: 전기가 부족해 JAVA  (0) 2023.02.27
[백준] 6497: 전력난 JAVA  (0) 2023.02.26
[백준] 1647: 도시 분할 계획 JAVA  (0) 2023.02.26
[백준] 1068: 트리 JAVA  (0) 2023.02.15

접근

테스트 케이스의 개수가 정해져있지 않고 0 0이 들어오면 종료한다는 조건 이외에는, 다른 크루스칼 알고리즘들과 동일하다. 

 

 

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

public class BJ6497 {
    static class Edge implements Comparable<Edge> {
        int from, to, cost;

        public Edge(int from, int to, int cost){
            this.from = from;
            this.to = to;
            this.cost = cost;
        }

        @Override
        public int compareTo(Edge o) {
            return this.cost - o.cost;
        }
    }

    static int[] root;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = null;
        StringBuilder sb = new StringBuilder();

        while(true){
            st = new StringTokenizer(br.readLine());
            int m = Integer.parseInt(st.nextToken());
            int n = Integer.parseInt(st.nextToken());

            // 테스트 케이스 종료
            if(m == 0 && n == 0)
                break;

            PriorityQueue<Edge> pq = new PriorityQueue<Edge>();
            root = new int[m+1];
            for (int i = 0; i < m+1; i++)
                root[i] = i;

            for (int i = 0; i < n; i++){
                st = new StringTokenizer(br.readLine());
                int x = Integer.parseInt(st.nextToken());
                int y = Integer.parseInt(st.nextToken());
                int z = Integer.parseInt(st.nextToken());

                pq.add(new Edge(x, y, z));
            }
            
            int minCost = kruskal(m, pq);
            sb.append(minCost).append("\n");
        }

        System.out.println(sb);
    }

    private static int kruskal(int m, PriorityQueue<Edge> pq) {
        int res = 0;
        int cnt = 0;

        while(!pq.isEmpty()){
            if(cnt == m-1) break;
            Edge e = pq.poll();

            int r1 = find(e.from);
            int r2 = find(e.to);

            if(r1 == r2){
                res += e.cost;
                continue;
            }

            union(r1, r2);
            cnt++;
        }

        while(!pq.isEmpty()){
            Edge e = pq.poll();
            res += e.cost;
        }

        return res;
    }

    private static void union(int r1, int r2) {
        root[r1] = r2;
    }

    static int find(int n){
        if(root[n] == n) return n;
        return root[n] = find(root[n]);
    }
}

'Algorithm' 카테고리의 다른 글

[백준] 10423: 전기가 부족해 JAVA  (0) 2023.02.27
[백준] 4386: 별자리 만들기 JAVA  (0) 2023.02.26
[백준] 1647: 도시 분할 계획 JAVA  (0) 2023.02.26
[백준] 1068: 트리 JAVA  (0) 2023.02.15
[백준] 1991: 트리 순회 JAVA  (0) 2023.02.15

+ Recent posts