자료구조와 알고리듬 With Java/[프로그래머스] Algorithm

Part 07 정렬 (Sort)

계란💕 2022. 3. 24. 10:54

  Note) 

  - 선형 자료구조 -> list, array

  - 정렬: 순서를 규칙에 맞게 바꾸는 것

 

1. Bubble sort

  - 크기를 비교해서 작은 것을 왼쪽에 둔다.

  - index 0 을 inde = 1 ~ n-1 까지의 값과 비교한다. 

  - index 1 을 inde = 2 ~ n-1 까지의 값과 비교한다. ...

  - 시간 복잡도: O(n)

  - 가장 느리고 확실

<hide/>
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;

public class BubbleSort implements  Sortable {

	public <T> List<T> sort(List<T> list , Comparator<T> comparator){
		
		List<T> copy = new LinkedList<>(list);
		int size = copy.size();
		
		for(int i = 0; i < size; ++i) {
			
			for(int j = i + i; j < size; ++j) {
				
				T d1 = copy.get(i);
				T d2 = copy.get(j);
				
				if(comparator.compare(d1, d2) > 0) {
					copy.set(i, d2);
					copy.set(j, d1);
				}
			}
		}
		return copy;	
	}	
}

  - 인자로 전달되는 list로부터 복사본 리스트인 copy를 만들고 순서를 교체해서 정렬 수행

  - 제너릭 타입 T는 참조형

 

2. Insertion sort (삽입 정렬)

  - 대소 비교해서 삽입한다.

  - [ 3 1 5 2 4 ] 에 대해

    -> 첫번째 원소3은 그냥 두고 1부터 보자.

    -> 1을 기준으로 왼쪽의 3은 1보다 크다. => [ 1 3 5 2 4 ]

    -> 다음으로 5기준으로 왼쪽을 보면 1, 3 즉 5보다 작은 값만 있다. => [ 1 3 5 2 4 ]

    -> 2기준으로 보면, 3, 5는 2보다 크니까 두 칸 옮긴다. => [ 1 2 3 5 4 ]

    -> 4기준으로 비교하여 옮긴다. => [ 1 2 3 4 5 ]

 

  - 값을 하나씩 옮겨 간다.

  - 시간 복잡도: O(n)

  - Comparator 인터페이스는 Comparable 인터페이스와 같이 객체를 정렬하는 데 사용되는 인터페이스이다.

  - Method generic type은 클래스에 제네릭 타입을 선언하지 않고 각 method마다 제네릭 타입을 선언 가능

    -> <T> List<T>

 

<hide/>

import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;

public class InsertSort implements Sortable {

	@Override
	public <T> List<T> sort(List<T> list, Comparator<T> comparator){
		
		List<T> copy = new LinkedList<>(list);
		int size = copy.size();
		
		for(int i = 0 ; i < size ; ++i){
			T d1 = copy.get(i);
			
			for(int j = i - 1; j >= 0; --j) {
				T d2 = copy.get(j);
				
				if(comparator.compare(d1, d2) > 0 ) break;
				copy.remove(j);			// 기존 아이템을 제거해서 
				copy.add(j + 1, d2);	// 특정 위치에 삽입한다.
			}
		}
		return copy;
	}
}

 

3. Selection sort

  - 전체 데이터의 최솟값을  찾아가면서 가장 왼쪽에 옮기는 방식

  - [ 3 1 5 2 4 ]의 최솟값: 1을 셀렉트해서 가장 왼쪽으로

    => [ 1 3 5 2 4 ]

  - 뒤에 네 개중에서 최솟값은 2

    => [ 1 2 3 5 4 ]

  - 뒤에 세 개중에서 최솟값은 3  

    => [ 1 2 3 5 4 ]

  - 뒤에 두 개중에서 최솟값은 4 

    => [ 1 2 3 4 5 ]

  - 범위가 하나니까 결국 끝

    => [ 1 2 3 4 5 ]

 

  - 시간 복잡도: O(n^2)

<hide/>
package D220324;

import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;

public class SelectionSort implements Sortable {

	public <T> List<T> sort(List<T> list, Comparator<T> comparator){
		
		List<T> copy = new LinkedList<>(list);
		int size = copy.size();
		
		for(int i = 0;i < size; ++i) {
			
			int minIndex = 1;
			T min = copy.get(i);
			
			for(int j = i + 1; j < size; ++j) {

				T d = copy.get(j);
				
				if(min == null || comparator.compare(min, d) > 0 ) {
					
					minIndex = j;
					min = d;		// 첫번째 요소를 min으로 먼저 지정하고 나머지 요소 중 min을 업데이트
				}
			}
			copy.remove(minIndex);
			copy.add(i, min);
		}
		return copy;		
	}
}

 

