해시(Hash)

  • 임의의 크기를 가진 데이터(Key)를 고정된 크기의 데이터(Value)로 변화시켜 저장하는 자료구조

해시 함수

  • 임의의 길이의 데이터를 고정된 길이의 데이터로 매핑하는 함수
  • 해시 함수에 의해 얻어지는 값을 해시 값, 해시 코드, 해시 라고 한다.

C++ STL  unordered_map

  • std::map은 자동으로 정렬되는 컨테이너이기 때문에 삽입, 제거가 빈번할 경우에는 성능이 저하된다. 그래서 그부분을 해결하기 위해 나온 것이 unordered_map입니다.
  • map은 Binary Search Tree로 탐색 시간 복잡도는 O(log n)입니다.
  • unordered_map은 해쉬테이블로 구현한 자료구조로 탐색 시간복잡도는 O(1)입니다.
  • unordered_map은 중복된 데이터를 허용하지 않고 map에 비해 데이터가 많을 시 월등히 좋은 성능을 보입니다.
  • 하지만, key가 유사한 데이터가 많을 시 해시 충돌로 인해 성능이 떨어질 수도 있습니다.

 

 

관련 문제

 

완주하지 못한 선수

https://programmers.co.kr/learn/courses/30/lessons/42576

 

코딩테스트 연습 - 완주하지 못한 선수

수많은 마라톤 선수들이 마라톤에 참여하였습니다. 단 한 명의 선수를 제외하고는 모든 선수가 마라톤을 완주하였습니다. 마라톤에 참여한 선수들의 이름이 담긴 배열 participant와 완주한 선수

programmers.co.kr

풀이 1 [문자열 vector 정렬 후 비교]

#include <string>
#include <vector>
#include <algorithm>

using namespace std;

string solution(vector<string> participant, vector<string> completion) {
    sort(participant.begin(),participant.end());
    sort(completion.begin(),completion.end());
    
    int i=0;
    for(;i<completion.size();i++){
        if(participant[i]!=completion[i]){
            return participant[i];
        }
    }
    return participant[i];
}

 

 

 

전화번호 목록

https://programmers.co.kr/learn/courses/30/lessons/42577?language=cpp 

 

코딩테스트 연습 - 전화번호 목록

전화번호부에 적힌 전화번호 중, 한 번호가 다른 번호의 접두어인 경우가 있는지 확인하려 합니다. 전화번호가 다음과 같을 경우, 구조대 전화번호는 영석이의 전화번호의 접두사입니다. 구조

programmers.co.kr

풀이1 [문자열 vector 정렬 substr 비교]

#include <string>
#include <vector>
#include <algorithm>

using namespace std;

bool solution(vector<string> phone_book) {
    sort(phone_book.begin(),phone_book.end());
    for(int i=1;i<phone_book.size();i++){
        if(phone_book[i-1]==phone_book[i].substr(0,phone_book[i-1].length()))
            return false;
    }
        
    return true;
}

풀이 2 [unordered_map 사용]

#include <string>
#include <vector>
#include <unordered_map>
#include <iostream>

using namespace std;

bool solution(vector<string> phone_book) {
    bool answer = true;

    unordered_map<string, int> hash_map;
    for(int i = 0; i < phone_book.size(); i++)
        hash_map[phone_book[i]] = 1;
    
    for(int i = 0; i < phone_book.size(); i++) {
        string phone_number = "";
        for(int j = 0; j < phone_book[i].size(); j++) {
            phone_number += phone_book[i][j];
            if(hash_map[phone_number] && phone_number != phone_book[i])
                answer = false;
        }
    }

    return answer;
}

 

위장

https://programmers.co.kr/learn/courses/30/lessons/42578#

 

코딩테스트 연습 - 위장

 

programmers.co.kr

풀이 [unordered_map 사용]

#include <string>
#include <vector>
#include <unordered_map>

using namespace std;

int solution(vector<vector<string>> clothes) {
    int answer = 1;
    unordered_map<string,int> hash_map;
    for(int i=0;i<clothes.size();i++)
        hash_map[clothes[i][1]]++;
    for(auto it = hash_map.begin(); it != hash_map.end(); it++)
        answer *= (it->second+1);
    return answer-1;

}

