#우선 만냠이라는 어플리케이션은...

 

 

이 어플리케이션은 

음식점 사이트 -냠톨릭에 이어서

 

같이 밥먹을 사람들 찾는 매칭 어플리케이션이다.

https://github.com/dfdfg42/CatholicTableMatching

 

GitHub - dfdfg42/CatholicTableMatching: 가톨릭대 혼밥 매칭 서비스

가톨릭대 혼밥 매칭 서비스. Contribute to dfdfg42/CatholicTableMatching development by creating an account on GitHub.

github.com

 

 

 

 

 

우선 어플리케이션이 어떻게 되어있는지 잠시 살펴보자면

 

 

 

 

 

이런식으로 매칭정보를 입력하고

매칭이 되면 문자로 수신 받을 수 있다.

 

 

 

 

#기존의 코드는..

 

우선 기존의 코드에 대해서 이해 할 필요가 있다.

그 전에 우선

 

각 유저는 원하는 시간 음식종류,이성인지 동성인지에 대한 매칭 폼을 작성한다

 

 

 

사용자의 matchform 은

음식종류,식사시간,성별

의 특성을 가지고 있다.

 

이걸 유저가 등록하면 유저와 동일한 matchform

을 가진 유저와 매칭을 해주는 것이다.

 

 

 

 

 

#그렇다면 기존의 코드는 어떻게 매칭되나..

 

우선 기존의 코드에 대해서 설명하자면

 

createMatch 함수로 1명 유저에 대해서 매칭을 시도한다

    @Transactional
    public Match createMatch(User user) {
        lock.lock(); //락 함걸어봄
        try {
            MatchForm matchForm = user.getMatchForm();
            List<User> potentialMatches = userRepository.findMatchesByPreferences(
                    matchForm.getFoodType(), matchForm.getTimeSlot(), matchForm.getPreferGender(), false);

            for (User potentialMatch : potentialMatches) {
                if (!potentialMatch.isMatched() && !potentialMatch.getId().equals(user.getId())
                        && !potentialMatch.getGender().equals(user.getGender())) {
                    Match match = new Match(user, potentialMatch, new Date(), matchForm.getTimeSlot(), matchForm.getFoodType());

                    potentialMatch.setMatched(true);
                    user.setMatched(true);

                    userRepository.save(potentialMatch);
                    userRepository.save(user);
                    return matchRepository.save(match);
                }
            }
            return null; // No match found
        } finally {
            lock.unlock(); // 락 임시
        }
    }

 

 

이 코드를 살펴보자면.

 

1.유저의 매치폼에 작성된 음식종류,시간,선호 성별과 동일한 매치을 가진 유저를 쿼리를 날려 

리스트로 가져온다

2.리스트를 순회하며 아직 매칭이 안됬다면 매칭을 하고 갱신한다

 

이렇게 하나의 유저를 매칭하는데 전체 유저를 조회해야되서 n의 시간복잡도가 필요하다

 

 

 

 

 

#이후에 모든 유저에 대해서 매칭하기

 

 

createMatchForAllUsers 함수로 모든 유저에 대해서 matchCreate 함수를 실행하기

    @Transactional
    public void createMatchForAllUsers() {
        List<User> users = userRepository.findAll();
        List<Match> matches = new ArrayList<>();
        for (User user : users) {
            if(!user.isMatched() && !(user.getMatchForm()==null) ){
                Match match = createMatch(user);
                if (match != null) {
                    matches.add(match);
                }
            }
        }
      /*  return matches; // 모든 매치 결과 반환*/
    }

 

 

이렇게 매칭되지 않은 모든 유저에 대해서 createMatch를 해주면 매칭은 완료된다

 

모든유저(n) 에 대해서 createMatch(n)을 하였기 때문에  이말은 n^2 의 시간이 걸린다는소리다

 

 

 

 

#그럼 이제 개선을 해보자 (이진탐색트리) (n->logn)

 

우선 이진탐색 트리로 개선을 해보도록 하자.

 

foodType, timeSlot, preferGender 세 가지 특성 을 하나의 트리 노드에 넣도록 하자

 

treeNode 코드

public class TreeNode {
    String foodType;
    String timeSlot;
    String preferGender;
    List<User> users;
    TreeNode left, right;

    public TreeNode(String foodType, String timeSlot, String preferGender, User user) {
        this.foodType = foodType;
        this.timeSlot = timeSlot;
        this.preferGender = preferGender;
        this.users = new ArrayList<>();
        this.users.add(user);
        this.left = this.right = null;
    }
}

 

 

 

트리 노드를 이용해서

이진 탐색 트리 코드를 작성하도록 하자

public class BinarySearchTree {
    private TreeNode root;

    public BinarySearchTree() {
        this.root = null;
    }

    public void insert(String foodType, String timeSlot, String preferGender, User user) {
        root = insertRec(root, foodType, timeSlot, preferGender, user);
    }

    private TreeNode insertRec(TreeNode root, String foodType, String timeSlot, String preferGender, User user) {
        if (root == null) {
            return new TreeNode(foodType, timeSlot, preferGender, user);
        }
        int cmp = compare(root, foodType, timeSlot, preferGender);
        if (cmp > 0) {
            root.left = insertRec(root.left, foodType, timeSlot, preferGender, user);
        } else if (cmp < 0) {
            root.right = insertRec(root.right, foodType, timeSlot, preferGender, user);
        } else {
            root.users.add(user);
        }
        return root;
    }

