google play api는 google play service의 하나의 파트 임.

google fit api는 안드로이드 4.1 이상부터 호환.

Google Fit API 장점

  • 거의 실시간의 히스토리 데이터를 적은 에너지의 블루투스 디바이스로부터 가져옴
  • 활동들을 기록 할 수 있음
  • 데이터를 세션과 연관시킬 수 있음
  • 피트니스 목표를 설정 할 수 있음

sensor data

  • 유저의 하루 활동 관련한 정보들을 앱에서 제공한다면 (예를 들어 하루동안의 걸음수), sensor data는 사용자의 행동을 거의 실시간으로 보여주는데 사용이 가능함.

record data

  • 앱이 꺼져있는 상황에서도 계속해서 데이터를 적재 할 수 있는 subscribe 메서드를 제공함

historical data

  • 만약 유저가 과거의 활동들로부터 피트니스 데이터를 보여주기를 원한다면 history api를 사용하면 됨
  • 데이터 subscribe가 선행 되어야 historical data를 읽을 수 있음 ( 도큐먼트가 친절하지 않아서 테스트 후에 알게 됨), 데이터가 없으면 빈 리스트로 응답이 옴.

session data

  • 개발자가 일정한 시간의 세션을 설정하고 그 설정안에 들어가는 범위의 데이터를 가져올 수 있음

내가 하고자 하는 것

  1. subscribe를 통해 앱 사용자의 활동내역(현재는 걸음 수, 이후에 추가할 예정)들을 클라우드 형태의 google fit store에 앱을 종료했을 때도 마찬가지로 계속해서 저장해야 함.
  2. 데이터가 있는 날부터 일(00시~24시) 기준으로 걸음 수 데이터를 보여줄 수 있어야 함
  3. 배터리 최적화, 잠자기 모드 등 예기치 못한 상황에서도 정상적으로 데이터를 가져와야 함
  4. 데이터는 자정 기준으로 새로 시작함

사전 준비

  1. google fit api 는 Google API Console 에 프로젝트를 등록 및 client ID 를 발급 받은 후 사용해야함.
  2. client ID 발급 후 사용할 library에서 google fit api 를 사용 설정 해야 함.
  3. 테스트를 위해서 OAuth 동의 화면 탭의 테스트 사용자에 테스트를 수행 할 사용자 정보를 등록해야 함.
  4. 모듈 범위의 gradle 에
plugin {
    id("com.android.application")
}

...

dependencies {
        implementation("com.google.android.gms:play-services-fitness:21.1.0")
        implementation("com.google.android.gms:play-services-auth:20.2.0")
}

다음 과 같이 dependency를 추가하여 필요한 라이브러리를 다운로드 함

  1. https://developers.google.com/fit/android/get-started 를 참고하면 더 자세히 setup에 관해 나와 있음

구현 방법

  1. fitness option 객체를 만들어 내가 사용하고자 하는 data들을 추가함
funcreateFitnessStepOptions() : FitnessOptions{
	val fitnessOptions = FitnessOptions.builder()
        .addDataType(DataType.TYPE_STEP_COUNT_DELTA, FitnessOptions.ACCESS_READ)
        .addDataType(DataType.AGGREGATE_STEP_COUNT_DELTA, FitnessOptions.ACCESS_READ)
        .addDataType(DataType.TYPE_DISTANCE_DELTA, FitnessOptions.ACCESS_READ)
        .addDataType(DataType.AGGREGATE_DISTANCE_DELTA, FitnessOptions.ACCESS_READ)
        .build()
	return fitnessOptions
}

걸음수와 지금까지 걸은 거리에 대한 데이터를 얻기 위해 위처럼 4개의 fitness data type을 추가함.

  1. google account를 가져옴
fun getAccount(fitnessOptions: FitnessOptions) : GoogleSignInAccount{
	val account = GoogleSignIn.getAccountForExtension(GlobalApplication.getApplicationContext(),fitnessOptions)
	return account
}

getAccountForExtension 메서드를 이용하여 추가한 데이터에 대한 인증을 사용하는 google signin account를 얻음.

  1. google fit api를 사용하기 위한 권한 요청