4. Quick sort 

  - 작은 것들, 큰 것들끼리 나눠서 합친다.

  - 시간 복잡도: best O(nlogn) / worst O(n^2) - pivot값에 따라 달라진다.

  - 아무거나 pivot값으로 정한다.

  - pivot을 기준으로 큰 값/ 작은 값으로 나눈다.

  - 나눠진 그룹 안에서 새로운 피봇을 정하고 큰 값 / 작은 값으로 다시 나눈다.

  - 이론적으로 가장 빠르다.

 

  - [3 1 2 4 5] 에 대해 피봇을 5라고 하자.

    -> (3 1 2 4) 에서 임의 피봇 2라고 한다.

    -> ( 1 ) ( 3  4  ) 두 그룹으로 나뉜다.

    -> 왼쪽 그룹에 1밖에 없으니 1 확정 

    -> 오른쪽 그룹에 피봇 정하고서 작은값 (3), 큰 값(4)로 나뉜다.

    -> 작은 그룹 / 피봇/ 큰 그룹 모두 합쳐서 집어 넣는다.

<hide/>
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Random;

class MyData implements Comparable<MyData>{
	int v;
	
	public MyData(int v) {
		this.v = v;
	}
	
	public String toString() {
		return "" + v;
	}
	
	public int compareTo(MyData o) {
		return Integer.compare(v, o.v);
	}
	
	 @Override
	public boolean equals(Object o) {
		if(this == o)	return true;
		if(o == null || getClass() != o.getClass())	return false;
		MyData mydata = (MyData) o;
		return v== mydata.v;
	}
	 
	 @Override
	public int hashCode() {
		return Objects.hash(v);		
			// 같은 value가 들어가면 같은 해시값이 나온다.
			// 키로 사용되는 것은 해시코드를 호출해서 나온 해시값을 이용해서 인덱스로 이용한다.
	}
}

public class QuickSort {
		public static void main(String[] args) {
			
			List<MyData> list = new LinkedList<>();
			Random r = new Random();
			for(int i = 0; i < 20; ++i) 	list.add(new MyData(r.nextInt(50)));
			
			System.out.println(list);
			list = quickSort(list);
			System.out.println(list);
			
		}
		
		static List<MyData> quickSort(List<MyData> list){
			
			if(list.size() <= 1) return list;
			MyData pivot = list.remove(0);	// 맨 앞의 값을 임의로 피봇 설정
			List<MyData> lesser = new LinkedList<>();
			List<MyData> greater = new LinkedList<>();
			
			for(MyData m : list) {
				if(pivot.compareTo(m) > 0 ) lesser.add(m);
				else greater.add(m);
			}
			
			List<MyData> merged = new LinkedList<MyData>();
			merged.addAll(quickSort(lesser));
			merged.add(pivot);
			merged.addAll(quickSort(greater));
				
			return merged;
		}
}

 

<hide/>


import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class QuickSort implements Sortable {
	
	@Override
	public <T> List<T> sort(List<T> list, Comparator<T> comparator) {
		List<T> copy = new LinkedList<>(list);
		return quickSort(copy, comparator);
	}
	
	private <T> List<T> quickSort(List<T> list, Comparator<T> comparator) {
		
		if (list.size() <= 1) return list;
		T pivot = list.remove(list.size() / 2);
		List<T> lesser = quickSort(sublist(list, (d) -> comparator.compare(pivot, d) > 0), comparator);
		List<T> greater = quickSort(sublist(list, (d) -> comparator.compare(pivot, d) <= 0), comparator);
	
	return new LinkedList<T>() {{
		addAll(lesser);
		add(pivot);
		addAll(greater);
	}};
	}
	
	private <T> List<T> sublist(List<T> list, Predicate<T> filter) {
			return list.stream().filter(filter).collect(Collectors.toList());
	}
}

  - 재귀를 위해 내부에 quickSort메서드와 특정 조건에 맞는 값으로만 구성되는 부분리스트를 만드는 sublist 메서드를 

  - 별도로 만들어 활용한다.

  - pivot 값을 리스트의 중값위치 값으로 설정한다.

  - merge된 리스트를 반환하기 위해 double brace initialization을 사용한다. 

 

 

