반응형

* 창발(Energence): 지속적인 관찰을 통해 특정한 패턴을 발견하는 것

 

1 객체지향

: 1980년대 부터 존재했던 소프트웨어 개발 방법론으로 큰 프로젝트 개발과 유지보수를 보다 쉽게 하기 위해 도입
e.g., 스몰토크, C++, JAVA, PHP

 

2 디자인 패턴(Design Pattern)

: 객체지향 구현 문제를 해결하기 위해 도입됐으며, 해결 방법을 재사용하는 것으로 객체지향 개발에서 디자인 패턴이 해결하는 주요 문제는 객체 간 관계와 소통 처리

 

3 SOLID 설계 원칙

: 유지보수성과 확장성을 극대화하기 위한 객체지향 설계 지침으로, C++에서는 다형성(Polymorphism), 추상 클래스(Abstract Class), 인터페이스 설계 등을 활용해 구현

* 다형성(Polymorphism)

더보기

같은 Interface(기본 Class)를 통해 다양한 구현을 사용하며, 기본 Class에 대한 의존성을 유지하면서도 구현을 독립적으로 관리 가능

 

3.1 SRP(Single Responsibility Principle, 단일 책임의 원칙)

: 하나의 클래스는 하나의 기능(책임)만 담당하며, 변경 이유도 하나여야 함

 

ERROR CASE
: 하나의 Class가 계산, 출력, 저장 등 여러 책임을 가짐

class Invoice{
public:
	void calculateTotal(){
    	// 계산 로직
    }
    
    void printInvoice(){
    	// 출력 로직
    }
    
    void saveToDatabase(){
    	// 저장 로직
    }
};

 

GOOD CASE

: 각각의 책임을 별도의 Class로 분리

class InvoiceCalculator{
public:
	void calculateTotal(){
    	// 계산 로직
    }
};

class InvoicePrinter{
public:
	void printInvoice(){
    	// 출력 로직
    }
};

class InvoiceRepository{
public:
	void saveToDatabase(){
    	// 저장 로직
    }
};

 

3.2 OCP(Open/Close Principle, 개방 폐쇄 원칙)

: 코드는 확장에 열려 있고 수정에는 닫혀있어야 하며, 새로운 기능을 추가할 때 기존 코드를 수정하지 않고 확장할 수 있도록 설계

 

ERROR CASE

: 도형의 종류가 늘어날 때 마다 calculateArea method 수정 필요

class Shape{
public:
	std::string type;
};

class AreaCalculator{
public:
	double calculateArea(const Shape& shape){
    	if(shape.type == "circle"){
        	// 원의 면적 계산
        } else if(shape.type == "rectangle"){
        	// 사각형의 면적 계산
        }
        return 0;
    }
};

 

GOOD CASE

: 다형성을 활용해 새로운 도형을 추가할 때 기존 코드를 수정하지 않음

class Shape{
public:
	virtual double calculateArea() const = 0; // 순수 가상함수
	virtual ~Shape() = default;
};

class Circle: public Shape{
public:
	double radius;
	Circle(double r): radius(r){}
	double calculateArea() const override{
		return 3.14 * radius * radius;
    }
};

class Rectangle: public Shape{
public:
	double width, height;
	Rectangle(double w, double h): width(w), height(h){}
	double calculateArea()const override{
		return width * height;
    }
};

void printArea(const Shape& shape){
	std::cout << "Area: " << shape.calculateArea() << std::endl;
}
더보기

-. virtual

: 해당 함수가 가상함수(Virtual Function)임을 의미하며, 기본 Class(Base Class)에서 선언된 이 함수는 파생 Class(Derived Class)에서 재정의 할 수 있음.

동일한 기본 Class를 상속 받더라도 파생 Class 마다 가상함수의 구현부를 다르게 정의할 수 있으며, 이를 통해 다형성 실현

 

-. virtual double calculateArea() const = 0;

: 함수의 구현부가 없음을 명시하며, 이렇게 선언된 함수는 순수 가상함수(Pure Virtual Function)로 선언되어 반드시 파생 Class에서 구현되어야 함

 

-. 순수 가상함수(Pure Virtual Function)

: 기본 Class에서는 Interface만 제공하고 실제 구현은 파생 Class에서 정의하도록 강제함.

이 경우 기본 Class는 추상 Class(Abstract Class)로 변환되며, 추상 Class는 Interface화 할 수 없음

 

-. virtual ~Shape() = default;

: 소멸자(Destructor)로 객체가 삭제되거나 수명이 끝날 때 호출되는 Class의 특별한 member function으로, Class의 자원을 정리하거나 Memory 해제를 위해 사용

소멸자는 함수의 일정이므로 함수 정의처럼 이름 뒤에 ( )를 사용하고, 매개변수를 가질 수 없으므로 항상 빈 괄호로 선언

 