    public void remove(String foodType, String timeSlot, String preferGender, User user) {
        root = removeRec(root, foodType, timeSlot, preferGender, user);
    }

    private TreeNode removeRec(TreeNode root, String foodType, String timeSlot, String preferGender, User user) {
        if (root == null) return null;

        int cmp = compare(root, foodType, timeSlot, preferGender);
        if (cmp > 0) {
            root.left = removeRec(root.left, foodType, timeSlot, preferGender, user);
        } else if (cmp < 0) {
            root.right = removeRec(root.right, foodType, timeSlot, preferGender, user);
        } else {
            Iterator<User> iterator = root.users.iterator();
            while (iterator.hasNext()) {
                User u = iterator.next();
                if (u.equals(user)) {
                    iterator.remove();
                    break;
                }
            }
            if (root.users.isEmpty()) {
                if (root.left == null) return root.right;
                if (root.right == null) return root.left;
                TreeNode temp = findMin(root.right);
                root.foodType = temp.foodType;
                root.timeSlot = temp.timeSlot;
                root.preferGender = temp.preferGender;
                root.users = temp.users;
                root.right = removeRec(root.right, temp.foodType, temp.timeSlot, temp.preferGender, user);
            }
        }
        return root;
    }

    private TreeNode findMin(TreeNode root) {
        while (root.left != null) root = root.left;
        return root;
    }

    public List<User> search(String foodType, String timeSlot, String preferGender) {
        return searchRec(root, foodType, timeSlot, preferGender);
    }

    private List<User> searchRec(TreeNode root, String foodType, String timeSlot, String preferGender) {
        if (root == null) return new ArrayList<>();

        int cmp = compare(root, foodType, timeSlot, preferGender);
        if (cmp == 0) {
            return root.users;
        } else if (cmp > 0) {
            return searchRec(root.left, foodType, timeSlot, preferGender);
        } else {
            return searchRec(root.right, foodType, timeSlot, preferGender);
        }
    }

    private int compare(TreeNode node, String foodType, String timeSlot, String preferGender) {
        int cmpFoodType = foodType.compareTo(node.foodType);
        if (cmpFoodType != 0) return cmpFoodType;
        int cmpTimeSlot = timeSlot.compareTo(node.timeSlot);
        if (cmpTimeSlot != 0) return cmpTimeSlot;
        return preferGender.compareTo(node.preferGender);
    }
}

 

 

여기서 이진탐색의 비교 함수에 대해서 짚고 넘어가자면

 

각각의 문자열을 비교 해서 더 작은쪽이 왼쪽

큰 쪽이 오른쪽 으로 리턴을 하게 되어있다

 

문자열은 사전순으로 비교하게 된다

 

그림으로 이진 탐색 트리가 어떻게 구성되는지 그려보자

 

전부 다 그린건 아니지만 대략

이런식으로 이진탐색 트리가 구성되고 

모든 특성에 대해서 이진 탐색 트리가 구성될 것이다.

 

 

이에 맞게 matchingService도 바꾸도록 하자

 

package com.csec.CatholicTableMatching.service;

import com.csec.CatholicTableMatching.BinarySearchTree;
import com.csec.CatholicTableMatching.domain.*;
import com.csec.CatholicTableMatching.repository.MatchFormRepository;
import com.csec.CatholicTableMatching.repository.MatchRepository;
import com.csec.CatholicTableMatching.security.domain.User;
import com.csec.CatholicTableMatching.security.repository.UserRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Service
public class MatchingService {

    private final BinarySearchTree binarySearchTree = new BinarySearchTree();
    private final UserRepository userRepository;
    private final MatchRepository matchRepository;
    private final MatchFormRepository matchFormRepository;

    // lock을 이용한 동시성 제어
    private final Lock lock = new ReentrantLock();

    public MatchingService(UserRepository userRepository, MatchRepository matchRepository, MatchFormRepository matchFormRepository) {
        this.userRepository = userRepository;
        this.matchRepository = matchRepository;
        this.matchFormRepository = matchFormRepository;
        initializeTreeWithUsers();
    }

    // 모든 유저를 트리에 삽입하는 메서드
    private void initializeTreeWithUsers() {
        List<User> users = userRepository.findAll();
        for (User user : users) {
            if (!user.isMatched() && user.getMatchForm() != null) {
                addToIndex(user);
            }
        }
    }

    @Transactional
    public void createMatchForAllUsers() {
        List<User> users = userRepository.findAll();
        List<Match> matches = new ArrayList<>();
        for (User user : users) {
            if (!user.isMatched() && user.getMatchForm() != null) {
                addToIndex(user);
                Match match = createMatch(user);
                if (match != null) {
                    matches.add(match);
                }
            }
        }
        // return matches; // 모든 매치 결과 반환 (필요 시)
    }

    private void addToIndex(User user) {
        MatchForm matchForm = user.getMatchForm();
        binarySearchTree.insert(matchForm.getFoodType(), matchForm.getTimeSlot(), matchForm.getPreferGender(), user);
    }

    private void removeFromIndex(User user) {
        MatchForm matchForm = user.getMatchForm();
        binarySearchTree.remove(matchForm.getFoodType(), matchForm.getTimeSlot(), matchForm.getPreferGender(), user);
    }

