Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sorting II #7

Closed
qwerty3345 opened this issue Sep 13, 2024 · 4 comments
Closed

Sorting II #7

qwerty3345 opened this issue Sep 13, 2024 · 4 comments
Assignees

Comments

@qwerty3345
Copy link
Member

qwerty3345 commented Sep 13, 2024

9/20

@qwerty3345 qwerty3345 changed the title Sorting | Sorting II Sep 13, 2024
@MUKER-WON
Copy link
Member

MUKER-WON commented Sep 20, 2024

정렬 (Sorting)

계수 정렬 (Counting Sort)

  • 주어진 범위 내에서 숫자들의 빈도를 세서 정렬하는 알고리즘
  • 시간 복잡도: O(N + K)
  • 메모리에 count를 저장하기 때문에 수의 범위가 한정적일 때에만 counting sort를 사용할 수 있다.

기수 정렬 (Radix Sort)

  • 각 자릿수를 기준으로 숫자를 정렬해 나가는 방식, 계수 정렬을 응용한 알고리즘
  • LSD와 MSD 두 가지 방식이 있다. 흔히 쓰이는 방식은 LSD
    • LSD(Least Significant Digit)
      • 가장 낮은 자릿수부터 시작하여 차례대로 정렬
    • MSD(Most Significant Digit)
      • 가장 높은 자릿수부터 시작하여 차례대로 정렬
  • 시간 복잡도: O(d * (N + K))
    • N: 입력 배열의 원소 개수
    • K: 각 자릿수의 값의 범위
    • d: 숫자의 자릿수
  • 공간 낭비가 심한 정렬 알고리즘이라 코딩테스트에서 사용할일이 없다.

Comparison Sort와 Non-comparison Sort

  Comparison Sort Non-comparison Sort
정의 비교 기반 정렬. 두 원소를 비교하여 어느 쪽이 더 큰지, 작은지를 판단하면서 정렬을 진행하는 알고리즘. 각 정렬과정에서 숫자 간의 비교 연산이 필수적 비교 기반이 아닌 정렬. 원소 간의 직접적인 비교 없이 정렬을 수행하는 알고리즘. 특정 조건을 기반으로 숫자들을 분류하거나 위치를 결정하는 방식으로 정렬
예시 알고리즘 Bubble Sort, Merge Sort, Quick Sort Counting Sort, Radix Sort
장점 어떤 데이터에도 적용 가능하며, 안정적인 정렬 알고리즘이 많다. 특정 상황에서는 Comparison Sort보다 매우 빠른 성능을 보여줌
단점 시간 복잡도에 O(NlogN)의 한계가 있음 메모리 데이터의 제약이 큼.

문제 풀이

백준 15688번: 수 정렬하기 5 (계수 정렬로 풀기)

  • 최대한 index를 헛돌게 하지 않기 위해 최소,최대 index값을 기억해 루프를 실행하고, print문을 최소로 출력하기 위해 문자열 변수에 출력값을 모두 모아 한번에 출력하게끔 해도.. 시간초과가 발생했다.
  • 역시 이유는 Swift의 느린 입력 때문이었는데 readLine()을 좀 더 빠르게 받아올 수 있는 방법을 라이노님이 만들어 놓은게 있어서 해당 클래스를 사용했다.
import Foundation

let file = FileIO()
let N = file.readInt()
var arr = Array(repeating: 0, count: 2000001)
var (minN,maxN) = (0,2000000)
var ans = ""

(0..<N).forEach { _ in
    let num = file.readInt()
    minN = min(minN, num+1000000)
    maxN = max(maxN, num+1000000)
    arr[num+1000000] += 1
}

(minN...maxN).forEach { n in
    guard arr[n] > 0 else { return }
    ans += String(repeating: "\(n-1000000)\n", count: arr[n])
}

print(ans)

// 라이노님의 빠른 입력처리
final class FileIO {
    private var buffer:[UInt8]
    private var index: Int
    
    init(fileHandle: FileHandle = FileHandle.standardInput) {
        buffer = Array(fileHandle.readDataToEndOfFile())+[UInt8(0)]
        index = 0
    }
    
    @inline(__always) private func read() -> UInt8 {
        defer { index += 1 }
        return buffer.withUnsafeBufferPointer { $0[index] }
    }
    