-. override

: 해당 함수가 순수 가상함수인지 여부와 상관 없이 부모 Class에서 선언된 가상함수를 재정의한다는 것을 명시적으로 나타냄

부모 Class에서 virtual로 선언된 함수만 override keyword로 재정의 가능하며, override keyword를 사용하지 않아도 재정의는 가능하나, override를 사용하면 Compiler가 재정의 여부를 확인하고 함수명이나 Signature가 다르면 Compile Error 발생

 

[ 참고 ] 상속과 선언의 상관 관계

더보기

-. 부모 Class에서 virtual로 선언된 함수: 자식 Class에서 선택적으로 재정의 가능하며, 재정의하지 않는 경우 부모 Class의 구현 그대로 사용

-. 부모 Class에서 순수가상함수로 선언된 함수: 자식 Class에서 반드시 선언 및 구현이 필요하며 자식 Class가 이를 구현하지 않으면 자식 Class도 추상 Class가 되며 Instance 화 불가능

class Work{
public:
	virtual void work1(){ // 기본 구현 제공
    	std::cout << "Base work!" << std::endl;
    }
    virtual void work2() = 0;
};

class Human: public Work{
	// work1()을 선언 및 구현하지 않아도 Error 없음
    // work2()는 선언해주지 않으면 compile error 발생
    void work2() override{
    	std::cout << "I am working!" << std::endl;
    }
};

int main(){
	Human h;
    h.work1(); // 부모 Class의 work() 호출: "Base work!"
    h.work2(); // "I am working!"
    return 0;
}

 

3.3 LSP(Liskov Substitution Principle, 리스코프 치환 원칙)

: 자식 Class는 부모 Class의 역할을 대체할 수 있어야 하며, 부모 Class의 동작을 깨뜨리거나 위반하지 않는 설계 필요

 

ERROR CASE

: Penguin Class가 Bird의 역할을 대체하지 못함

class Bird{
public:
	virtual void fly(){
		std::cout << "I can fly!" << std::endl;
	}
};

class Penguin: public Bird{
public:
	void fly() override{
		throw std::logic_error("Penguin can't fly!");
	}
};

 

GOOD CASE

: Bird와 FlyingBird로 역할 분리

class Bird{
public:
	virtual void eat(){
    	std::cout << "I can eat!" << std::endl;
    }
    virtual ~Bird() = default; // 소멸자
};

class FlyingBird: public Bird{
public:
	virtual void fly(){
    	std::cout << "I can fly!" << std::endl;
    }
};

class Penguin: public Bird(){
public:
	void eat() override{
    	std::cout << "I am a penguin and I eat fish!" << std::endl;
    }
};

class Sparrow: public FlyingBird{
public:
	void fly() override{
    	std::cout << "I am a sparrow and I can fly!" << std::endl;
    }
};

[ 참고 ] 소멸자(Destructor)

더보기

객체가 삭제되거나 수명이 끝날 때 호출되는 Class의 특별한 member 함수로, Class의 자원을 정리하거나 memory 해제를 위해 사용

소멸자는 함수의 일종이므로 함수 정의처럼 이름 뒤에 ( )를 사용하고 매개변수를 가질 수 없으므로 항상 빈 괄호로 선언

 

가상 소멸자(Virtual Destructor)

: Class가 상속될 때 자식 Class에서 자원을 적절히 해제하도록 설계된 소멸자로, 부모 Class의 소멸자를 virtual로 선언하면 부모 Class를 통해 파생 Class 객체를 삭제할 때 파생 Class의 소멸자도 호출

소멸자를 virtual로 선언하면 상속 받는 Class에서 동적 할당된 객체가 안전하게 삭제되며, 부모 Class의 소멸자가 가상함수가 아니면 부모 Class의 pointer로 파생 Class 객체를 삭제할 때 부모 Class의 소멸자만 호출하고 파생 Class의 소멸자는 호출되지 않아 memory 누수 발생 가능

→ default를 사용해 compiler에게 기본 소멸자를 생성하도록 지시해 개발자가 특별히 소멸자의 구현을 작성하지 않아도 기본 동작(자원 해제)을 자동으로 수행

 

3.4 ISP(Interface Segregation Principle, 인터페이스 분리의 원칙)

: Client는 자신이 사용하지 않는 Method에 의존하지 않아야 하며, 큰 Interface를 작고 구체적인 Interface로 분리

 

ERROR CASE

: 로봇은 eat() Method가 필요하지 않지만 Worker Interface 때문에 구현해야 함

class Worker{
public:
	virtual void work() = 0;
	virtual void eat() = 0;
};