    @Transactional
    public Match createMatch(User user) {
        lock.lock(); // 동시성 제어를 위한 lock
        try {
            MatchForm matchForm = user.getMatchForm();
            List<User> potentialMatches = binarySearchTree.search(matchForm.getFoodType(), matchForm.getTimeSlot(), matchForm.getPreferGender());

            for (User potentialMatch : potentialMatches) {
                if (!potentialMatch.isMatched() && !potentialMatch.getId().equals(user.getId())
                        && !potentialMatch.getGender().equals(user.getGender())) {
                    Match match = new Match(user, potentialMatch, new Date(), matchForm.getTimeSlot(), matchForm.getFoodType());

                    potentialMatch.setMatched(true);
                    user.setMatched(true);

                    removeFromIndex(user);
                    removeFromIndex(potentialMatch);

                    userRepository.save(potentialMatch);
                    userRepository.save(user);
                    return matchRepository.save(match);
                }
            }
            return null; // No match found
        } finally {
            lock.unlock(); // 락 해제
        }
    }

    @Transactional
    public List<Match> MatchResult() {
        return matchRepository.findAll();
    }

    @Transactional
    public User findPartner(User user) {
        User partner = matchRepository.findPartnerByUser1(user);
        if (partner == null) {
            partner = matchRepository.findPartnerByUser2(user);
        }
        return partner;
    }
}

 

 

 

 

 

#이진탐색트리 개선 결과

 

 

 

 

매칭이 잘 되는걸 확인 할 수 있다.

 

 

 

#이진탐색 트리보다 해쉬맵이 낫지 않나? (logn->n)

 

 

 

분명 이진탐색트리로

 

각 특성을 가진 유저들을 log n 시간으로 조회할 수 있긴 하지만

이럴거면 그냥 해쉬맵을 사용하는게 더 좋지 않을까?

 

해쉬맵은 O(1)속도로 접근하는데 

만약 유저가 전부 해쉬맵에 들어가있다면 비효율 적이겟지만

 

현 상황은 특성들만 분류하기때문에 굳이 이진탐색트리를 쓸 필요가 없다.

 

 

 

그러니 코드를 해쉬맵으로 바꾸자

 

각 특성별로 조합해 키를만들기만하면 된다.

그리고 추가적으로 umatched 유저들을 저장해두는 해쉬맵도 따로 만들도록 하자.

package com.csec.CatholicTableMatching.service;

import com.csec.CatholicTableMatching.BinarySearchTree;
import com.csec.CatholicTableMatching.domain.*;
import com.csec.CatholicTableMatching.repository.MatchFormRepository;
import com.csec.CatholicTableMatching.repository.MatchRepository;
import com.csec.CatholicTableMatching.security.domain.User;
import com.csec.CatholicTableMatching.security.repository.UserRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Service
public class MatchingService {

    private final HashMap<String, List<User>> userTable = new HashMap<>();
    private final HashMap<String, List<User>> unmatchedUserTable = new HashMap<>();
    private final UserRepository userRepository;
    private final MatchRepository matchRepository;
    private final MatchFormRepository matchFormRepository;

    private final Lock lock = new ReentrantLock();

    public MatchingService(UserRepository userRepository, MatchRepository matchRepository, MatchFormRepository matchFormRepository) {
        this.userRepository = userRepository;
        this.matchRepository = matchRepository;
        this.matchFormRepository = matchFormRepository;
        initializeTableWithUsers();
    }

    private void initializeTableWithUsers() {
        List<User> users = userRepository.findAll();
        for (User user : users) {
            if (user.getMatchForm() != null) {
                addToTable(user);
                if (!user.isMatched()) {
                    addToUnmatchedTable(user);
                }
            }
        }
    }

    @Transactional
    public void createMatchForAllUsers() {
        List<User> users = userRepository.findAll();
        List<Match> matches = new ArrayList<>();
        for (User user : users) {
            if (!user.isMatched() && user.getMatchForm() != null) {
                addToTable(user);
                addToUnmatchedTable(user);
                Match match = createMatch(user);
                if (match != null) {
                    matches.add(match);
                }
            }
        }
    }

    private void addToTable(User user) {
        MatchForm matchForm = user.getMatchForm();
        String key = generateKey(matchForm.getFoodType(), matchForm.getTimeSlot(), matchForm.getPreferGender());
        userTable.computeIfAbsent(key, k -> new ArrayList<>()).add(user);
    }

    private void addToUnmatchedTable(User user) {
        MatchForm matchForm = user.getMatchForm();
        String key = generateKey(matchForm.getFoodType(), matchForm.getTimeSlot(), matchForm.getPreferGender());
        unmatchedUserTable.computeIfAbsent(key, k -> new ArrayList<>()).add(user);
    }

    private void removeFromTable(User user) {
        MatchForm matchForm = user.getMatchForm();
        String key = generateKey(matchForm.getFoodType(), matchForm.getTimeSlot(), matchForm.getPreferGender());
        List<User> users = userTable.get(key);
        if (users != null) {
            users.remove(user);
            if (users.isEmpty()) {
                userTable.remove(key);
            }
        }
    }

    private void removeFromUnmatchedTable(User user) {
        MatchForm matchForm = user.getMatchForm();
        String key = generateKey(matchForm.getFoodType(), matchForm.getTimeSlot(), matchForm.getPreferGender());
        List<User> users = unmatchedUserTable.get(key);
        if (users != null) {
            users.remove(user);
            if (users.isEmpty()) {
                unmatchedUserTable.remove(key);
            }
        }
    }

    private String generateKey(String foodType, String timeSlot, String preferGender) {
        return foodType + "|" + timeSlot + "|" + preferGender;
    }

    @Transactional
    public Match createMatch(User user) {
        lock.lock();
        try {
            MatchForm matchForm = user.getMatchForm();
            String key = generateKey(matchForm.getFoodType(), matchForm.getTimeSlot(), matchForm.getPreferGender());
            List<User> potentialMatches = unmatchedUserTable.getOrDefault(key, new ArrayList<>());

            for (User potentialMatch : potentialMatches) {
                if (!potentialMatch.isMatched() && !potentialMatch.getId().equals(user.getId())
                        && !potentialMatch.getGender().equals(user.getGender())) {
                    Match match = new Match(user, potentialMatch, new Date(), matchForm.getTimeSlot(), matchForm.getFoodType());

                    potentialMatch.setMatched(true);
                    user.setMatched(true);

                    removeFromTable(user);
                    removeFromTable(potentialMatch);
                    removeFromUnmatchedTable(user);
                    removeFromUnmatchedTable(potentialMatch);

                    userRepository.save(potentialMatch);
                    userRepository.save(user);
                    return matchRepository.save(match);
                }
            }
            return null;
        } finally {
            lock.unlock();
        }
    }

    @Transactional
    public List<Match> matchResult() {
        return matchRepository.findAll();
    }

    @Transactional
    public User findPartner(User user) {
        User partner = matchRepository.findPartnerByUser1(user);
        if (partner == null) {
            partner = matchRepository.findPartnerByUser2(user);
        }
        return partner;
    }
}

 

 

 

#결과

 

결과적으로 훨씬 빨라졌고

결과도 여전히 잘 나오는 것을 확인 할 수 있다.

O(n)에서 O(1)로 줄게 되었다.

 

모든 유저에 대해서 매칭을 하면 O(n)-> 에서 O(1)로 줄게 된 것이다.

 

 

 

#문제 간단 정리

 

브루트 포스로는 시간초과와 효율성으로 엉망일게 보이니

투 포인터/슬라이딩 윈도우를 활용하자

 

 

#문제 해결 방법

 

우선 사실 구간을 [1,3] [2,4] 이런식으로 출력 하라는 것 자체가

어쩌면 투 포인터 사용에 대한 힌트일지도 모른다.

 

1.우선 전체 보석의 종류의 개수를 센 뒤에.

2. 투 포인터를 사용한다

 만약 현재 보석의 모든 개수를 포함한다면

 left를 증가시키고 

보석의 종류가 부족하다면 right 를 증가시키면 된다.

 

2-1 

 여기서 보석종류에 따른 개수를

map 으로 기록해주는데

만약 left가 한칸 올라가서 map에 있는 보석종류의 개수가 0이되었다면

전체 보석의 개수를 기록하고 있는 변수를 (currenTypes)를 -1 해주면된다

 

반대로 right 한칸 올라가서 보석종류의 개수가 0개였다면

전체 보석의 개수를 기록하고 있는 변수를 (currenTypes)를 +1 해주면된다

 

이 작업은 효율성 때문에 이뤄지는 작업이다.

현재 가지고있는 보석의 종류 개수를 쉽게 카운트 하기 위해서.

 

나머지는 전체 코드를 보고 이해하는게 쉬울것이다.

 

 

#전체 코드

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

using namespace std;

vector<int> solution(vector<string> gems) {
    unordered_map<string, int> gemCount;
    set<string> gemTypes(gems.begin(), gems.end());
    int totalTypes = gemTypes.size();
    
    int minlength = INT_MAX;
    pair<int, int> index;
    
    // 투 포인터, 슬라이딩 윈도우를 활용
    int l = 0, r = 0;
    int currentTypes = 0;
    
    while (true) {
        if (currentTypes == totalTypes) { // 모든 종류의 보석이 포함되었을 때
            if (minlength > r - l) {
                minlength = r - l;
                index = make_pair(l, r);
            }
            if (--gemCount[gems[l]] == 0) {
                currentTypes--;
            }
            l++;
        } else { // 모든 종류의 보석이 포함되지 않았을 때
            if (r == gems.size()) break; // 종료 조건
            if (gemCount[gems[r]]++ == 0) {
                currentTypes++;
            }
            r++;
        }
    }
    
    vector<int> answer = {index.first + 1, index.second};
    return answer;
}

 

https://www.acmicpc.net/problem/2312

 

 

 

#문제 간단 정리

 

N을 소인수분해한 결과를 출력

 

 

#문제 해결 방법

 

우선 첫째로 소인수 분해를 하기 위해

소수들로 나눠줘야 하는데

 

그렇기 때문에 1. 소수를 구해야 한다

 

소수를 구하는 방법은 여러가지 방법이 있지만

브루트포스로 1부터 N까지 소수를 구하거나 하면 너무 시간이 오래 걸리니

에라토스테네스로 소수를 구해서 

소수들만 따로 구해주도록한다.

 

그렇다면 이제 주어진 수를 소수로 나누고 개수를 카운팅해야된다

2. 소수로 나누고 개수를 카운팅해야된다