fun checkHasGrantedDataAccess(account: GoogleSignInAccount, fitnessOptions: FitnessOptions, callback: () -> Unit){
    Log.i(TAG,"checkHasGrantedDataAccess${account.id}, fitnessOptions${fitnessOptions}")
if(!GoogleSignIn.hasPermissions(account, fitnessOptions)){
	//해당 google id가 permission이 허용되었는지 체크함.
	Log.d(TAG,"!GoogleSignIn.hasPermissions")
	GoogleSignIn.requestPermissions(mActivity,GOOGLE_FIT_PERMISSIONS_REQUEST_CODE, account, fitnessOptions)
  }else{
	  callback()
  }
}

account 가 접근하고자 하는 google fit api data에 대한 권한이 있는지 체크하고, 권한이 있다면 callback함수를 실행함. 없다면 권한을 요청함.

  1. 걸음 수 / 걸은 거리 데이터 구독 시작
//피트니스 데이터(걸음 수)구독셋팅
fun subscribeStepCountData(successCallback: () -> Unit?, failureCallback: () -> Unit?){
    Fitness.getRecordingClient(GlobalApplication.getApplicationContext(),mGoogleSignInAccount)
  .subscribe(DataType.TYPE_STEP_COUNT_DELTA)// no scopes are specified.
	.addOnSuccessListener{
	Log.i(TAG,"successfully subscribe step count data!!")
	  successCallback()
	}.addOnFailureListener{
	Log.w(TAG,"there was a problem subscribing step count data${it}")
	  failureCallback()
	}
}

  1. 걸음 수를 원하는 날짜부터 가져옴 (수정 중)
fun getStepCountForDays(days : Int){

	val cal = Calendar.getInstance()
	val now = Date()
	    cal.time= now
	val endTime = cal.timeInMillis
	
	cal.set(Calendar.HOUR_OF_DAY, -days)
	    cal.set(Calendar.MINUTE, 0)
	    cal.set(Calendar.SECOND, 0)
	    cal.set(Calendar.MILLISECOND, 0)
	
	val startTime = cal.timeInMillis
	
	val readRequest = DataReadRequest.Builder()
	        .aggregate(DataType.TYPE_STEP_COUNT_DELTA, DataType.AGGREGATE_STEP_COUNT_DELTA)
	        .bucketByTime(1, TimeUnit.DAYS)
	        .setTimeRange(startTime, endTime, TimeUnit.SECONDS)
	        .build()
	
	    Fitness.getHistoryClient(mActivity,mGoogleSignInAccount)
	    .readData(readRequest)
	    .addOnSuccessListener{
	response->
	for(dataSetinresponse.buckets.flatMap{ it.dataSets}) {
	  Log.i(TAG,"dataSet ==${dataSet}")
		//원하는 로직 추가 }
	}.addOnFailureListener{
		//실패 처리로직 추가
	}
}

원하는 날부터 현재까지 일 단위의 걸음 수 데이터를 가져오는 로직

결과

테스트를 해보려고 구글 피트니스 앱과 데이터를 비교한 결과 데이터가 잘 조회 되는 것을 확인하였음. 이 과정에서 한번 더 고민하고 액션을 취했던 부분은 혹시나 하는 마음에 매일 자정이 되기 찰나의 순간에 하루동안의 데이터를 조회해 오는 메서드 readDailyTotal 를 호출하여 데이터를 저장하는 방법과, DataReadRequest 객체에 startTime과 endTime을 설정해 그 기간동안의 data를 가져와 사용하는 방법 간 데이터 결과에 차이가 있는가에 대한 테스트를 했고 데이터간 차이가 없는 것을 확인하고 후자의 방식으로 채택했음. 그러나 startTime과 endTime을 잘못 설정하면 자정이 기준이 아니고 현재 시간을 기준으로 하루가 책정되기 때문에 startTime과 endTime을 설정을 자신이 필요한 데이터에 따라 정확히 할 필요가 있음.

 

환경 변수 란 ?

어떠한 프로세스가 실행될 때 영향을 미치는 동적인 값

OS 단에 선언되어있는 배포 환경에 따라 값을 동적으로 변경하기 위한 변수

내가 하고 싶었던 건?

환경변수인 NODE_ENV 값을 local, development, production으로 각각 나누어 의도한 바에 따라 .env파일을 분기하여 사용하고 싶었다

시행착오

  1. process.env 환경변수를 vue.파일의 script 태그 안에서 콘솔로 로그를 찍으니 계속 undefined를 얻었다.. → client side에서는 process.env를 사용 할 수 없다는 사실을 모르고 있었다. 알고보니 process.env는 server side에서만 사용이 가능했고, clientside에서도 활용이 가능하게 하기 위해 nuxt에서는 nuxt.config.ts 파일 안에 config 설정을 따로 두고 있었다.
  2. cmd 에서 환경변수를 set하는 방법을 몰라서 한참을 헤맸다. package.json 의 scripts 부분 안에 있는 명령어들을 지정할 때이런 방식으로 환경변수를 셋팅 하니 잘 동작했다.
  3. SET 환경변수명=값 & nuxt build
  4. SET NODE_ENV를 변경해도 저절로 .env 파일을 찾아가지 못했다. 예전 Vue CLI를 이용해서 프로젝트를 생성했을때는 뒤에 --mode local 이런 방식으로 지정하면 .env.local 파일의 변수값을 바라보게 자동으로 셋팅이 되었는데, Nuxt3는 그렇게 되지 않았고const phase = process.env.PHASE!!주의할 점!!따라서 nuxt.config.ts 안에서 process.env값을 runtimeConfig로 정의하여 컴포넌트 안에서 사용해야한다.
  5. nuxt에서는 process.env 값을 component안에서 console로 찍어 확인하면 nuxt.config.ts 안에서의 process.env 값과 다르다.
  6. require(’dotenv’).config({path:./.env${phase}})
  7. 해결방법으로 스크립트에 PHASE라는 환경변수값을 셋팅하고, 그값을 찾아와 nuxt.config.ts 파일 에서 dotenv의 파일 path값을 동적으로 바꿔주었다. ex) SET PHASE=local&&nuxi dev