5. Merge sort (병합 정렬)

  - 시간 복잡도: O(nlogn)

  - 일단 나눴다가 작은 것부터 합친다.

  - [3 1 2 4 5]

    -> [3 1 ] [2 4 5]

    -> [3] [1]  [2] [4 5] 

    -> [3] [1] [2] [4] [5] ... 하나씩 쪼개질 때까지 나눈다.

     -> 그 다음에 대소 비교하면서  합친다.

    -> [1] [3] [2] [ 4 5 ]

    -> [ 1 3 ]  [ 2 4 5 ]

    -> 두 그룹의 가장 작은 값인 1, 2를 비교해서 1, 2를 차례로 넣는다. [1 2  ...] => [ 1 2 3 4 5 ]

<hide/>
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
public class MergeSort implements Sortable {
	public <T> List<T> sort(List<T> list, Comparator<T> comparator){
		
		List<T> copy = new LinkedList<>(list);		
		return mergeSort(copy, comparator);
	}
    
	private <T> List<T> mergeSort(List<T> list, Comparator<T> comparator){
		
		if(list.size() <= 1) return list; 
		
        int mid = list.size() / 2;
		List<T> left = sort(list.subList(0, mid) , comparator);	// list.subList를 통해 인덱스에 따른 sublist사용
		List<T> right = sort(list.subList(mid, list.size()) , comparator);
		
		List<T> merged = new LinkedList<T>();
		
		while(!left.isEmpty() || !right.isEmpty()) {	// left, right 중 하나라도 원소있으면 실행
		
			if(left.isEmpty()) {
				merged.addAll(right);
				break;
			}
			if(right.isEmpty()) {
				merged.addAll(left);
				break;
			}
			T leftFirst = left.get(0);
			T rightFirst = right.get(0);
			
			if(comparator.compare(leftFirst, rightFirst) < 0) {
				merged.add(left.remove(0));
			}else {
				merged.add(right.remove(0));		
			}
		}
		return merged;
	}
}

  - sublist는 사이즈가 고정되어 있기 때문에 메서드 sort를 통해서 재귀시킴으로써 copy를 만든다.

 

  

  Note) comparator란?

    -> 인터페이스이며 compare를 구현해야한다.

    -> 인터페이스이기 때문에 new 불가능

 

  Note) toString

  - ( "" +  값 ) 보다는 String.valueOf() 를 권장한다. 

 

  Note) compareTo

  - (v -o.v)보다는 Integer.compare(v, o.v)를 권장. 

 

  Ex) 제일 작은 수 제거하기

    - 정수를 저장한 배열에서 가장 작은 수를 제거한 배열을 리턴하는 함수 만들기

<hide/>
import java.util.*;
class Solution {
    public int[] solution(int[] arr) {
        
        if(arr.length == 1) return new int[] {-1};    	  // 원소가 1개이면 -1을 반환한다.
      
        int min = Arrays.stream(arr).min().getAsInt(); 	 // 최솟값을 찾는다.
        
        return Arrays.stream(arr).filter(a -> a != min).toArray();   // 최솟값만 빼고 반환한다.
    }
}
<hide/>
import java.util.*;
class Solution {
    public int[] solution(int[] arr) {    
        if(arr.length == 1) return new int[] {-1};   
        
        int min = Integer.MAX_VALUE;
        for(int n: arr) if(n < min) min = n;
        
        int [] answer = new int[arr.length - 1];    
        int index = 0;
        
        for(int n : arr){
            if(n == min) continue;
            answer[index++] = n;
        }
        return answer;
    }
}

 

  Ex) 문자열 내마음대로 정렬하기

  - 각각의 문자열에 대해 주어진 인덱스의 문자를 사전순으로 비교하여 나열한다.

<hide/>
import java.util.*;
class Solution {
    public String[] solution(String[] strings, int n) {
        Arrays.sort(strings,  (s1, s2) -> {				// n번째를 기준으로 정렬하기 위해 comparator
       
            if(s1.charAt(n) == s2.charAt(n)) return s1.compareTo(s2);		
            	// n번째 문자가 같은 경우,  s1, s2를 사전순으로 정렬
            
            return s1.charAt(n) - s2.charAt(n);		
            	// n번째 문자가 각각 다른 경우, s1과 s2의 각각 n번째 글자를 기준으로 정렬한다. 
        });
        return strings;
    }
}

 

  Ex) JadenCase 문자열 만들기

  - JadenCase: 모든 단어의 첫 글자는 항상 대문자 (공백 다음도 대문자)

  - 글자수만큼 스트링이 생성되는 것은 비효율적이므로 스트링 빌더, 스트링 버퍼를 이용하는게 더 좋다.

  - append를 이용해 스트링 빌더에 누적해서 가지고 있다가 마지막에 toString으로 문자열 객체를 하나 만든다.

 

  Sol 1)