class Robot: public Worker{
public:
	void work() override{
    	std::cout << "I am working!" << std::endl;
    }
    void eat() override{
    	// 로봇은 eat를 사용할 필요가 없음
    }
};

 

GOOD CASE

: Interface를 분리해 필요하지 않은 의존성 제거

class Workable{
public:
	virtual void work() = 0;
    virtual ~Workable() = default;
}

class Eatable{
public:
	virtual eat() = 0;
    virtual ~Eatable() = default;
};

class Human: public Workable, public Eatable{
public:
	void work() override{
    	std::cout << "I am working!" << std::endl;
    }
    void eat() override{
    	std::cout << "I am eating!" << std::endl;
    }
};

class Robot: public Workable{
public:
	void work() override{
    	std::cout << "I am working!" << std::endl;
    }
};

 

3.5 DIP(Dependency Inversion Principle, 의존관계 역전의 원칙)

: 상위 모듈은 하위 모듈에 의존해서는 안되며 둘 다 추상화에 의존해야 하며, 구체 Class 대신 Interface나 추상 Class에 의존하는 설계 지향

 

ERROR CASE

: Computer Class가 특정 구현(Keyboard, Monitor)에 의존

class Keyboard{};
class Monitor{};

class Computer{
private:
	Keyboard keyboard;
    Monitor monitor;
};

 

GOOD CASE

: Interface에 의존하도록 수정

class InputDevice{
public:
	virtual void input() = 0;
    virtual ~InputDevice() = default;
};

class DisplayDevice{
public:
	virtual void display() = 0;
    virtual ~DisplayDevice() = default;
};

class Keyboard: public InputDevice{
public:
	void input() override{
    	std::cout << "Keyboard Input" << std::endl;
    }
};

class Monitor: public DisplayDevice{
public:
	void display() override{
    	std::cout << "Monitor display" << std::endl;
    }
};

class Computer{
// private으로 선언해 캡슐화 함으로써 외부 코드가 inputDevice와 displayDevice 포인터에 직접 접근하거나 변경하지 못하도록 보호
private:
	InputDevice* inputDevice; // 순수가상함수로 정의된 Interface로, 실제로는 이를 구현한 자식 Class(Keyboard)가 할당됨
    DisplayDevice* displayDevice; // 순수가상함수로 정의된 Interface로, 실제로는 이를 구현한 자식 Class(Monitor)가 할당됨
    
public:
	// 생성자에서 두 개의 포인터 매개변수를 받아 초기화하고, 외부에서 장치 객체를 생성해 전달하므로 Computer Class는 특정 입력/출력 장치의 구현에 직접 의존하지 않음
	Computer(InputDevice* input, DisplayDevice* display): inputDevice(input), displayDevice(display){}
    void use(){
    	// inputDevice와 displayDevice의 동작을 호출하며 inputDevice와 displayDevice가 가리키는 구체적인 구현에 따라 use()의 동작이 달라짐
    	inputDevice->input();
        displayDevice->display();
    }
};

int main(){
	Keyboard keyboard; // Keyboard 객체 생성
    Monitor monitor; // Monitor 객체 생성
    
    Computer computer(&keyboard, &monitor); // Computer 객체는 Keyboard와 Monitor 객체의 주소를 전달받아 초기화
    computer.use(); // 1) keyboard.input() 실행 -> "Keyboard input" 출력
                   //  2) monitor.display() 실행 -> "Monitor display" 출력
    return 0;
}

* 캡슐화

더보기

Class의 내부 구현(Data와 Method)을 숨기고, 외부에서 접근할 수 있는 공용 Interface (public)만 제공하는 원칙으로 내부 상태를 외부로부터 보호해 Class 사용자가 필요 이상으로 내부 구조를 알 필요 없도록 설계

-. Data 보호: 외부 코드가 inputDevice와 displayDevice Pointer에 직접 접근하거나 변경하지 못하도록 보호하며 잘못된 수정으로 인한 예기치 않은 동작 방지

-. 내부구현 변경에 유연: inputDevice와 displayDevice가 private member라면 내부 구현을 변경해도 외부 코드에 영향을 주지 않음

-. 명확한 책임 분리: Computer Class는 입력 장치와 출력 장치를 관리하고 사용하는 책임을 가지며 외부에서 직접 member 변수에 접근하면 책임이 모호해 질 수 있음

 

[ 참고 ] 객체 생성 시 Life Cycle

더보기

부모 Class의 객체가 별도로 생성되었다면 부모 Class의 Life Cycle은 자식 Class와 독립적이나, 자식 Class 객체는 반드시 부모 Class의 Life Cycle 내에서 동작

 

1) 부모 Class와 자식 Class의 객체가 별도로 생성된 경우

: 두 객체는 서로 다른 Memory 공간에 존재하므로 부모 Class 객체를 삭제해도 자식 Class의 객체에는 아무런 영향을 미치지 않음