- unordered_map을 사용하여 옷 종류를 ket값으로 두고 종류 별 갯수를 value값으로 저장하여 value값+1을 모두 곱하여 입을 수 있는 착장의 경우의 수를 구함

 

 

베스트앨범

https://programmers.co.kr/learn/courses/30/lessons/42579

 

코딩테스트 연습 - 베스트앨범

스트리밍 사이트에서 장르 별로 가장 많이 재생된 노래를 두 개씩 모아 베스트 앨범을 출시하려 합니다. 노래는 고유 번호로 구분하며, 노래를 수록하는 기준은 다음과 같습니다. 속한 노래가

programmers.co.kr

 

유니온 파인드란?

 

- 그래프 알고리즘 중 한 가지

- 분리 집합(상호배타적 집합)(Disjoing-set) 과 같은 알고리즘

- 여러 노드가 존재할 경우, 선택한 두 노드가 서로 같은 그래프에 속하는지 판별하는 알고리즘

- 위 과정이 Find, Union 두 가지 연산으로 이루어짐

 Find : 노드 x 가 어떤 집합에 속하는지 확인하는 연산

 Union : 노드 x와 y가 포함되어 있는 집합을 병합하는 연산

 

 

init함수

void init() {
	for (int i = 1; i <= N; i++)
		root[i] = i;
}

- 모든 노드에 대하여 자기 자신을 자신의 root 로 초기화

 

Find함수

int find(int i) {
	if (i == root[i])
		return i;
	else
		return root[i] = find(root[i]); //경로 압축
}

- 파라미터로 들어간 노드의 루트 노드를 반환하는 find 함수

- 어느 트리(집합)에 속해있는지를 확인하는 연산

- 한 쪽으로 트리가 치우쳐진 경우 트리로 구현하는 이점이 사라지기 때문에 경로 압축 최적화를 한다.

- 경로 압축 최적화 : i 에 대하여 루트 노드인 root[i]를 찾은 경우 i의 부모를 바로 루트 노드를 바꾸어 트리의 높이를 압축하여 find 함수의 효율을 높인다.

 

Union함수

void merge(int a, int b) {
	int x = find(a);
	int y = find(b);

	//루트가 같다면 union과정 생략
	if (x == y)
		return;    
    if (rank[x] > rank[y])
        root[y] = x;
	else
		root[x] = y;
        
    //두 트리의 깊이가 같은 경우 y의 깊이를 늘림    
    if(rank[x] == rank[y])
        ++rank[y];
}

- 집합을 함칠 때 높이가 더 높은 트리가 낮은 트리 밑으로 들어갈 경우 트리가 점점 깊어져 효율성이 떨어진다.

- rank 배열을 사용하여 트리의 높이가 낮은 트리가 높은 트리 밑으로 들어가도록 한다.

 

 

 

- Given : What software will look like to user (= pre-conditions)

- When : Things that the user will do (= event or operation)

- Then : What the user should expect (= post-conditions)

 

- Given : 시나리오 진행에 필요한 값을 설정, 테스트의 상태를 설정

- When : 시나리오 진행 필요 조건 명시, 테스트하고자 하는 행동

- Then : 시나리오를 완료했을 때 보장해야하는 결과를 명시, 예상되는 변화 설명

 

ex)

Given - 테스트에 사용할 유저객체 선언 및 이름 정의

When - 생성된 객체를 사용하여 회원가입(join(member))을 진행한다.

Then - 회원가입이 완료되었는지 확인한다.(검증)( findOne(), findMember() )

 

code)

 @Test
    void 회원가입() {
        //given
        Member member = new Member();
        member.setName("spring");

        //when
        Long saveId = memberService.join(member);

        //then
        Member findMember = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());

    }

'spring' 카테고리의 다른 글

[Spring] DI(의존성주입-Dependency Injection)  (0) 2021.01.06

의존성 주입(Dependency Injection-DI)

(before)

public class MemberService {
   private final MemberRepository memberRepository = new
            MemoryMemberRepository();
}

이렇게 new를 통해 직접 객체를 생성하였을 경우 MemberService는 MemoryMemberRepository에 의존 된다.