코루틴

코루틴 이란 ? 비동기적으로 실행되는 코드를 간소화 하기 위해 안드로이드에서 사용할 수 있는 동시 실행 설계 패턴입니다.

  • 경량 : 코루틴은 실행중인 스레드를 차단하지 않는 정지를 지원하므로 단일 스레드에서 많은 코루틴을 실행할 수 있습니다.
  • 메모리 누수 감소 : 구조화된 동시 실행을 사용하여 범위 내에서 작업을 실행합니다.
  • 기본으로 제공되는 취소 지원 : 실행 중인 코루틴 계층 구조를 통해 취소가 전달됩니다.
  • 이외 Jetpack 지원등..

백그라운드 스레드에서 실행

기본 스레드에서 네트워크 요청을 보내면 응답을 받을 때까지 스레드가 대기하거나 차단됩니다.

스레드가 차단될 경우 앱이 정지되고 응답없음(ANR) 대화상자가 표시될 수 있습니다.

이것의 해결방식으로 네트워크 요청을 새로운 코루틴을 만들고 I/O스레드에서 네트워크 요청을 실행할 수 있습니다.

example 1)

class LoginViewModel(
    private val loginRepository: LoginRepository
): ViewModel() {

    fun login(username: String, token: String) {
        // Create a new coroutine to move the execution off the UI thread
        viewModelScope.launch(Dispatchers.IO) {
            val jsonBody = "{ username: \\"$username\\", token: \\"$token\\"}"
            loginRepository.makeLoginRequest(jsonBody)
        }
    }
}
  • viewModelScope는 사전 정의된 CoroutineScope 입니다. 모든 코루틴은 코루틴범위 내에서 실행해야 합니다. CoroutineScope는 하나 이상의 관련 코루틴을 관리합니다.
  • launch는 코루틴을 만들고 함수 본문의 실행을 해당하는 디스패처에 전달하는 함수입니다.
  • Dispatchers.IO는 이 코루틴을 I/O작업용으로 예약된 스레드에서 해야 함을 나타냅니다.

기본 안전을 위해 코루틴 사용

기본 스레드에서 UI 업데이트를 차단하지 않는 한수를 기본 안전 함수로 간주합니다. 기본스레드에서 네트워크 요청을 하면 UI가 차단되므로 이 네트워크 요청이 담긴 함수는 기본 안전 함수가 아닙니다.

코루틴 라이브러리의 withContext() 함수를 사용하여 코루틴 실행을 다른 스레드로 이동합니다.

example 2)

class LoginRepository(...) {
    ...
    suspend fun makeLoginRequest(
        jsonBody: String
    ): Result<LoginResponse> {

        // Move the execution of the coroutine to the I/O dispatcher
        return withContext(Dispatchers.IO) {
            // Blocking network request code
        }
    }
}
  • withContext(Dispatcher.IO)는 코루틴 실행을 I/O스레드로 이동하여 호출 함수를 기본 안전 함수로 만들고 필요에 따라 UI를 업데이트 하도록 설정합니다.
  • makeLoginRequest 에는 supsend 키워드가 표시됩니다. suspend키워드는 코루틴 내에서 함수가 호출되도록 강제하는 Kotlin의 방법입니다.