에라토스테네스의 체로 구한 소수들로 계속해서 나눠서 소인수분해한 결과를

담는 것은 어렵지 않다.

 

그런데 만약에 2x2x3 이렇게 되있다면 2가 두번 중복 되기 때문에

2 2 이렇게 (숫자) (개수) 로 한번에 출력해야 되기 때문에

나는 map 을 사용해서 map에 이미 존재한다면 map 의 키 값을 증가시켜줘 

개수를 세어줬다.

 

 

#전체 코드

 

에라토스테네스의 체는 https://dfdfg42.tistory.com/entry/%EC%97%90%EB%9D%BC%ED%86%A0%EC%8A%A4%ED%85%8C%EB%84%A4%EC%8A%A4%EC%9D%98-%EC%B2%B4-C-%EC%BD%94%EB%93%9C

 

에라토스테네스의 체 C++ 코드

https://www.acmicpc.net/problem/2960#include #include #include #include #include using namespace std;bool check[1000001];int main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); int n, k; cin >> n >> k; int count = -1; for (int i = 2; i

dfdfg42.tistory.com

를 참고하도록 하자(모른다면)

 

#include <iostream>
#include <vector>
#include <string>
#include <cstring>
#include <map>

using namespace std;


const int MAX = 100'000;

bool check[MAX + 1];
vector<int> primes;
vector<int> factors;

void Factorization(int num) {

    for (int i : primes) {
        if (num % i == 0) {
            factors.push_back(i);
            Factorization(num / i);
            break;
        }
    }

    return;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);



    for (int i = 2; i <= MAX; i++) {

        if (check[i] == false) {
            primes.push_back(i);

            for (int j = i; j <= MAX; j += i) {
                if (check[j] == false) {
                    check[j] = true;

                }
            }
        }

    }


    int t;
    cin >> t;

    for (int i = 0; i < t; i++) {
        int a;
        cin >> a;


        Factorization(a);

        map<int, int> factorCount;
        for (int f : factors) {
            if (factorCount.find(f) != factorCount.end()) {
                factorCount[f]++;
            }
            else {
                factorCount[f] = 1;
            }
        }

        for ( auto& pair : factorCount) {
            cout << pair.first << " " << pair.second << '\n';
        }

        factors.clear();
        factorCount.clear();
    }

    return 0;
}

'[백준] > C++' 카테고리의 다른 글

백준 31718번 Double Up [C++]  (0) 2024.07.04
백준 15831번 준표의 조약돌[C++]  (0) 2024.07.01
백준 11721번 열 개씩 끊어 출력하기 [C++]  (0) 2024.06.19
백준 1786번 찾기 [C++]  (0) 2024.06.18
백준 9742번 순열 [C++]  (0) 2024.06.18

https://www.acmicpc.net/problem/2960

#include <iostream>
#include <string>
#include <istream>
#include <vector>
#include <algorithm>
using namespace std;
bool check[1000001];



int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, k;
    cin >> n >> k;
    int count = -1;

    for (int i = 2; i <= n; i++) {

        if (check[i] == false) {

            for (int j = i; j <= n; j += i) {
                if (check[j] == false) {
                    check[j] = true;
                    k--;
                    count = j;

                }
                if (k == 0) {
                    cout << count;
                    break;
                }
            }
        }
        
    }




    return 0;
}

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

LIS, LCS C++코드  (0) 2024.08.26
플로이드 워셜 C++코드  (0) 2024.08.26
KMP C++ 코드  (0) 2024.06.18
정렬,머지소트,퀵소트 C++ 코드  (0) 2024.06.18
위상정렬 C++ 코드  (1) 2024.06.18

https://www.acmicpc.net/problem/11721

 

 

 

#문제 간단 정리

문자열을 10개 씩 끊어 출력하는 문자열 문제

 

#문제 해결 방법

https://dfdfg42.tistory.com/entry/C-%EB%AC%B8%EC%9E%90%EC%97%B4-%EC%9E%90%EB%A5%B4%EA%B8%B0-%EC%B4%9D%EC%A0%95%EB%A6%AC

 

C++ 문자열 자르기 총정리

1) substr 함수substr 함수는 문자열의 특정 부분을 잘라내는 데 사용됩니다.기본 사용법cpp #include #include using namespace std;int main() { string s = "0123456789"; string subs1 = s.substr(2, 5); // subs1 = "23456" cout 시작

dfdfg42.tistory.com

 

c++ 는 문자열 다루는게 귀찮기 때문에 잘 알아 두도록 하자

substr 을 사용하였다.

 

#전체 코드

 

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

using namespace std;



int main() {
    
    string input;

    getline(cin, input);
    

    for (int i = 0; i < input.length(); i++) {
        string temp;

        if (input.length() - i > 10) {
            temp = input.substr(i, 10);
            cout << temp << '\n';
            i += 9;
        }
        else {
            temp = input.substr(i, input.length() - i);
            cout << temp << '\n';
            break;
        }

    }

    return 0;
}

'[백준] > C++' 카테고리의 다른 글

백준 15831번 준표의 조약돌[C++]  (0) 2024.07.01
백준 2312번 수 복원하기 [C++]  (0) 2024.06.26
백준 1786번 찾기 [C++]  (0) 2024.06.18
백준 9742번 순열 [C++]  (0) 2024.06.18
백준 1275번 커피숍2 [C++]  (0) 2024.06.16

1) substr 함수

substr 함수는 문자열의 특정 부분을 잘라내는 데 사용됩니다.

기본 사용법

cpp
 
#include <iostream>
#include <string>

using namespace std;

int main() {
    string s = "0123456789";

    string subs1 = s.substr(2, 5); // subs1 = "23456"
    cout << "subs1: " << subs1 << endl;

    string subs2 = s.substr(5); // subs2 = "56789"
    cout << "subs2: " << subs2 << endl;

    string subs3 = s.substr(); // subs3 = "0123456789"
    cout << "subs3: " << subs3 << endl;

    return 0;
}
  • 시작 위치와 길이를 지정하여 문자열을 자릅니다.
  • 시작 위치만 지정하면 해당 위치부터 끝까지 문자열을 자릅니다.
  • 아무 것도 지정하지 않으면 문자열 전체를 복사합니다.

find 함수와 함께 사용하기

#include <iostream>
#include <string>

using namespace std;

int main() {
    string s = "0123456789";

    string subs1 = s.substr(s.find('6')); // subs1 = "6789"
    cout << "subs1: " << subs1 << endl;

    string subs2 = s.substr(s.find('6'), 2); // subs2 = "67"
    cout << "subs2: " << subs2 << endl;

    return 0;
}​
  • find 함수를 이용해 특정 문자나 문자열을 찾고, 그 위치부터 잘라낼 수 있습니다.

추가 팁:

  • substr 함수는 범위가 문자열의 길이를 초과할 경우 자동으로 문자열 끝까지 자릅니다.
  • 인덱스가 음수거나 범위를 벗어나면 out_of_range 예외가 발생할 수 있으므로 주의가 필요합니다.

2) sstream을 이용한 쪼개기

sstream을 이용하면 문자열을 특정 구분자로 쉽게 쪼갤 수 있습니다.

기본 사용법

#include <sstream>
#include <vector>
#include <string>
#include <iostream>

using namespace std;

int main() {
    string s = "012 34567 89";
    istringstream ss(s);
    string subs1; // 임시 변수
    vector<string> v;

    while (getline(ss, subs1, ' ')) {
        v.push_back(subs1); // v = {"012", "34567", "89"}
    }

    // 결과 출력
    for (const auto& str : v) {
        cout << str << " ";
    }

    return 0;
}​
  • getline 함수를 이용하여 구분자로 문자열을 쪼개고, 이를 벡터에 저장합니다.

추가 팁:

  • getline 함수는 구분자를 지정하지 않으면 기본적으로 줄바꿈 문자(\n)를 구분자로 사용합니다.
  • 문자열을 여러 번 쪼개야 할 경우 istringstream을 반복 사용하여 처리할 수 있습니다.

3) C의 strtok 함수 사용

strtok 함수는 C 스타일 문자열을 특정 구분자로 쪼갤 때 사용됩니다.

기본 사용법

#include <iostream>
#include <cstring>

using namespace std;

int main() {
    char str[] = "a b c d e f";
    char* p_str = strtok(str, " ");
    
    while (p_str != NULL) {
        cout << p_str << ' ';
        p_str = strtok(NULL, " ");
    }

    return 0;
}
  • 첫 번째 인자에 자를 문자열을, 두 번째 인자에 구분자를 넣어 사용합니다.

여러 구분자 사용하기

#include <iostream>
#include <cstring>

using namespace std;

int main() {
    char str2[] = "a-b,c:d e-f";
    char* p_str2 = strtok(str2, ",- :");

    while (p_str2 != NULL) {
        cout << p_str2 << ' ';
        p_str2 = strtok(NULL, ",- :");
    }

    return 0;
}
  • 여러 구분자를 설정하여 문자열을 쪼갤 수 있습니다.

추가 팁:

  • strtok 함수는 원본 문자열을 변경합니다. 따라서 원본 문자열을 보존해야 할 경우 복사본을 사용해야 합니다.
  • strtok 함수는 스레드 안전하지 않으므로 멀티스레딩 환경에서는 사용을 피하거나 대체 함수(strtok_r)를 사용하는 것이 좋습니다.

마무리

위에서 다룬 substr, sstream, strtok 함수는 각각의 장점과 단점을 가지고 있습니다. 상황에 맞게 적절한 방법을 사용하여 문자열을 효율적으로 다뤄보세요. 이 글이 여러분의 C++ 프로그래밍에 도움이 되길 바랍니다.

코드 예제

substr 함수

#include <iostream>
#include <string>

using namespace std;

int main() {
    string s = "0123456789";

    string subs1 = s.substr(2, 5); // subs1 = "23456"
    cout << "subs1: " << subs1 << endl;

    string subs2 = s.substr(5); // subs2 = "56789"
    cout << "subs2: " << subs2 << endl;

    string subs3 = s.substr(); // subs3 = "0123456789"
    cout << "subs3: " << subs3 << endl;

    return 0;
}

sstream을 이용한 쪼개기

#include <sstream>
#include <vector>
#include <string>
#include <iostream>

using namespace std;

int main() {
    string s = "012 34567 89";
    istringstream ss(s);
    string subs1; // 임시 변수
    vector<string> v;

    while (getline(ss, subs1, ' ')) {
        v.push_back(subs1); // v = {"012", "34567", "89"}
    }

    // 결과 출력
    for (const auto& str : v) {
        cout << str << " ";
    }

    return 0;
}