이런 경우 MemoryMemberRepository()가 아닌 다른 객체를 사용하고 싶어도 코드를 직접 수정하지 않고는 불가능하다.

 

이를 다시 설명하면

 

" 객체를 필요로하는 클래스 내에서 직접 객체를 생성하는것은 클래스를 특정 객체에 커밋하는 것이고 이후에 클래스로부터 독립적으로(클래스의 수정 없이) 인스턴스의 생성을 변경하는것이 불가능하기 때문에 유연하지 못하다. 이는 다른 객체를 필요로하는 경우 클래스를 재사용할 수 없게하며, 실제 객체를 모의 객체로 대체할 수 없기 때문에 클래스를 테스트하기 힘들게한다. "

 

이러한 경우에 의존성을 없애기 위해 의존성 주입 방식을 사용하며 의존성 주입의 목적은 다음과 같다.

 

" 의존성 주입은 프로그램 디자인이 결합도 느슨하게 되도록하고 의존관계 역전 원칙  단일 책임 원칙을 따르도록 클라이언트의 생성에 대한 의존성을 클라이언트의 행위로부터 분리하는 것이다"

 

"의존성 주입의 의도는 객체의 생성과 사용의 관심을 분리하는 것이다. 이는 가독성과 코드 재사용을 높혀준다."

 

(after)

public class MemberService {
	private final MemberRepository memberRepository;
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}

이렇게 생성자를 통해 어떠한 객체를 받아서 MemberRepository를 생성해주면 의존성 주입을 하였다고 할 수 있다.

class MemberServiceTest {

    MemberService memberService;
    MemoryMemberRepository memberRepository;
    @BeforeEach
    public void beforeEach(){
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
    }
}

이렇게 MemberService에 넘겨주는 객체에 따라 의존성을 주입받을수 있다.

 

의존성주입 장점

  • 의존 관계 설정이 컴파일시가 아닌 실행시에 이루어져 모듈들간의 결합도를 낮출 수 있다.
  • 코드 재사용을 높여서 작성된 모듈을 여러 곳에서 소스코드의 수정 없이 사용할 수 있다.
  • 모의 객체등을 이용한 단위 테스트의 편의성을 높여준다.

의존성주입 방법

  • 생성자를 이용 (가장 권장)
@Controller
public class MemberController {

    private final MemberService memberService;

    //생성자 주입
    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }
}
  • setter를 이용
@Controller
public class MemberController {

    private final MemberService memberService;

    //setter
    @Autowired
    public setMemberService(MemberService memberService) {
        this.memberService = memberService;
    }
}
  • Field변수를 이용
@Controller
public class MemberController {

    @Autowired
    private MemberService memberService;
}

 

'spring' 카테고리의 다른 글

[Testing] Given-When-Then pattern  (0) 2021.01.06

트리의 개념

트리는 그래프의 한 종류로서 정점(node)들과 정점들을 연결하는 간선(edge) 들로 구성된다. 

사이클이 없는 하나의 연결 그래프이자 DAG(방향성이 있는 비순환 그래프) 의 한 종류이다.

트리는 하나의 루트 노드를 갖으며 부모 노드 밑에 여러 자식 노드가 연결되고, 자식 노드 각각에 다시 자식 노드가 연결되는 재귀적 형태의 자료구조이다. 

 

트리 관련 용어

 

  • 루트 노드(root node) : 부모가 없는 노드. 트리는 하나의 루트 노드만을 가진다.

  • 단말 노드(leaf node) : 자식이 없는 노드이다.

  • 내부(internal) 노드 : 리프 노드가 아닌 노드.

  • 링크(link) : 노드를 연결하는 선 (edge, branch 라고도 부름).

  • 형제(sibling) : 같은 부모를 가지는 노드.

  • 노드의 크기(size) : 자신을 포함한 모든 자손 노드의 개수.

  • 노드의 깊이(depth) : 루트에서 어떤 노드에 도달하기 위해 거쳐야 하는 간선의 수

  • 노드의 레벨(level) : 트리의 특정 깊이를 가지는 노드의 집합

  • 노드의 차수(degree) : 부(하위) 트리 갯수/간선수 (degree) = 각 노드가 지닌 가지의 수

  • 트리의 차수(degree of tree) : 트리의 최대 차수

  • 트리의 높이(height) : 루트 노드에서 가장 깊숙히 있는 노드의 깊이

 