<hide/>
class Solution {
    public String solution(String s) {
        String answer = "";
        String s2 = s.toLowerCase();
        char last = ' ';			// 첫글자를 대문자로 바꾸기 위해 last의 초깃값 ' '으로 설정
        
        for(char c : s2.toCharArray() ){
            
            if(last == ' ') c = Character.toUpperCase(c);		// 공백의 다음 글자를 대문자로 바꾼다.
            answer += c; 		// 결괏값 
            last = c;			// 마지막 글자 
        }
        return answer;
    }
}

 

  Sol 2) 더 효율적인 방법

<hide/>
class Solution {
    public String solution(String s) {
    	StringBuilder sb = new StringBuilder();
        String s2 = s.toLowerCase();
        char last = ' ';
        
        for(char c : s2.toCharArray()){
            if(last == ' ') c = Character.toUpperCase(c);
            sb.append(c);
            last = c;
        }
        return sb.toString();
    }
}

 

  - 1번의 경우 String을 이용하면 연산을 통해 새로운 문자열로 변경될 때마다 새로운 스트링 객체를 반환한다.

  - 불필요한 스트링 객체가 연속 발생하며 프로그램의 성능이 저하될 수 있다.

    -> StringBuilder클래스를 통해 새로운 객체를 만들지 않고 새롭게 문자 변경 작업하는 하는게 더 좋다.

 

  Ex) H-index

  - 먼저 sort를 이용해서 정렬한다.

  - 그 다음 배열의 길이에서 인덱스를 빼준 값을 H에 저장한다. (H = citations.length - i)

    -> H는 i번째 논문 보다 더 많이 이용된 논문들의 개수를 의미한다.

  - citations[i]의 값이 H보다 크면 H-index 를 리턴하도록한다.

<hide/>

import java.util.*;

// 프로그래머스 lv 2 H-idx
//https://school.programmers.co.kr/learn/courses/30/lessons/42747

public class Test1 {

    public static int solution(int[] citations) {

        int answer = 0;
        System.out.println();
        Arrays.sort(citations); //  [0 1 3 5 6 7 9]
        System.out.println(Arrays.toString(citations));

        for(int i = 0;i < citations.length; ++i){ // 1번 이상: 7개 , ... ,3번 이상 5개(length - i)  5번 이상 4개, 6번 이상 3개

            int H = citations.length - i;   // i번째 논문보다 많이 인용된 논문의 개수 카운트
            if( citations[i] >=  H){
                System.out.printf("citation[%d] = %d >= %d %n", i, citations[i], H);
                return H;
            }
        }
        return  0;
    }

    public static void main(String[] args){

        int[] arr1 = {3, 0, 6, 1, 5};
        int[] arr2 = {3, 0, 6, 1, 5, 7, 2};     //  0 1 2 3 5 6  7
        int[] arr3 = {3, 0, 6, 1, 5, 2, 4, 7, 8};   // 0 1 2 3 4 5 6 7 8
        int[] arr4 = {3, 0, 6, 1, 5, 2, 4, 7, 8, 10, 11, 12, 13};   // 0 1 2 3 4 5 6 7 8 10 11 12 13

        System.out.println(solution(arr1)); // 3
        System.out.println(solution(arr2)); // 3
        System.out.println(solution(arr3)); // 4
        System.out.println(solution(arr4)); // 6
    }
}

 

  Note) 입출력 예시

 

 

 

 

출처: https://programmers.co.kr/learn/courses/13577

'자료구조와 알고리듬 With Java > [프로그래머스] Algorithm' 카테고리의 다른 글

Part 09 Tree  (0) 2022.03.28
Part 08 Graph (그래프)  (0) 2022.03.24
Part 06 선형탐색 (Linear Search)  (0) 2022.03.23
Part 05 Stack과 Queue  (0) 2022.03.23
Part 04 집합 (Set)  (0) 2022.03.22