strtok 함수 사용

#include <iostream>
#include <cstring>

using namespace std;

int main() {
    char str[] = "a b c d e f";
    char* p_str = strtok(str, " ");
    
    while (p_str != NULL) {
        cout << p_str << ' ';
        p_str = strtok(NULL, " ");
    }

    return 0;
}

여러 구분자 사용하기

 
#include <iostream>
#include <cstring>

using namespace std;

int main() {
    char str2[] = "a-b,c:d e-f";
    char* p_str2 = strtok(str2, ",- :");

    while (p_str2 != NULL) {
        cout << p_str2 << ' ';
        p_str2 = strtok(NULL, ",- :");
    }

    return 0;
}

 

 

#문제 간단 정리

노골적으로 O(N+M) 가 걸리는 KMP 사용하라는 문제이다

 

 

#문제 해결 방법

https://dfdfg42.tistory.com/entry/KMP-C-%EC%BD%94%EB%93%9C

 

KMP C++ 코드

#include #include #include using namespace std;vector makePI(const string& pattern) { int m = pattern.size(); vector PI(m, 0); int k = 0; for (int i = 1; i 0 && pattern[k] != pattern[i]) k = PI[k - 1]; if (pattern[k] == pattern[i]) k++; PI[i] = k; } return

dfdfg42.tistory.com

 

KMP 코드를 활용하도록 하자

 

 

#전체 코드

 

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

using namespace std;

vector<int> makePI(const string& pattern) {
    int m = pattern.size();
    vector<int> PI(m, 0);
    int k = 0;

    for (int i = 1; i < m; i++) {
        while (k > 0 && pattern[k] != pattern[i])
            k = PI[k - 1];
        if (pattern[k] == pattern[i])
            k++;
        PI[i] = k;
    }

    return PI;
}

void KMP(const string& text, const string& pattern) {
    int n = text.size();
    int m = pattern.size();
    vector<int> PI = makePI(pattern);
    int k = 0;

    vector<int> result;

    for (int i = 0; i < n; i++) {
        while (k > 0 && pattern[k] != text[i])
            k = PI[k - 1];
        if (pattern[k] == text[i])
            k++;
        if (k == m) {
            result.push_back(i - m + 1);
            k = PI[k - 1];
        }
    }

    cout << result.size() << "\n";
    for (int idx : result) {
        cout << idx + 1 << " "; 
    }
    cout << "\n";
}

int main() {
    string text, pattern;

    getline(cin, text);
    getline(cin, pattern);

    KMP(text, pattern);

    return 0;
}

'[백준] > C++' 카테고리의 다른 글

백준 2312번 수 복원하기 [C++]  (0) 2024.06.26
백준 11721번 열 개씩 끊어 출력하기 [C++]  (0) 2024.06.19
백준 9742번 순열 [C++]  (0) 2024.06.18
백준 1275번 커피숍2 [C++]  (0) 2024.06.16
백준 27498번 연애 혁명 [C++]  (0) 2024.06.06
#include <iostream>
#include <vector>
#include <string>

using namespace std;

vector<int> makePI(const string& pattern) {
    int m = pattern.size();
    vector<int> PI(m, 0);
    int k = 0;

    for (int i = 1; i < m; i++) {
        while (k > 0 && pattern[k] != pattern[i])
            k = PI[k - 1];
        if (pattern[k] == pattern[i])
            k++;
        PI[i] = k;
    }

    return PI;
}

void KMP(const string& text, const string& pattern) {
    int n = text.size();
    int m = pattern.size();
    vector<int> PI = makePI(pattern);
    int k = 0;

    // 디버깅을 위한 출력
    cout << "텍스트: " << text << endl;
    cout << "패턴: " << pattern << endl;
    cout << "PI 배열: ";
    for (int val : PI) {
        cout << val << " ";
    }
    cout << endl;

    for (int i = 0; i < n; i++) {
        while (k > 0 && pattern[k] != text[i])
            k = PI[k - 1];
        if (pattern[k] == text[i])
            k++;
        if (k == m) {
            cout << "매칭 위치: " << i - m + 1 << endl;
            k = PI[k - 1];
        }
    }
}

int main() {
    string text, pattern;

    getline(cin, text);
    getline(cin, pattern);

    KMP(text, pattern);

    return 0;
}

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

플로이드 워셜 C++코드  (0) 2024.08.26
에라토스테네스의 체 C++ 코드  (0) 2024.06.26
정렬,머지소트,퀵소트 C++ 코드  (0) 2024.06.18
위상정렬 C++ 코드  (1) 2024.06.18
크루스칼, 프림 알고리즘 C++ 코드  (0) 2024.06.06

 

#정렬속도 비교

 

알고리즘 최선 수행시간 평균 수행시간 최악 수행시간 Run-time(정수 60,000개, 단위sec)
삽입 정렬 O(n) O(n2) O(n2) 7.438
선택 정렬 O(n2) O(n2) O(n2) 10.842
버블 정렬 O(n2) O(n2) O(n2) 22.894
카운팅 정렬 O(n2) O(n+k) O(n+k) .
Shell 정렬 O(n) O(n1.5) O(n2) 0.056
퀵 정렬 O(n log n) O(n log n) O(n2) 0.014
병합 정렬 O(n log n) O(n log n) O(n log n) 0.026
힙정렬 O(n log n) O(n log n) O(n log n) 0.034

 