트리의 종류

 

1. 이진 트리 (Binary tree)

  : 부모 노드가 자식 노드를 최대 2개씩만 갖고 있는 트리

 

2. 이진 검색 트리 (Binary Search Tree)

 : left child 와 그 이하 노드들의 데이터가 현재 노드의 데이터보다 작아야 하고

 : right child 와 그 이하 노드들의 데이터가 현재 노드의 데이터보다 커야 한다.

 

3. 균형 트리 (Balanced Tree)

 : O(logN) 의 시간에 insert와 find를 할 수 있을 정도로 left노드와 right노드의 갯수가 균형적으로 이루어져 있는 트리

 

4. 완전 이진 트리 (Complete Binary Tree)

 : 트리의 모든 높이에서 노드가 꽉 차 있는 이진 트리(마지막 레벨을 제외하고 모든 레벨이 완전히 채워져 있음)

 : 마지막 레벨은 완전히 채워져 있지 않아도 되지만 노드가 왼쪽에서 오른쪽 순으로 채워져야 한다.

 

5. 정 이진트리 (Full Binary Tree)

 : 모든 노드가 0개 또는 2개의 자식 노드를 갖는 트리

 

6. 포화 이진 트리 (Perfect Binary Tree)

 : 정 이진트리면서 완전 이진트리인 경우

 : 모든 말단 노드는 같은 높이에 있어야 하며, 마지막 단계에서는 노드의 개수가 최대가 되어야 한다.

 : 모든 내부 노드가 두 개의 자식 노드를 가진다.

 : 노드의 개수가 2^(k-1) 개여야 한다. (k: 트리 높이)

 

 

Dijkstra Algorithm

음의 가중치가 없는 그래프의 한 정점(頂點, Vertex)에서 모든 정점까지의 최단거리를 각각 구하는 알고리즘(최단 경로 문제, Shortest Path Problem)이다.

 

- 다익스트라 알고리즘의 최단 경로 문제 해결 과정

 

노드(Node) : 0번 ~ 6번

링크(Link) : 각 노드를 연결하는 선

비용(Cost) :링크 위에 표시된 숫자, 해당 링크를 지나갈때에 소요되는 경비, 비용

 

다익스트라 알고리즘을 통해 0번 노드를 시작접으로 1~6번 노드를 목적지로 하는 최단 경로 6개를 구하는 과정

S : 이미 처리가 완결된 노드의 집합

- 시작 노드인 0번 부터 처리를 시작

D : 0번 ~ 6번 노드에 대한, 시작노드로부터의 소요되는 비용

- 0번 -> 1번 노드 비용 : 5 // 0번 -> 3번 노드 비용 : 1

T : 해당 노드로 가는데 연결된 노드의 번호

- 상단 : 노드 번호 // 하단 : 연결된 노드 번호

 

S 집합에 3번 노드 추가

- D 저장소에 3번 노드에 대한 비용값이 가장 최소이기 때문

3번 노드와 링크로 연결된 2번 노드와 5번 노드에 대한 비용값을 계산

- D 저장소에 기록

시작 노드 0번 노드로부터 2번 노드까지 소요되는 비용 : 3

- 0번 -> 3번 -> 2번 이동 루트에 있는 비용값의 합 = 1 + 2 = 3

시작 노드 0번 노드로부터 5번 노드까지 소요되는 비용 : 2

- 0번 -> 3번 -> 5번 이동 루트에 있는 비용값의 합 = 1 + 1 = 2

 

S 집합에 5번 노드 추가

- 이미 처리가 완료된 노드 이외의 노드 중 5번 노드의 비용이 2로 최소이기 때문

- S 집합에 존재하는지 확인하여 이미 처리가 완료된 노드인지 확인

5번 노드와 링크로 연결된 2번 노드, 3번 노드, 6번 노드에 대한 비용값을 계산

- 2번 노드 : 2 + 2 = 4 (전 단계에서 계산된 비용값 3보다 크므로 무시)

- 3번 노드 : 이미 처리 완료