example 3)

class LoginViewModel(
    private val loginRepository: LoginRepository
): ViewModel() {

    fun login(username: String, token: String) {

        // Create a new coroutine on the UI thread
        viewModelScope.launch {
            val jsonBody = "{ username: \\"$username\\", token: \\"$token\\"}"

            // Make the network call and suspend execution until it finishes
            val result = loginRepository.makeLoginRequest(jsonBody)

            // Display result of the network request to the user
            when (result) {
                is Result.Success<LoginResponse> -> // Happy path
                else -> // Show error in UI
            }
        }
    }
}

다시 example 1을 수정한 버전을 살펴봅시다.

  • launch가 Dispatchers.IO 매개변수를 사용하지 않습니다. Dispatcher를 launch에 전달하지 않으면 코루틴은 기본 스레드에서 실행됩니다.
  • 앱이 기본스레드의 View 레이어에서 login 함수를 호출합니다.
  • launch가 기본 스레드에서 네트워크 요청을 보낼 때 코루틴을 만들며, 코루틴이 실행을 시작합니다.
  • 코루틴 내에서 makeLoginRequest 호출은 withContext 블록 실행이 끝날 때 까지 코루틴의 추가 실행을 정지합니다 (ViewmodelScope의 코루틴의 코드를 makeLoginRequest의 withContext 블록 실행이 끝날때까지 일시 정지하고 끝난 후 부터 다시 실행합니다.)
  • withContext블록이 완료되면 네트워크 요청 결과와 함께 기본 스레드에서 실행을 재개합니다.

 

출처 : https://developer.android.com/kotlin/coroutines?hl=ko 

 

Android의 Kotlin 코루틴  |  Android 개발자  |  Android Developers

Android의 Kotlin 코루틴 코루틴은 비동기적으로 실행되는 코드를 간소화하기 위해 Android에서 사용할 수 있는 동시 실행 설계 패턴입니다. 코루틴은 Kotlin 버전 1.3에 추가되었으며 다른 언어에서 확

developer.android.com

 

 

실행 컨텍스트

컨텍스트란? 한국말로 ‘문맥’, 코드의 실행환경.

브라우저가 스크립트를 로딩해서 실행하는 순간 전역 컨텍스트 생성됨. 이는 페이지가 종료될 때까지 유지됨.

자바스크립트는 함수 스코프를 따름. 함수를 호출할 때마다 함수 컨텍스트가 하나씩 더 생김.

  • 전역 컨텍스트 하나가 생성 된 후 , 함수 호출시마다 컨텍스트가 생성됨.
  • 컨텍스트 생성 시 컨텍스트 안에 변수객체(arguments, variable), scope chain, this가 생성됨.
  • 컨텍스트 생성 후 함수가 실행 됨. 사용되는 변수들은 변수 객체안에서 값을 찾고 없으면 스코프 체인을 타고 올라감.
  • 함수 실행이 마무리 되면 컨텍스트는 사라짐 (클로저는 사라지지 않음), 페이지가 종료되면 전역 컨텍스트가 사라짐

전역 컨텍스트

전역 컨텍스트가 생성된 후 변수객체, scope chain, this가 들어옴.

전역 컨텍스트는 arguments가(함수의 인자) 없음. variable은 해당 스코프의 변수들임.

scope chain(자신과 상위 스코프들의 변수객체)은 자기 자신인 저역 변수 객체임.

this는 따로 설정되어 있지 않으면 window임. this를 바꾸는 방법은 new를 호출하는 것. 기본적으로 일반함수의 this는 window이며, new나 bind 같은 상황에서 this가 바뀜.

함수 컨텍스트

함수를 호출하는 순간 새로운 컨텍스트인 함수 컨텍스트가 생김. 전역 컨텍스트는 그대로. this는 따로 설정해준 적 없으면 window가 됨.

호이스팅

호이스팅이란 ? 변수를 선언하고 초기화했을 때 선언 부분이 최상단으로 끌어올려지는 현상을 의미. 함수 선언식 (ex ) function sayWow(){})으로 선언하였을 때는 식 자체가 통째로 끌어올려짐.