#머지소트

#include <iostream>
#include <vector>

// 함수 선언
std::vector<int> merge_sort(const std::vector<int>& Data);
std::vector<int> merge(const std::vector<int>& left, const std::vector<int>& right);

// Merge Sort 함수 정의
std::vector<int> merge_sort(const std::vector<int>& Data) {
    if (Data.size() <= 1) {
        return Data;
    }

    int mid = Data.size() / 2;
    std::vector<int> left(Data.begin(), Data.begin() + mid);
    std::vector<int> right(Data.begin() + mid, Data.end());

    left = merge_sort(left);
    right = merge_sort(right);

    return merge(left, right);
}

// Merge 함수 정의
std::vector<int> merge(const std::vector<int>& left, const std::vector<int>& right) {
    std::vector<int> result;
    int i = 0, j = 0;

    while (i < left.size() && j < right.size()) {
        if (left[i] < right[j]) {
            result.push_back(left[i]);
            i++;
        } else {
            result.push_back(right[j]);
            j++;
        }
    }

    while (i < left.size()) {
        result.push_back(left[i]);
        i++;
    }

    while (j < right.size()) {
        result.push_back(right[j]);
        j++;
    }

    return result;
}

// 메인 함수 정의
int main() {
    std::vector<int> Data = {38, 27, 43, 3, 9, 82, 10};
    std::vector<int> sorted_Data = merge_sort(Data);

    for (int num : sorted_Data) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

 

#퀵소트

#include <iostream>
#include <vector>

std::vector<int> Data = {3, 2, 4, 6, 9, 1, 8, 7, 5};

void Quick_sort(int _from, int _to);
int partition(int left, int right);

void Quick_sort(int _from, int _to) {
    if (_from < _to) {
        int pivot = partition(_from, _to);
        Quick_sort(_from, pivot - 1);
        Quick_sort(pivot + 1, _to);
    }
}

int partition(int left, int right) {
    int where = left;
    int pivot = Data[where];
    while (left < right) {
        while (left < Data.size() && Data[left] <= pivot) {
            left++;
        }
        while (Data[right] > pivot) {
            right--;
        }
        if (left < right) {
            std::swap(Data[left], Data[right]);
        }
    }
    std::swap(Data[where], Data[right]);
    return right;
}

int main() {
    Quick_sort(0, Data.size() - 1);
    for (int num : Data) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
    return 0;
}

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

에라토스테네스의 체 C++ 코드  (0) 2024.06.26
KMP C++ 코드  (0) 2024.06.18
위상정렬 C++ 코드  (1) 2024.06.18
크루스칼, 프림 알고리즘 C++ 코드  (0) 2024.06.06
세그먼트 트리 C++ 코드  (0) 2024.06.06

https://www.acmicpc.net/problem/9742

 

 

#문제 간단 정리

dfs 등 완전탐색으로 

순열을 탐색해 

해당 순번의 순열을 출력하는 문제

 

 

 

#문제 해결 방법

우선 eof 를 확인하는 입출력에 주의 

https://dfdfg42.tistory.com/entry/%EB%B0%B1%EC%A4%80-C-eof-%EC%B2%98%EB%A6%AC-%EB%B0%A9%EB%B2%95

 

백준 C++ eof 처리 방법

https://www.acmicpc.net/problem/10951 cin.eof() 를 사용 주요 사항입력 시도 후에 확인 가능: cin.eof()는 입력 시도가 실패한 후에만 의미가 있습니다. 즉, cin >> variable과 같은 입력 시도가 먼저 이루어져야

dfdfg42.tistory.com

 

 

나는 dfs 로 

모든 문자열을 계속해서 만들었고

문자열을 하나 만들때마다 찾은 문자열의수 seq 를 증가시켜주고

seq로 그 문자열 몇번째 순서인지 기억하여

우리가 찾는순번의 문자열이면 ans로 저장을 했다.

 

만들어진 문자열의 총 개수가 찾는 인덱스 보다 클 때만 정답이 있으니

이때만 ans 출력해주면 된다.

 

 

그리고 dfs의 인덱스 처리에 주의해주고 

 

처음에는 모든 문자열을 저장하려고 했는데 

메모리 초과가 나서

그냥 그 순번의 문자열만 저장하도록 바꿨다.

 

#전체 코드

 

#include <iostream>
#include <vector>
#include <cstring>

using namespace std;

bool visited[10];
string s;
int totalLength;
int findSeq;
int seq;
string ans;


void dfs(int idx,string make) {
    if (idx == totalLength) {
        seq += 1;
        if (seq == findSeq) {
            ans = make;
        }

        return;
    }

    for (int i = 0; i <= totalLength; i++) {
        if (!visited[i]) {
            visited[i] = true;
            make += s[i];
            dfs(idx + 1, make);
            make.pop_back();
            visited[i] = false;
        }
    }

}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    while (1) {

        cin >> s >> findSeq;
        totalLength = s.length()-1;

        seq = 0;
        
        memset(visited, false, sizeof(visited));

        if (cin.eof()) {
            break;
        }


        dfs(-1, "");

        if (seq >= findSeq) {
            cout << s << " " << findSeq << " = " << ans << '\n';
        }
        else {
            cout << s << " " << findSeq << " = No permutation"  << '\n';
        }

    }

    
    return 0;
}

+ Recent posts