#include <iostream>
using namespace std;

class Parent{
public:
	Parent() {cout << "Parent Constructor" << endl;}
    ~Parent() {cout << "Parent Destructor" << endl;}
};

class Child: public Parent{
public:
	Child() {cout << "Child Constructor" << endl;}
    ~Child() {cout << "Child Destructor" << endl;}
};

int main(void){
	Parent* parentObj = new Parent(); // 부모 Class 객체 생성
    Child* childObj = new Child(); // 자식 Class 객체 생성
    delete parentObj; // 부모 객체 삭제(Child는 여전히 유효)
    child Obj -> ~Child(); // 수동으로 자식 객체 소멸
    return 0;
}

 

결과

Parent Constructor
Parent Constructor
Child Contructor
Parent Destructor
Child Destructor
Parent Destructor

 

2) 부모 Class Pointer로 자식 Class를 관리하는 경우

: 부모 Class Pointer를 사용해 자식 Class 객체를 참조하고 있다면 부모 Class Pointer로 객체를 삭제할 때 부모 Class의 소멸자만 호출

이 때, 부모 Class의 소멸자가 가상소멸자(virtual)로 선언되지 않았다면 자식 Class의 소멸자가 호출되지 않아 메모리 누수 발생 가능

int main(){
	Parent* parentPtr = new Child(); // 부모 Pointer로 자식 객체 참조
    delete parentPtr; // 부모 포인터로 객체 삭제
    return 0;
}

 

결과

Parent Constructor
Child Constructor
Parent Destructor

 

3) 가상소멸자 사용하는 경우

: 부모 Class의 소멸자를 가상소멸자(virtual)로 선언하면 부모 Pointer를 사용하더라도 자식 Class의 소멸자가 먼저 호출되며, 상속 관계에서 객체를 안전하게 삭제하기 위한 권장 방법

class Parent{
public:
	Parent() {cout << "Parent Constructor" << endl;
    virtual ~Parent() {cout << "Parent Destructor" << endl;}
};

class Child: public Parent{
public:
	Child() {cout << "Child Constructor" << endl;
    ~Child() {cout << "Child Destructor" << endl;
};

int main(){
	Parent* parentPtr = new Child(); // 부모 Pointer로 자식 객체 참조
    delete parentPtr; // 부모 Pointer로 객체 삭제
    return 0;
}

 

결과

Parent Constructor
Child Constructor
Child Destructor
Parent Destructor

 

4) Smart Pointer를 사용한 객체의 Life Cycle 관리

: C++11 부터 제공되는 Smart Pointer(std::shared_ptr, std::unique_ptr)를 사용하면 객체의 Life Cycle을 안전하게 관리 가능

 

4 GoF(Gang of Four)

: Erich Gamma, Richard Helm, Ralph Johnson, John Vissides의 저서인 'GoF의 디자인패턴'을 가리키는 용어로, 처음 소프트웨어 공학에서 사용되는 패턴을 정리한 사람들의 별칭

객체지향의 문제점을 분석해 24개의 패턴으로 분류

 

5 패턴의 요소

: 24개로 분리된 디자인 패턴은 공통된 4가지 요소를 가짐

 

5.1 이름(Pattern Name)

: 패턴 이름을 통해 피턴의 용도를 직관적으로 이해 가능

 

5.2 문제(Problem)

: 각 패턴은 해결하고자 하는 문제를 갖고 있으며, 이러한 문제들은 패턴 적용을 고려해야 하는 시점을 암시

 

5.3 해법(Solution)

: 문제점을 인식했다면 객체 요소 간 관계를 정리하고 객체들을 추상화 및 나열함으로써 해결 방법을 찾음

 

5.4 결과(Consequence)

: 디자인 패턴은 알려진 문제점들을 해결하는 데 효과적이지만 모든 코드에서 디자인 패턴이 유용하다고 볼 수는 없으므로 꼭 필요한 경우를 생각해 적절히 분배해 사용해야 함

 

6 디자인 패턴의 사용 목적

6.1 통일성

: 디자인 패턴은 개발 방법을 정의함으로써 보다 통일화된 코드를 작성할 수 있음

 

6.2 실체화(Reification)

: 구현(Implementation) 보다는 디자인을 의미하며, 구조만으로 패턴을 파악하는 것은 불가능하므로 의도를 파악하는 것이 중요

 

6.3 유지보수 및 방어적 설계

: 향후 추가되는 코드를 수정하기 쉽게 변경할 수 있도록 구현해야 하며, 오랫동안 문제 없이 유지보수를 하기 위해 변경 가능한 디자인(Design for change)으로 설계해야함

코드를 작성하면서 어떤 부분이 향후 수정될 것으로 예측된다면 해당 기능을 방어적으로 처리할 수 있도록 코드가 설계되어야 하며, 지속적인 리팩토링 작업을 수행해 주어야 함

 

디자인 패턴 vs 처리 성능
성능 최적화를 위해서는 많은 함수 호출과 객체 간 호출이 적을 수록 좋으나 디자인 패턴에서는 코드의 가독성과 유지보수를 위해 객체의 메소드를 분리하며 호출도 자주 발생하게 되므로 패턴을 너무 많이 사용하는 경우 잦은 메소드 호출로 인해 성능이 저하될 수 있음
반응형

'SW > Design Pattern' 카테고리의 다른 글

1. 생성패턴 - Factory Pattern  (0) 2025.02.10
반응형

깊이 우선 탐색(DFS, Depth First Search)

특정 Data를 탐색할 때 깊이가 깊은 것을 우선적으로 탐색하는 알고리즘으로 Stack 자료구조를 사용

사실 Stack을 사용하지 않아도 구현이 가능한데, 이는 컴퓨터가 구조적으로 항상 Stack의 원리를 사용하기 때문이다.

 

깊이 우선 탐색(DFS) Sequence

더보기

Step 1: 시작 Node를 Stack에 넣고 방문 처리

Step 2: Stack의 최상단 Node 확인

Step 3: 최상단 Node에 방문하지 않은 인접 Node가 있다면 그 Node를 Stack에 넣고 방문 처리