    @inline(__always) func readInt() -> Int {
        var sum = 0
        var now = read()
        var isPositive = true
        
        while now == 10
                || now == 32 { now = read() }
        if now == 45{ isPositive.toggle(); now = read() }
        while now >= 48, now <= 57 {
            sum = sum * 10 + Int(now-48)
            now = read()
        }
        return sum * (isPositive ? 1:-1)
    }
    
    @inline(__always) func readString() -> String {
        var str = ""
        var now = read()
        
        while now == 10 || now == 32 { now = read() }
        while now != 10 && now != 32 && now != 0 {
            str += String(bytes: [now], encoding: .ascii)!
            now = read()
        }
        return str
    }
}

백준 2910번: 빈도 정렬

_ = readLine()
var dic = [Int: (Int,Int)]()
var ans = ""

readLine()!.split { $0 == " " }.enumerated().forEach {
    let n = Int(String($0.element))!
    dic[n, default: ($0.offset, 0)].1 += 1
}

print(dic.sorted {
    $0.value.1 != $1.value.1 ? $0.value.1 > $1.value.1 : $0.value.0 < $1.value.0
}.map {
    String(repeating: "\($0.key) ", count: $0.value.1)
}.joined())

@shippingpark
Copy link
Collaborator

#Counting Sort
표를 보면 정렬 가능
1이 2개니까 2개 쓰고, 2가 1개니까 1개 쓰고, 줄줄이 쓰면 정렬되는 형태
정렬 알고리즘 중에 가장 쉬운 형태
Couting Sort는 만능이 아님. 각 수의 등장 횟수를 저장해야 하는데, 큰 테이블을 만들어두고 수에 해당하는 원소값을 1증가시키는 방법이 있음
만약 수의 범위가 너무너무 크다면, 크기가 10억인 배열이 필요하고, 메모리 제한이 512메가바이트여도 int 기준 1.2억인 배열밖에 잡을 수 없으니...
즉, 수의 범위가 한정적일 때에만 카운팅 소트를 쓸 수 있음.

수의 범위가 1000만 이하 일 때는 카운팅 소트를 쓰고, 아니라면 쓰지 않는 것이 좋음

15688번 수 정렬하기 (시간초과)

절댓값인 경우 범위를 2배로 봐야한다는 것을 잊지 말 것.

import Foundation

let N = Int(readLine()!)!
var arr = Array(repeating: 0, count: 2000001)
for _ in 0..<N {
  let input =  Int(readLine()!)!
  arr[1000000+input] += 1
}

for idx in arr.indices {
  guard arr[idx] != 0 else { continue }
  var count = arr[idx]
  let num = idx-1000000
  while count != 0 {
    print(num)
    count -= 1
  }
}

수의 범위가 제한되어 있을 떄에는 couting Sort 가 간단해서 활용할 여지가 있다
하지만 수의 범위가 크면 사용할 수 없다

Radix Sort (라딕스 소트 / 기수정렬)

자릿수를 이용해서 정렬을 수행하는 알고리즘으로, Couting Sort 응용에 해당한다.

1의 자릿수에 맞게 배열에 수를 넣어준다.
0번 리스트부터 보면서 수를 꺼내서 수열에 재배열한다

처음에는 1의 자리수가 같은 친구들끼리는 초반의 순서가 유지된다.

왜 421보다 502가 클까?

100의 자리를 비교하고, 10의 자리를 비교하고 ,1의 자리를 비교하고
즉 100이 더 큰 영향력을 행사함.

상대적 위치가 유지된 채로 정렬이 수행됨

자릿수의 최대갯수가 D라고 할 때, D번에 걸쳐서 Counting Sort 를 하는 것과 같다
리스트의 갯수를 K개라고 할 때, 시간 복잡도는 O(D(N+K))이다
다만, 리스트의 개수K는 원소의 개수 N은에 비해 무시가 가능할 정도로 작기 때문에 대게 시간복잡도를 O(DN))

라딕스 소트의 맹점

한 개의 리스트에 N개의 원소를 전부 넣어야 할 수도 있으니, N칸의 배열로 만들어야 하는데,
너무 공간의 낭비가 심하다.

공간의 낭비를 해결하려면 연결리스트나 동적 배열을 사용해야 하는데, STL을 사용하지 못하면 구현이 까다롭다
코딩 테스트에서 특히나 라딕스 소트를 구현해야 할 상황이 거의 없음

예제

n은 원소의 수
arr는 정렬을 하고 싶은 원소들의 목록
d는 자릿수의 최댓값
p10은 10의 지수를 저장할 배열

p10[i]에 10^i 승을 넣고 싶다면