함수 표현식으로 선언할 경우에는 호이스팅이 일어나지 않음 (ex) var sayYeah = function() {})

클로저

클로저란 ? 비공개 변수를 가질 수 있는 환경에 있는 함수.

비공개 변수는 클로저 함수 내부에 생성한 변수도 아니고, 매개변수도 아닌 변수를 의미.

ex) function parent(){

var name = ”mom”

return function () {

console.log(name)

}

}

var closure = parent()

closure() // mom

여기서 name변수나, name변수가 있는 스코프에 대해서 클로저라고 부를 수 있음.

이를 컨텍스트적으로 분석해보면

  1. 전역 컨텍스트 생성 후 parent()함수 호출 시 parent 함수의 컨텍스트가 새로 만들어 짐
  2. var closure = parent() 할때 function을 return 하고 그 function을 선언할 때 scope chain은 lexical scoping을 따라서 [’parent 변수객체’, ‘전역 변수객체’]를 포함함. 따라서 closure을 호출할 때 scope체인은 closure 변수객체, parent 변수객체, 전역 변수객체 순으로 따라 올라가게 되고, scope chain을 통해 parent의 name 변수에 접근 할 수 있음.

클로저를 잘못 사용했을 시 성능 문제와 메모리 문제가 발생할 수 있음.

Map ?

es6에 추가된 자료형으로 Object와 비슷하지만 크게 다른점으로는

1. 순서가 있음

2 iterable 함

3. key값에 string값이 아닌 다른 타입이 들어갈 수 있음

4. 크기를 쉽게 알 수 있음

 

이렇게 있습니다.

 

그럼 위와 같은 Map이 주는 장점들로 인해 항상 Object보단 Map을 사용하는것이 좋을까요?

 

그렇지는 않습니다.

 

Object는 데이터를 저장하기 위한 굉장히 간단한 구조입니다. 그렇기 때문에 key값이 string인 간단한 데이터를 저장하기 위해서는 생성이 굉장히 쉽습니다. 또한 JSON으로 데이터를 어딘가에 전송해야 할 경우에는 Object를 사용해야합니다. Map은 아직 JSON으로 변환 되어 전달되지 않기 때문입니다.

 

그러나 Map은 Hash구조로 Object보다 순환이 빠르기 때문에 데이터를 추가하거나 수정하는 것이 빈번 할 경우 Map을 쓰는것이 좋습니다.

Map의 사용

map은 생성자를 이용하여 생성합니다.

const wrongMap = new Map()

 

set(key, value) 메서드를 이용하여 데이터를 set하고, has 메서드로 입력한 key에 해당하는 데이터 보유 유무를 확인합ㄴ디ㅏ. 또한 get 메서드를 이용하여 입력한 key에 대한 value를 얻을 수도 있습니다.

contacts.set('Jessie', {phone: "213-555-1234", address: "123 N 1st Ave"})
contacts.has('Jessie') // true
contacts.get('Hilary') // undefined

 

delete메서드를 이용하여 입력한 key에 해당하는 데이터를 지울수도 있으며 

size메서드로 map의 크기도 알 수 있습니다.

contacts.delete('Raymond') // false

 

 

 

 

Set과 마찬가지로 Map은 Map이외에 WeakMap이라는 자료형도 있습니다.

이 자료형은 마찬가지로 iterable 하지 않으며, 참조카운팅을 추가 하지도 않습니다.

 

WeakMap은 WeakSet과 달리 사용성이 좋은걸로 알고있으나.. 저는 사용해본적이 없어 사용할 일이 생긴다면 사용해보고 정리하여 따로 포스팅 하겠습니다.

 

Set 이란?

es6 에서 추가된 자료형으로

1. 배열의 중복을 제거 하고 싶을 때

2. 자료전체를 순회할 필요성이 있을 경우

3. 값의 유무를 판단 할 때

 

유용하게 쓸 수 있습니다.

 

1 번의 경우 Set의 중복된 값을 허용하지 않는다는 속성을 이용한 것이고

2,3 번의 경우 Set은 순회를 더 빨리 돈다는 점에서 유용하게 쓰입니다.

 

특히 저는 코딩테스트에서 중복제거를 위해 자주 사용하곤 합니다.

 

Set의 사용

set은 아래와 같이 생성 할 수 있으며, 생성자 안에는 iterable 한 객체들이 들어갈 수 있습니다. ex) Array

var mySet = new Set();
mySet2 = new Set([1, 2, 3, 4]);

set은 array의 push 처럼 add를 이용하여 값을 추가 할 수 있고, 중복을 허용하지 않습니다.

mySet.add(1); // Set { 1 }
mySet.add(5); // Set { 1, 5 }
mySet.add(5); // Set { 1, 5 }

set은 has 메서드를 이용하여 해당 값이 set에 포함되어 있는지의 여부를 확인할 수 있습니다. 

mySet.has(1); // true
mySet.has(3); // false, 3은 set에 추가되지 않았음
mySet.has(5);              // true

set의 size 메서드는 set의 크기를 반환하며, delete메서드를 이용하여 해당 요소를 제거할 수 있습니다.

mySet.size; // 5

mySet.delete(5); // set에서 5를 제거함

array를 set의 생성자 안에 넣어 set으로 변경할 수도 있지만, set을 array로 변경할 수도 있습니다.

// set을 Array로 변환하기 위해 전개 연산자 사용함.
console.log([...mySet]); // myArray와 정확히 같은 배열을 보여줌

이는 set은 iterable하기 때문에 spread연산자를 사용할 수 있어 가능합니다.

 

 

이 외로 WeakSet이라는 자료형도 존재합니다. 이는 set과 비슷하나 iterable하지 않고, 참조카운트를 증가시키기 않는 자료형입니다. iterable하지 않기때문에 안에 속성들을 순회하거나 탐색할 수 없습니다. 

 

WeakSet은 활용도가 미미하기 때문에 따로 다루지는 않습니다.

WeakSet의 활용도가 미미하기 때문인지 혹자들은 WeakMap과 통일성을 위해 만들어졌다고도 합니다..

 

탐색 이란?

많은 양의 데이터 중에서 원하는 데이터를 찾는 과정을 말한다.

대표적인 그래프 탐색 알고리즘으로는 DFS와 BFS가 있다.

 

스택 자료구조

먼저 들어온 데이터가 나중에 나가는 형식(선입후출)의 자료구조이다.

입구와 출구가 동일한 형태로 스택을 시각화 할 수 있고, 박스를 아래에서부터 위로 차례대로 쌓고 위부터 아래로 내려놓는 방식의 박스쌓기를 생각하면 이해하기 쉽다.

DFS알고리즘에서 자주 쓰인다.

 

 자료구조

먼저 들어온 데이터가 먼저 나가는 형식(선입선출)의 자료구조이다.

양끝이 입구와 출구로 나뉘어져있다.

BFS알고리즘에서 자주 쓰인다.

 

재귀함수

자기자신을 다시 호출하는 함수를 의미한다.

재귀함수를 무한히 호출하면 컴퓨터 메모리를 초과하기때문에 종료조건을 반드시 명시해야한다.

DFS, BFS알고리즘에서 자주 쓰이는 방식이다.

 

DFS

깊이우선탐색 알고리즘으로 주로 스택을 이용하여 구현한다.

트리나 그래프에서 한 루트에서 최대한 깊숙히 들어가서 탐색하다가 인접 노드가 모두 방문되었으면 다시 돌아가 다른 루트로 탐색한다.

 

 

컴퓨터는 기본적으로 stack구조를 취하기 때문에 재귀함수로 dfs를 실행할 경우 제일 나중에 실행된 재귀함수가 가장 먼저 종료된다. 그리고 가장 처음에 호출 되었던 함수가 가장 나중에 종료되고 재귀를 최종으로 마치게된다.

BFS

넓이우선탐색 알고리즘으로 큐 자료구조를 이용하여 구현하는게 일반적이다.

그래프나 트리에서 큐에서 노드를 꺼낸 뒤에 해당 노드의 인접노드 중 방문하지 않은 노드를 모두 큐에 삽입하고 방문처리한다.

더이상 그 과정을 수행할 수 없을 때까지 반복한다.

BFS는 최단 거리를 구하는 문제에서 자주 쓰인다.

 

 

그림 출처 : https://gmlwjd9405.github.io/2018/08/14/algorithm-dfs.html

 

[알고리즘] 깊이 우선 탐색(DFS)이란 - Heee's Development Blog

Step by step goes a long way.

gmlwjd9405.github.io

DFS/BFS 무료 강의 

https://www.youtube.com/watch?v=7C9RgOcvkvo 

 

+ Recent posts