           방문하지 않은 인접 Node가 없으면 Stack에서 최상단 Node 삭제

Step 4: Step 2로 이동

 

Code

#include <iostream>
#include <stack>
#include <vector>
using namespace std;

#define MAX 7
int visited[7];
vector <int> a[8];

void dfs(int x){
    // 이미 방문한 적 있으면 return
    if(visited[x]) return;

    // 방문 처리
    visited[x] = true;
    cout << x << " ";

    // 인접 노드 방문
    for(int i=0;i<a[x].size();i++){
        int y = a[x][i];
        dfs(y);
    }
}

int main(void){
    a[1].push_back(2);
    a[1].push_back(3);

    a[2].push_back(1);
    a[2].push_back(3);
    a[2].push_back(4);
    a[2].push_back(5);

    a[3].push_back(1);
    a[3].push_back(2);
    a[3].push_back(6);
    a[3].push_back(7);

    a[4].push_back(2);
    a[4].push_back(5);

    a[5].push_back(2);
    a[5].push_back(4);

    a[6].push_back(3);
    a[6].push_back(7);

    a[7].push_back(3);
    a[7].push_back(6);

    dfs(1);
}

출력

1 2 3 6 7 4 5
반응형

'Algorithm' 카테고리의 다른 글

[Algorithm] 너비 우선 탐색(BFS, Breadth First Search)  (0) 2023.04.09
[Algorithm] 큐(Queue)  (0) 2023.04.09
[Algorithm] 스택(Stack)  (0) 2023.04.09
[Algorithm] 심화 정렬 문제  (0) 2023.04.09
[Algorithm] 계수 정렬(Counting Sort)  (0) 2023.04.09
반응형

너비 우선 탐색(BFS, Breadth First Search)

특정 Data를 탐색할 때 너비를 우선으로 하여 탐색을 수행하는 알고리즘

최단 경로를 찾아준다는 점에서 최단 길이를 보장해야할 때 많이 사용되며, Queue를 이용한다.

e.g., 미로 찾기

 

너비 우선 탐색(BFS) Sequence

더보기

Step 1: 시작 Node를 큐에 삽입하고 방문 처리

Step 2: Queue에서 하나의 Node를 꺼냄

Step 3: 연결된 Node 중 방문하지 않은 Node를 방문하고 큐에 삽입

Step 4: Step 2로 이동

∴ BFS 수행 순서: 1 2 3 4 5 6 7

 

Code

#include <iostream>
#include <queue>
#include <vector>
using namespace std;

#define MAX 7
int visited[7]; // 방문 유무를 표시할 배열
vector<int> a[8];

void bfs(int start){
    queue<int> q;

    // 시작 노드 큐에 추가
    q.push(start);
    visited[start] = true;

    // 큐가 빌 때까지 반복
    while(!q.empty()){
        int x = q.front(); q.pop();
        cout << x << " ";

        // 현재 큐에서 꺼낸 Node와 인접한 노드 모두 방문
        for(int i=0;i<a[x].size();i++){
            int y = a[x][i];
            // 방문한 적 없는 노드이면 큐에 추가 후 방문 처리
            if(!visited[y]){
                q.push(y);
                visited[y] = true;
            }
        }
    }
}

int main(void){
    a[1].push_back(2);
    a[1].push_back(3);

    a[2].push_back(1);
    a[2].push_back(3);
    a[2].push_back(4);
    a[2].push_back(5);

    a[3].push_back(1);
    a[3].push_back(2);
    a[3].push_back(6);
    a[3].push_back(7);

    a[4].push_back(2);
    a[4].push_back(5);

    a[5].push_back(2);
    a[5].push_back(4);

    a[6].push_back(3);
    a[6].push_back(7);

    a[7].push_back(3);
    a[7].push_back(6);

    bfs(1);

    return 0;
}

 

출력

1 2 3 4 5 6 7
반응형

'Algorithm' 카테고리의 다른 글

[Algorithm] 깊이 우선 탐색(DFS, Depth First Search)  (0) 2023.04.09
[Algorithm] 큐(Queue)  (0) 2023.04.09
[Algorithm] 스택(Stack)  (0) 2023.04.09
[Algorithm] 심화 정렬 문제  (0) 2023.04.09
[Algorithm] 계수 정렬(Counting Sort)  (0) 2023.04.09
반응형

큐(Queue)

먼저 들어온 Data가 먼저 처리되는 선입선출(First In First Out, FIFO), 후입후출(Last In Last Out, LILO) 자료구조

 

큐(Queue) Sequence

더보기

명령: 삽입(7) → 삽입(5) → 삽입(4) → 삭제( ) → 삽입(6) → 삭제( )

삽입 7
삽입 5
삽입 4
삭제
삽입 6
삭제

 

Code

#include <iostream>
#include <queue>
using namespace std;

int main(void){
    queue<int> q;
    q.push(7);
    q.push(5);
    q.push(4);
    q.pop();
    q.push(6);
    q.pop();
    while(!q.empty()){
        cout << q.front() << " ";
        q.pop();
    }
    return 0;
}

출력

4 6
반응형
반응형

스택(Stack)

가장 마지막에 들어온 Data가 가장 먼저 처리되는 후입선출(Last In First Out, LIFO), 선입후출(First In Last Out, FILO) 자료구조

 

Stack 동작 Sequnce

더보기

명령: 삽입 (7) → 삽입(5) → 삽입(4) → 삭제() → 삽입(6) → 삭제()

삽입 7
삽입 5
삽입 4
삭제
삽입 6
삭제

 

Code

#include <iostream>
#include <stack>
using namespace std;

int main(void){
    stack<int> s;
    s.push(7);
    s.push(5);
    s.push(4);
    s.pop();
    s.push(6);
    s.pop();
    while(!s.empty()){
        cout << s.top() << " ";
        s.pop();
    }
    return 0;
}

출력

5 7
반응형
반응형

1181 단어 정렬

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

 

1181번: 단어 정렬

첫째 줄에 단어의 개수 N이 주어진다. (1 ≤ N ≤ 20,000) 둘째 줄부터 N개의 줄에 걸쳐 알파벳 소문자로 이루어진 단어가 한 줄에 하나씩 주어진다. 주어지는 문자열의 길이는 50을 넘지 않는다.

www.acmicpc.net

Code

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
#define MAX 20000+10

int N;
string tmp;
vector <string> vec;

void Input(){
    cin >> N;
    for(int i=0;i<N;i++){
        cin >> tmp;
        vec.push_back(tmp);
    }
}

void Output(){
    tmp = "";
    for(int i=0;i<N;i++)
        if(tmp == vec[i]) continue;
        else{
            cout << vec[i] << "\n";
            tmp = vec[i];
        }
}

bool comp(string a, string b){
    if(a.length() == b.length()) return a < b;
    else return a.length() < b.length();
}

void Sort(){
    sort(vec.begin(), vec.end(), comp);
}

int main(void){
    Input();
    Sort();
    Output();
    return 0;
}

 

1431 시리얼 번호

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

 

1431번: 시리얼 번호

첫째 줄에 기타의 개수 N이 주어진다. N은 50보다 작거나 같다. 둘째 줄부터 N개의 줄에 시리얼 번호가 하나씩 주어진다. 시리얼 번호의 길이는 최대 50이고, 알파벳 대문자 또는 숫자로만 이루어

www.acmicpc.net

Code

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
#define MAX 1000+10

int N;
vector <string> vec;

void Input(){
    string tmp="";
    cin >> N;
    for(int i=0;i<N;i++){
        cin >> tmp;
        vec.push_back(tmp);
    }
}

void Output(){
    for(int i=0;i<N;i++)
        cout << vec[i] << "\n";
}

int cal(string word){
    int sum=0;
    // 숫자만 뽑아내기
    for(int i=0;i<word.length();i++){
        if('0' <= word[i] && word[i] <= '9'){
            sum += word[i] - '0';
        }
    }
    return sum;
}

bool comp(string a, string b){
    int sum_a, sum_b = 0;
    // A와 B의 길이가 다르면 짧은 것이 먼저온다
    if(a.length() != b.length()) return a.length() < b.length();
    // 서로 길이가 같다면 A의 모든 자리수의 합과 B의 모든 자리수의 합을 비교해서 작은 합을 가지는 것이 먼저 온다. (숫자인 것만 더한다)
    else{
        sum_a = cal(a);
        sum_b = cal(b);
        if(sum_a != sum_b) return sum_a < sum_b;
        else{
            // 만약 1, 2번 둘 조건으로도 비교할 수 없으면, 사전순으로 비교한다 숫자가 알파벳보다 사전순으로 작다.
            return a < b;
        }
    }
}

void Solution(){
    sort(vec.begin(), vec.end(), comp);
}

int main(void){
    Input();
    Solution();
    Output();
}

 

10989 수 정렬하기 3

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

 

10989번: 수 정렬하기 3

첫째 줄에 수의 개수 N(1 ≤ N ≤ 10,000,000)이 주어진다. 둘째 줄부터 N개의 줄에는 수가 주어진다. 이 수는 10,000보다 작거나 같은 자연수이다.

www.acmicpc.net

Code

#include <iostream>
using namespace std;

int N;
int tmp;
int count_arr[10000+10];

int main(void){
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> N;
    for(int i=0;i<N;i++){
        cin >> tmp;
        count_arr[tmp]++;
    }

    for(int i=1;i<10001;i++){
        while(count_arr[i]){
            cout << i << "\n";
            count_arr[i]--;
        }
    }
    return 0;
}
반응형

'Algorithm' 카테고리의 다른 글

[Algorithm] 큐(Queue)  (0) 2023.04.09
[Algorithm] 스택(Stack)  (0) 2023.04.09
[Algorithm] 계수 정렬(Counting Sort)  (0) 2023.04.09
[Algorithm] 힙 정렬(Heap Sort)  (0) 2023.04.09
[Algorithm] C++ STL sort 함수  (0) 2023.04.08
반응형

계수 정렬(Counting Sort)

범위 조건이 있을 때에 한해서 퀵 정렬(Quick Sort), 병합 정렬(Merge Sort), 힙 정렬(Heap Sort)보다 더 빠르게 정렬해야하는 경우 사용될 수 있는 알고리즘으로 단순히 크기를 기준으로 세는 알고리즘

모든 데이터에 한 번씩만 접근하면 된다는 점에서 매우 효율적인 알고리즘이다.

 

계수 정렬(Counting Sort) Sequence

더보기
DATA
초기 상태
결과

 

Code

#include <iostream>
using namespace std;
#define MAX 5

int arr[] = {1, 3, 2, 4, 3, 2, 5, 3, 1, 2, 3, 4, 4, 3, 5, 1, 2, 3, 5, 2, 3, 1, 4, 3, 5, 1, 2, 1, 1, 1};
int count_arr[MAX+1] = {0,};

int main(void){
    for(int i=0;i<sizeof(arr)/4;i++){
        count_arr[arr[i]]++;
    }

    for(int i=0;i<MAX+1;i++){
    	if(count_arr[i]== 0) continue;
        for(int j=1;j<=count_arr[i];j++){
            cout << i << " ";
        }
    }
    return 0;
}

 

출력

1 1 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 3 3 3 4 4 4 4 5 5 5 5

 

시간 복잡도

모든 원소를 한 번씩만 확인하기 때문에 O(N)

반응형

'Algorithm' 카테고리의 다른 글

[Algorithm] 스택(Stack)  (0) 2023.04.09
[Algorithm] 심화 정렬 문제  (0) 2023.04.09
[Algorithm] 힙 정렬(Heap Sort)  (0) 2023.04.09
[Algorithm] C++ STL sort 함수  (0) 2023.04.08
[Algorithm] 병합 정렬(Merge Sort)  (0) 2023.04.08
반응형

C++ 언어의 경우 훌륭한 정렬 관련 Library가 존재한다.

 

기본 사용법

Code - 기본

#include <iostream>
#include <algorithm> // sort()는 algorithm library에 포함되어있음
using namespace std;

int main(void){
    int a[10] = {4, 7, 6, 2, 1, 5, 8, 10, 3, 9};
    sort(a, a+10); // 정렬할 배열의 메모리 주소, 정렬할 마지막 원소의 메모리 주소
    for(int i=0;i<10;i++)
        cout << a[i] << " ";
    cout << endl;
    return 0;
}

출력

1 2 3 4 5 6 7 8 9 10

 

Code - 정렬할 기준 정하는 방식

#include <iostream>
#include <algorithm>
using namespace std;

// 오름차순 정렬
bool comp_up(int a, int b){ // true, false를 기준으로 정렬 기준을 판단
    return a < b; // a가 b보다 작으면 정렬
}

// 내림차순 정렬
bool comp_down(int a, int b){
    return a > b; // a가 b보다 크면 정렬
}

int main(void){
    int a[10] = {4, 7, 6, 2, 1, 5, 8, 10, 3, 9};
    sort(a, a+10, comp_up);
    for(int i=0;i<10;i++)
        cout << a[i] << " ";
    cout << endl;
    sort(a, a+10, comp_down);
    for(int i=0;i<10;i++)
        cout << a[i] << " ";
    cout << endl;
    return 0;
}

출력

1 2 3 4 5 6 7 8 9 10 
10 9 8 7 6 5 4 3 2 1

 

Code - Data를 묶어서 정렬

#include <iostream>
#include <algorithm>
using namespace std;

// class: 하나의 객체를 정의함으로써 여러 개의 변수를 정의
// 일종의 새로운 자료형
class Student{
public:
    string name;
    int score;
    // 생성자: 특정 객체 초기화
    Student(string name, int score){
        this->name = name;
        this->score = score;
    }
    // 정렬 기준: 점수가 낮은 순서
    bool operator <(Student &student){
        return this->score < student.score;
    }
};

bool compare(int a, int b){
    return a > b;
}

int main(void){
    // 5명의 학생
    Student students[] = {
        Student("student1", 90),
        Student("student2", 80),
        Student("student3", 79),
        Student("student4", 66),
        Student("student5", 78)
    };
    sort(students, students+5); // 이미 Class 안에 정렬 기준을 정의해두었으니 변수의 이름만 넣어주면 됨
    for(int i=0;i<5;i++)
        cout << students[i].name << " ";
    cout << endl;
    return 0;
}

출력

student4 student5 student3 student2 student1

 

Code - Pair library를 이용한 정렬

더보기

Vector: 마치 배열과 같이 작동하는데 원소를 선택적으로 삽입(Push) 및 삭제(Pop)할 수 있는 단순한 배열을 사용하기 쉽게 개편한 자료구조

Pair: 한 쌍의 데이터를 처리할 수 있도록 해주는 자료구조

#include <iostream>
#include <algorithm>
#include <vector> // 연결리스트 형태로 표현되는 자료구조
using namespace std;

int main(void){
    // 한 쌍의 데이터를 묶어서 사용할 수 있는 library
    // 기본적으로 first, second 인자를 가짐
    vector<pair<int, string>> v;
    v.push_back(pair<int, string> (90, "student1"));
    v.push_back(pair<int, string> (79, "student2"));
    v.push_back(pair<int, string> (88, "student3"));
    v.push_back(pair<int, string> (69, "student4"));
    v.push_back(pair<int, string> (95, "student5"));

    sort(v.begin(), v.end());

    for(int i=0;i<v.size();i++){
        cout << v[i].second << ' ';
    }
    return 0;
}

출력

student4 student2 student3 student1 student5

 

Code - 정렬 기준을 명시하는 Pair 사용

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

bool comp(pair<string, pair<int, int>> a,
          pair<string, pair<int, int>> b){
    // 성적이 같으면 생년월일이 더 느린 학생이 높은 우선순위 가짐
    if(a.second.first == b.second.first)
        return a.second.second > b.second.second;
    // 성적이 다르면 성적이 더 높은 학생이 높은 우선순위 가짐
    else
        return a.second.first > b.second.first;
}

int main(void){
    vector<pair<string, pair<int, int>>> v;
    v.push_back(pair<string, pair<int, int>> ("student1", pair<int, int>(90, 970416)));
    v.push_back(pair<string, pair<int, int>> ("student2", pair<int, int>(87, 990722)));
    v.push_back(pair<string, pair<int, int>> ("student3", pair<int, int>(93, 891130)));
    v.push_back(pair<string, pair<int, int>> ("student4", pair<int, int>(87, 920631)));
    v.push_back(pair<string, pair<int, int>> ("student5", pair<int, int>(84, 950718)));

    sort(v.begin(), v.end(), comp);

    for(int i=0;i<=v.size();i++)
        cout << v[i].first << ' ';
    return 0;
}

출력

student3 student1 student2 student4 student5
반응형

'Algorithm' 카테고리의 다른 글

[Algorithm] 계수 정렬(Counting Sort)  (0) 2023.04.09
[Algorithm] 힙 정렬(Heap Sort)  (0) 2023.04.09
[Algorithm] 병합 정렬(Merge Sort)  (0) 2023.04.08
[Algorithm] 기초 정렬 문제  (0) 2023.04.08
[Algorithm] 퀵 정렬(Quick Sort)  (0) 2023.04.08

+ Recent posts