- 6번 노드 : 2 + 3 = 5 (D 저장소에 기록)

 

S 집합에 2번 노드 추가

- 이미 처리가 완료된 노드 이외의 노드 중 2번 노드의 비용이 2로 최소이기 때문

2번 노드와 링크로 연결된 1번 노드, 3번 노드, 5번 노드, 6번 노드에 대한 비용값을 계산

- 1번 노드 : 3 + 1 = 4 (전 단계에서 계산된 비용값 5 보다 작으므로 D 저장소 값을 변경)

- 3번 노드 : 이미 처리 완료( S 집합 포함 )

- 5번 노드 : 이미 처리 완료( S 집합 포함 )

- 6번 노드 : 3 + 8 = 11 (전 단계에서 계산된 비용값 5보다 크므로 무시)

 

S 집합에 1번 노드 추가

- 이미 처리가 완료된 노드 이외의 노드 중 1번 노드의 비용이 최소이기 때문

1번 노드와 링크로 연결된 0번 노드, 2번 노드, 4번 노드에 대한 비용값을 계산

- 0번 노드 : 이미 처리 완료( S 집합 포함 )

- 2번 노드 : 이미 처리 완료( S 집합 포함 )

- 4번 노드 : 4 + 3 = 7 (D 저장소에 기록)

S 집합에 6번 노드 추가

- 이미 처리가 완료된 노드 이외의 노드 중 6번 노드의 비용이 최소이기 때문

6번 노드와 링크로 연결된 1번 노드, 2번 노드, 4번 노드, 5번 노드에 대한 비용값을 계산

- 1번 노드 : 이미 처리 완료( S 집합 포함 )

- 2번 노드 : 이미 처리 완료( S 집합 포함 )

- 4번 노드 : 5 + 1 = 6 (전 단계에서 계산된 비용값 7 보다 작으므로 D 저장소 값을 변경)

- 5번 노드 : 이미 처리 완료( S 집합 포함 )

 

S 집합에 4번 노드 추가

- 유일하게 처리되지 않은 노드

4번 노드와 링크로 연결된 1번 노드, 6번 노드에 대한 비용값을 계산

- 1번 노드 : 이미 처리 완료( S 집합 포함 )

- 6번 노드 : 이미 처리 완료( S 집합 포함 )

-> 더이상 진행하지 않고 종료

 

T 저장소를 통해 출발 노드인 0번 노드에서 각 노드에 대한 최단 경로 파악

 

  1. 0번 노드에서 1번 노드 : (1번 노드)←(2번 노드)←(3번 노드)←(0번 노드)
  2. 0번 노드에서 2번 노드 : (2번 노드)←(3번 노드)←(0번 노드)
  3. 0번 노드에서 3번 노드 : (3번 노드)←(0번 노드)
  4. 0번 노드에서 4번 노드 : (4번 노드)←(6번 노드)←(5번 노드)←(3번 노드)←(0번 노드)
  5. 0번 노드에서 5번 노드 : (5번 노드)←(3번 노드)←(0번 노드)
  6. 0번 노드에서 6번 노드 : (6번 노드)←(5번 노드)←(3번 노드)←(0번 노드)

 

 

- 수도 코드

 

 

- 시간 복잡도

  • n 개의 정점(node)이 있다고 했을 경우
  • 주 반복문을 n번 반복하고
  • 내부 반복문을 2n 번 반복하므로
  • O(n^2) 의 시간복잡도를 가진다.

수학에서 에라토스테네스의 체는 소수를 찾는 방법이다. 고대 그리스 수학자 에라토스테네스가 발견하였다.

 

 

에라토스테네스의 체는 소수를 구하기 위한 방법 중 하나로 프로그래밍 언어를 통해 알고리즘을 구현할 수 있다.

 

  1. 2부터 소수를 구하고자 하는 구간의 모든 수를 나열한다. 그림에서 회색 사각형으로 두른 수들이 여기에 해당한다.
  2. 2는 소수이므로 오른쪽에 2를 쓴다. (빨간색)
  3. 자기 자신을 제외한 2의 배수를 모두 지운다.
  4. 남아있는 수 가운데 3은 소수이므로 오른쪽에 3을 쓴다. (초록색)
  5. 자기 자신을 제외한 3의 배수를 모두 지운다.
  6. 남아있는 수 가운데 5는 소수이므로 오른쪽에 5를 쓴다. (파란색)
  7. 자기 자신을 제외한 5의 배수를 모두 지운다.
  8. 남아있는 수 가운데 7은 소수이므로 오른쪽에 7을 쓴다. (노란색)
  9. 자기 자신을 제외한 7의 배수를 모두 지운다.
  10. 위의 과정을 반복하면 구하는 구간의 모든 소수가 남는다.

 

 

1. 구간 합 VS 부분 합

- Prefix sum(구간 합) : 구간 합이란 수들의 나열에서 특정 구간의 합을 의미한다. 구간 합 알고리즘은 보통 1차원배열에서 i~k 인덱스 사이의 값들의 합을 구하는데 사용한다.

 

- Partial sum(부분 합) : 부분 합이란 구간 합과 달리 처음부터 특정인덱스까지의 합을 의미한다. 즉 0~k 인덱스 사이의 값들의 합을 의미한다.

 

2. 구간 합 알고리즘

  •  반복문을 사용하여 i~k사이의 값을 더하는 알고리즘의 시간복잡도는 O(n)이다.
  •  이 같은 알고리즘을 사용할 경우 n의 값이 클 경우 이를 정해진 시간 내에 해결할 수 없다.
  •  하지만 구간 합 알고리즘을 사용하여 구간합을 구하는 경우 O(1)의 성능을 갖는다.
  •  구간 합 알고리즘은 현재 진행단계까지의 인덱스까지 값의 합을 저장하는 sum[]배열을 사용한다.
  •  j번째 바로 앞까지의 총합에 arr[j] 값을 더하면 j번째까지의 총합을 의미하므로 sum[j] = sum[j-1] + arr[j] 로 표현할 수 있다.

 

3. 구현

 

다음은 백준 11659번 문제 구간합 구하기4 의 풀이이다.

 

-구간합 구하기 4

 

11659번: 구간 합 구하기 4

첫째 줄에 수의 개수 N (1 ≤ N ≤ 100,000), 합을 구해야 하는 횟수 M (1 ≤ M ≤ 100,000)이 주어진다. 둘째 줄에는 N개의 수가 주어진다. 수는 1,000보다 작거나 같은 자연수이다. 셋째 줄부터 M개의 줄에

www.acmicpc.net

- 문제 : 첫째 줄에 수의 개수 N (1 ≤ N ≤ 100,000), 합을 구해야 하는 횟수 M (1 ≤ M ≤ 100,000)이 주어진다. 둘째 줄에는 N개의 수가 주어진다. 수는 1,000보다 작거나 같은 자연수이다. 셋째 줄부터 M개의 줄에는 합을 구해야 하는 구간 i와 j가 주어진다.

- 총 M개의 줄에 입력으로 주어진 i번째 수부터 j번째 수까지 합을 출력한다.

 

 

* 풀이

다음과 같은 문제에서 처음 입력받은 N개의 숫자에 대하여 미리 구간합 (sum[i+1] = sum[i] + num)을 구함으로 인해

M번의 연산에서 이전에 sum[i] 에 저장되어 있는 0~i번째 수까지의 구간 합을 사용함으로써 문제를 정해진 시간 내에 풀 수 있다.

 

다음과 같은 문제를 미리 구간합을 구하지 않고 합을 구할 때 마다 반복문 내에서 index까지의 합을 구하여 중첩 반복문을 사용하여 풀 경우 M(연산횟수) 에 따라 전체 시간이 증가하게 되어 시간 복잡도가 O(M*N*N)으로 주어진 시간을 초과하게 된다.

 

#include <iostream>
#define MAX 100000

int sum[MAX];

int main(void)

	int N, M;
	cin >> N >> M;
	for (int i = 0; i < N; i++)
		{
			int num;
			cin >> num;
			sum[i+1] = sum[i] + num;
		}

	for (int i = 0; i < M; i++)
	{
		int x, y;
		cin >> x >> y;
		cout << sum[y] - sum[x-1] << endl;
	}
	return 0;
}

 

+ Recent posts