p10[0] - 1;
for(int i = 1; i < d; i++) p10[i] = p[i-1] * 10

방식으로 해야 한다

p10[i] = pow(10, i)
와 같은 방식으로 pow 함수를 쓰게 되면 pow가 실수형이기 때문에 실수형 때문에 꼬일 수 있다는 점 유의하기
(아 이거 C++ 이라서 이런 말 해주신 거군요 )

Comparison Sort, Non-comparison Sort

버블머지퀵소트와 지금의 카운팅 래딕스 소트의 차이?

저번에 학습한 정렬들은 원소들 끼리 크기를 비교하는 과정이 있었는데,
Couting과 Radix 소트는 원소끼리 크기를 비교하지 않음

STL Sort

코딩 테스트에서는 STL 의 Sort 함수를 써서 진행
STL Sort 는 퀵소트지만, 깊이가 깊어지면 nlogN을 보장하는 알고리즘으로 교체되는 형태
하지만 stable Sort가 아니라서 상대적인 순서는 보존되지 않을 수 있음

잘 모르겠다. 어떻게 쓰는 거지?

정렬의 응용

정렬을 이용해서 시간복잡도를 개선할 수 있다 !

@qwerty3345
Copy link
Member Author

qwerty3345 commented Sep 20, 2024

BOJ 2910 빈도 정렬

import Foundation

let input = readLine()!.components(separatedBy: " ").compactMap { Int($0) }
let (N, C) = (input[0], input[1])
let numbers = readLine()!.components(separatedBy: " ").compactMap { Int($0) }

var numberCount: [Int: Int] = [:]
for i in 0..<N {
    numberCount[numbers[i], default: 0] += 1
}

let sorted = numberCount.sorted {
    if $0.value == $1.value {
        return numbers.firstIndex(of: $0.key)! < numbers.firstIndex(of: $1.key)!
    }

    return $0.value > $1.value
}

let result = sorted.reduce(into: []) {
    $0 += Array(repeating: $1.key, count: $1.value)
}
.map(String.init)
.joined(separator: " ")

print(result)
  • 리팩터링 by 클로드
    • 인덱스 저장을 통한 시간 복잡도 35% 단축
import Foundation

// 입력 처리
let input = readLine()!.split(separator: " ").compactMap { Int($0) }
let (N, _) = (input[0], input[1])
let numbers = readLine()!.split(separator: " ").compactMap { Int($0) }

// 빈도 계산 및 정렬
var frequencyMap = [Int: (count: Int, firstIndex: Int)]()
for (index, number) in numbers.enumerated() {
    if let (count, firstIndex) = frequencyMap[number] {
        frequencyMap[number] = (count + 1, firstIndex)
    } else {
        frequencyMap[number] = (1, index)
    }
}

let sortedNumbers = frequencyMap.sorted { (a, b) in
    if a.value.count == b.value.count {
        return a.value.firstIndex < b.value.firstIndex
    }
    return a.value.count > b.value.count
}.flatMap { Array(repeating: $0.key, count: $0.value.count) }

// 결과 출력
print(sortedNumbers.map(String.init).joined(separator: " "))

@stealmh
Copy link
Collaborator

stealmh commented Sep 20, 2024

문제풀이

BOJ 2910 빈도정렬

코드
/*
 1. 많이 나온 수는 맨 앞에 배치
 2. 동일하게 나온 수는 맨 앞에 나온 것을 기준삼음
 
 */

struct Solution {
    var total: Int
    var index: Int
}


let input = readLine()!.split(separator: " ").map { Int($0)! }
let n = input[0],
    c = input[1]

let message_array = readLine()!.split(separator: " ").map { Int($0)! }
var checkBox: [Int: Solution] = [:]


// 1.
for i in 0..<message_array.count {
    if let _ = checkBox[message_array[i]] {
        checkBox[message_array[i]]!.total += 1
        checkBox[message_array[i]]!.index = min(checkBox[message_array[i]]!.index, i)
    } else {
        checkBox[message_array[i]] = Solution(total: 1, index: i)
    }
}
// 2.
let sortedArray = checkBox.sorted { (lhs, rhs) -> Bool in
    if lhs.value.total == rhs.value.total {
        return lhs.value.index < rhs.value.index
    }
    return lhs.value.total > rhs.value.total
}

// 3.
var answer: [String] = []

for (key, value) in sortedArray {
    for i in 0..<value.total {
        answer.append(String(key))
    }
}

print(answer.joined(separator: " "))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants