인프런 워밍업 클럽 4기 CS 전공지식  2주차 발자국

인프런 워밍업 클럽 4기 CS 전공지식 2주차 발자국

[컴퓨터 구조]

멀티플렉서 (Multiplexer, MUX)

  • 여러 개의 입력 중 하나를 선택해 출력하는 회로

  • 2^n개의 입력 → 1개의 출력

  • 선택 신호(Select Line)로 출력 결정

  • 예: 4:1 MUX는 입력 4개, 출력 1개, 선택선 2개

주요 역할: 다수의 데이터 중 필요한 것만 선택


디멀티플렉서 (Demultiplexer, DEMUX)

  • 하나의 입력을 선택된 출력 하나로 전달하는 회로

  • 1개의 입력 → 2^n개의 출력

  • 선택 신호로 어느 출력으로 보낼지 결정

주요 역할: 하나의 데이터를 여러 대상 중 하나로 분배


디코더 (Decoder)

  • n개의 입력을 받아 2^n개의 출력 중 하나를 활성화

  • 특정 입력 조합 → 해당 출력 하나만 1(활성화), 나머지는 0

  • 예: 3→8 디코더는 입력 3개 → 출력 8개

주요 역할: 특정 주소 또는 명령어를 구분해 선택


컨트롤 버퍼 (Control Buffer)

  • 제어 신호(Control Signal) 또는 제어 정보(Control Data)를 일시 저장하거나 전달하는 버퍼 회로

  • CPU 내부, 메모리 컨트롤러, I/O 디바이스 제어 등에서 사용

  • 동기화, 속도 차 완충, 임시 저장, 전달 안정화 등의 역할 수행

  • 경우에 따라 컨트롤 레지스터(Control Register) 와 혼용되기도 함

주요 역할:

  • 제어 신호의 전달 시 타이밍 조절

  • 동기화 문제 해결

  • 제어 흐름의 안정성 유지



반가산기 (Half Adder)

  • 두 개의 1비트 이진수(A, B)를 더하는 회로

  • 출력: 합(Sum), 자리올림(Carry)

  • 논리식:

    • Sum = A ⊕ B

    • Carry = A ∧ B

특징: 이전 자리에서의 올림값(Carry-in)을 처리할 수 없음


전가산기 (Full Adder)

  • 세 개의 1비트 이진수(A, B, Carry-in)를 더하는 회로

  • 출력: 합(Sum), 자리올림(Carry-out)

  • 논리식:

    • Sum = A ⊕ B ⊕ Cin

    • Cout = (A ∧ B) ∨ (B ∧ Cin) ∨ (A ∧ Cin)

특징: 자리올림 입력을 처리할 수 있어 다비트 덧셈 가능


조합 논리 회로 (Combinational Logic Circuit)

  • 현재 입력에만 의존하여 출력이 결정되는 회로

  • 과거 상태(기억)를 고려하지 않음 → 메모리 없음

  • 출력 = 입력의 조합 결과

특징:

  • 입력이 바뀌면 출력이 즉시 바뀜

  • 클럭 신호 불필요

대표 예시:

  • 가산기(Adder), 디코더(Decoder), 인코더(Encoder), 멀티플렉서(MUX), 비교기 등


순차 논리 회로 (Sequential Logic Circuit)

  • 입력 + 현재 상태(과거 출력)에 따라 출력이 결정

  • 기억 기능(메모리) 포함

  • 클럭(Clock) 신호 사용 → 타이밍 기반 동작

특징:

  • 출력이 시간의 흐름에 따라 달라짐

  • 상태를 저장하고 변화함 (플립플롭, 레지스터 등 포함)

대표 예시:

  • 플립플롭(Flip-Flop), 레지스터(Register), 카운터(Counter), FSM(상태 기계), 메모리 등


SR 래치 (Set-Reset Latch)

  • S(Set), R(Reset) 두 입력으로 출력 Q 제어

  • 출력은 이전 상태를 기억함 (순차 회로의 기본 형태)

image

  • 문제점: S=1, R=1이면 출력이 불안정해짐


D 래치 (Data or Delay Latch)

  • SR 래치의 문제(S=1, R=1)를 해결하기 위해 설계

  • 입력: D(Data), Enable (또는 Clock)

  • Enable이 1일 때만 D값이 Q로 전달됨

image

  • 특징: 항상 안정적인 동작, 메모리 소자에 많이 사용


JK 래치

  • SR 래치의 금지 상태를 해결한 구조

  • 입력: J, K, Clock

  • J: Set 역할, K: Reset 역할

  • J=K=1이면 출력 반전

image

  • 특징: 플립플롭 구현에 주로 사용됨


    클럭 (Clock)

    • 디지털 회로에서 동기 신호를 제공하는 주기적인 펄스

    • 회로 동작의 타이밍 기준 역할

    • 클럭 상승엣지(↑), 하강엣지(↓)에서 동작하도록 회로 설계됨

    플립플롭 (Flip-Flop)

    • 1비트의 정보를 저장할 수 있는 순차 논리 회로

    • 클럭에 의해 상태가 변하거나 유지됨

    • D, JK, T 등 여러 종류가 있음


    레지스터 (Register)

    • 여러 개의 플립플롭을 묶은 구조 (보통 8, 16, 32비트 등)

    • CPU 내부에서 데이터를 임시 저장하는 데 사용

    • 동기식으로 동작하며 고속 처리 가능

    RAM (Random Access Memory)

    • 데이터를 읽고 쓰기 가능한 임의 접근 메모리

    • 전원이 꺼지면 데이터가 사라짐(휘발성)

    • 주소 지정으로 원하는 위치의 데이터를 직접 접근


[자료구조와 알고리즘]

 

강의 수강

나의 주력 언어는 c++이라서 강의를 토대로 c++버젼으로 바꿔보았다


Red-Black Tree

  • 자기 균형 이진 탐색 트리(BST)의 일종

  • 삽입/삭제 시 균형 유지를 위한 색상(빨강/검정) 속성 사용

 

Red-Black Tree의 주요 특징

  • 노드는 빨강(Red) 또는 검정(Black)

  • 루트 노드는 항상 검정

  • 모든 리프(NIL)는 검정 (NULL 노드 포함)

  • 빨간 노드의 자식은 반드시 검정 (연속된 빨강 없음)

  • 임의의 노드에서 리프까지 가는 모든 경로에는 동일한 수의 검정 노드 존재

 

Red-Black Tree의 장점

  • AVL Tree보다 회전 횟수가 적음

  • 삽입/삭제 성능이 더 안정적

Red-Black Tree의 단점

  • AVL보다 탐색 시간이 조금 느림 (균형이 느슨함)

 

시간 복잡도

  • 탐색 / 삽입 / 삭제: O(log n)

  • 최악의 경우에도 높이가 log₂(n) 이내로 유지됨

Red_BlackTree.h

#pragma once
#include "BinarySearchTree.h"

// Red-Blakc Tree
// 1) 모든 노드는 Red or Black
// 2) Root는 Black
// 3) Leaf(NIL)는 Black
// 4) Red 노드의 자식은 Black (연속해서 Red-Red X)
// 5) 각 노드로부터 ~ 리프까지 가는 경로들은 모두 같은 수의 Black

class Red_BlackTree
{
public:
	Red_BlackTree();
	~Red_BlackTree();

	void Print();
	void Print(Node* node, int x, int y);

	Node* Search(Node* node, int key);

	Node* Min(Node* node);
	Node* Max(Node* node);
	Node* Next(Node* node);

	void	Insert(int key);
	void	InsertFixup(Node* node);

	void	Delete(int key);
	void	Delete(Node* node);
	void	DeleteFixup(Node* node);

	void	Replace(Node* u, Node* v);

	// Red-Black Tree
	void	RotateLeft(Node* node);
	void	RotateRight(Node* node);

private:
	Node* _root = nullptr;
	Node* _nil = nullptr;
};

Red_BlackTree.cpp

#include "Red_BlackTree.h"
#include <iostream>
#include <Windows.h>

using namespace std;

enum class ConsoleColor
{
	BlACK = 0,
	RED = FOREGROUND_RED,
	GREEN = FOREGROUND_GREEN,
	BLUE = FOREGROUND_BLUE,
	YELLOW = RED | GREEN,
	WHITE = RED | GREEN | BLUE,
};

void SetCursorColor(ConsoleColor color)
{
	HANDLE output = ::GetStdHandle(STD_OUTPUT_HANDLE);
	::SetConsoleTextAttribute(output, static_cast<SHORT>(color));
}

void SetCursorPosition(int x, int y)
{
	HANDLE output = ::GetStdHandle(STD_OUTPUT_HANDLE);
	COORD pos = { static_cast<SHORT>(x), static_cast<SHORT>(y) };
	::SetConsoleCursorPosition(output, pos);
}

void ShowConsoleCursor(bool flag)
{
	HANDLE output = ::GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO cursorInfo;
	::GetConsoleCursorInfo(output, &cursorInfo);
	cursorInfo.bVisible = flag;
	::SetConsoleCursorInfo(output, &cursorInfo);
}
Red_BlackTree::Red_BlackTree()
{
	_nil = new Node(); // Black
	_root = _nil;
	_root->parent = _nil;

}

Red_BlackTree::~Red_BlackTree()
{
	delete _nil;
}

void Red_BlackTree::Print()
{
	::system("cls");
	ShowConsoleCursor(false);
	Print(_root, 10, 0);
}

void Red_BlackTree::Print(Node* node, int x, int y)
{
	if (node == nullptr)
		return;

	SetCursorPosition(x, y);

	if (node->color == Color::Black)
		SetCursorColor(ConsoleColor::BLUE);
	else
		SetCursorColor(ConsoleColor::RED);

	cout << node->key;
	Print(node->left, x - (5 / (y + 1)), y + 1);
	Print(node->right, x + (5 / (y + 1)), y + 1);

	SetCursorColor(ConsoleColor::WHITE);
}


Node* Red_BlackTree::Search(Node* node, int key)
{
	if (node == _nil || key == node->key)
		return node;

	if (key < node->key)
		return Search(node->left, key);
	else
		return Search(node->right, key);
}

Node* Red_BlackTree::Min(Node* node)
{
	while (node->left != _nil)
		node = node->left;

	return node;
}

Node* Red_BlackTree::Max(Node* node)
{
	while (node->right != _nil)
		node = node->right;

	return node;
}

Node* Red_BlackTree::Next(Node* node)
{
	if (node->right != _nil)
		return Min(node->right);

	Node* parent = node->parent;

	while (parent != _nil && node == parent->right)
	{
		node = parent;
		parent = parent->parent;
	}

	return parent;
}

void Red_BlackTree::Insert(int key)
{ 
	Node* newNode = new Node();
	newNode->key = key;

	Node* node = _root;
	Node* parent = _nil;

	while (node != _nil)
	{
		parent = node;
		if (key < node->key)
			node = node->left;
		else
			node = node->right;
	}

	newNode->parent = parent;

	if (parent == _nil)
		_root = newNode;
	else if (key < parent->key)
		parent->left = newNode;
	else
		parent->right = newNode;

	// 검사
	newNode->left = _nil;
	newNode->right = _nil;
	newNode->color = Color::Red;

	InsertFixup(newNode);
}

void Red_BlackTree::InsertFixup(Node* node)
{
	// 1) p = red, uncle = red
	// -> p = black, uncle = black, pp = red로 바꿈
	// 2) p = red, uncle = black (triangle)
	// -> 회전을 통해 case 3으로 바꿈
	// 3) p = red, uncle = black (list)
	// -> 색상 변경 + 회전

	while (node->parent->color == Color::Red)
	{
		if (node->parent == node->parent->parent->left)
		{
			Node* uncle = node->parent->parent->right;
			if (uncle->color == Color::Red) // p = red, uncle = red
			{
				node->parent->color = Color::Black; // p
				uncle->color = Color::Black; // u
				node->parent->parent->color = Color::Red;
				node = node->parent->parent;
			}
			else // p = red, uncle = black
			{
				if (node == node->parent->right) 
				{
					// Triangle 타입
					//       [pp(B)]
					//   [p(R)]     [u(B)]
					//      [n(R)]

					//        [pp(B)]
					//      [p(R)]  [u(B)]
					//   [n(R)] 

					node = node->parent;
					RotateLeft(node);
				}

				// List 타입
				//        [pp(R)]
				//      [p(B)]  [u(B)]
				//   [n(R)]  

				//       [p(B)]  
				//   [n(R)]   [pp(R)]
				//					[u(B)]

				node->parent->color = Color::Black;
				node->parent->parent->color = Color::Red;
				RotateRight(node->parent->parent);
			}
		}

		else
		{
			Node* uncle = node->parent->parent->left;
			if (uncle->color == Color::Red) // p = red, uncle = red
			{
				node->parent->color = Color::Black; // p
				uncle->color = Color::Black; // u
				node->parent->parent->color = Color::Red;
				node = node->parent->parent;
			}
			else // p = red, uncle = black
			{
				if (node == node->parent->left)
				{
					node = node->parent;
					RotateRight(node);
				}

				// List 타입
				//					 [p(B)]    
				//			  [pp(R)]      [n(R)]  
				//      [u(B)]

				node->parent->color = Color::Black;
				node->parent->parent->color = Color::Red;
				RotateLeft(node->parent->parent);
			}
		}
	}

	_root->color = Color::Black;
}

void Red_BlackTree::Delete(int key)
{
	Node* deleteNode = Search(_root, key);
	Delete(deleteNode);
}

// 먼저 BST 삭제 실행
void Red_BlackTree::Delete(Node* node)
{
	if (node == _nil)
		return;

	if (node->left == _nil)
	{
		Color color = node->color;
		Node* right = node->right;

		Replace(node, node->right);

		if (color == Color::Black)
			DeleteFixup(right);
	}
	else if (node->right == _nil)
	{
		Color color = node->color;
		Node* left = node->left;

		Replace(node, node->left);

		if (color == Color::Black)
			DeleteFixup(left);
	}
	else
	{
		// 다음 데이터 찾기
		Node* next = Next(node);
		node->key = next->key;
		Delete(next);
	}
}

// 먼저 BST 삭제 실행...
// - Case 1) 삭제할 노드가 Red-> 그냥 삭제! 끝!
// - Case 2) root가 DB -> 그냥 추가 Black 삭제! 끝!
// - Case 3) DB의 sibling 노드가 Red
// -- s = black, p = red (s <-> p 색상 교환)
// -- DB 방향으로 rotate(p)
// -- goto other case
// - Case 4) DB의 sibling 노드가 Black && sibling의 양쪽 자식도 Black
// -- 추가 Black을 parent에게 이전
// --- p가 Red이면 Black 됨.
// --- p가 Black이면 DB 됨.
// -- s = red
// -- p를 대상으로 알고리즘 이어서 실행 (DB가 여전히 존재하면)
// - Case 5) DB의 sibling 노드가 Black && sibling의 near child = red, far child = black
// -- s <-> near 색상 교환
// -- far 방향으로 rotate(s)
// -- goto case 6
// - Case 6) DB의 sibling 노드가 Black && sibling의 far child = red
// - p <-> s 색상 교환
// - far = black
// - rotate(p) (DB 방향으로)
// - 추가 Black 제거
void Red_BlackTree::DeleteFixup(Node* node)
{
	Node* x = node;

	// [Case 1][Case 2]
	while (x != _root && x->color == Color::Black)
	{
		//			[p(B)]
		// [x(DB)]			[s(R)]
		//				[1]

		//			[S(B)]
		//		[p(R)]
		// [x[DB]	[1]

		if (x == x->parent->left)
		{
			Node* s = x->parent->right;
			// [Case 3]
			if (s->color == Color::Red)
			{
				s->color = Color::Black;
				x->parent->color = Color::Red;

				RotateLeft(x->parent);
				s = x->parent->right;
			}

			// [Case 4]
			if (s->left->color == Color::Black &&
				s->right->color == Color::Black)
			{
				s->color = Color::Red;
				x = x->parent;
			}
			else
			{
				// [Case 5]

				//			[p]
				// [x(DB)]		   [s(B)]
				//			 [near(R)] [far(B)]


				//			[p]
				// [x(DB)]		   [near(B)] 
				//						[s(R)]
				//							[far(B)]

				if (s->right->color == Color::Black)
				{
					s->left->color == Color::Black;
					s->color == Color::Red;
					RotateRight(s);
					s = x->parent->right;	// near
				}

				// [Case 6]

				//			[p]
				// [x(DB)]		   [s(B)] 
				//						[far(R)]

				s->color = x->parent->color;
				x->parent->color = Color::Black;
				s->right->color = Color::Black;
				RotateLeft(x->parent);
				x = _root; // 루프를 빠져나오기 위해서
			}
		}
		else
		{
			// [Case3]
			Node* s = x->parent->left;
			if (s->color == Color::Red)
			{
				s->color = Color::Black;
				x->parent->color = Color::Red;
				RotateRight(x->parent);
				s = x->parent->left; // [1]
			}

			// [Case4]
			if (s->right->color == Color::Black && s->left->color == Color::Black)
			{
				s->color = Color::Red;
				x = x->parent;
			}
			else
			{
				// [Case5]
				if (s->left->color == Color::Black)
				{
					s->right->color = Color::Black;
					s->color = Color::Red;
					RotateLeft(s);
					s = x->parent->left;
				}

				// [Case6]
				s->color = x->parent->color;
				x->parent->color = Color::Black;
				s->left->color = Color::Black;
				RotateRight(x->parent);
				x = _root;
			}
		}
	}
	x->color = Color::Black;
}

// u 서브트리를 v 서브트리로 교체
// 그리고 delete u
void Red_BlackTree::Replace(Node* u, Node* v)
{
	if (u->parent == _nil)
		_root = v;
	else if (u == u->parent->left)
		u->parent->left = v;
	else
		u->parent->right = v;

	v->parent = u->parent;

	delete u;
}

//    [x]  
// [1]   [y]
//      [2][3]

//     [y]
//  [x]   [3]
// [1][2]

void Red_BlackTree::RotateLeft(Node* x)
{
	Node* y = x->right;

	x->right = y->left;

	if (y->left != _nil)
		y->left->parent = x;

	y->parent = x->parent;

	if (x->parent == _nil)
		_root = y;
	else if (x == x->parent->left)
		x->parent->left = y;
	else
		x->parent->right = y;

	y->left = x;
	x->parent = y;
}

//     [y]
//  [x]   [3]
// [1][2]

//    [x]  
// [1]   [y]
//      [2][3]

void Red_BlackTree::RotateRight(Node* y)
{
	Node* x = y->left;

	y->left = x->right;

	if (x->right != _nil)
		x->right->parent = y;

	x->parent = y->parent;

	if (y->parent == _nil)
		_root = x;
	else if (y == y->parent->right)
		y->parent->right = x;
	else
		y->parent->left = x;

	x->right = y;
	y->parent = x;
}


출력 결과

int main()
{
	Red_BlackTree rbt;

	rbt.Insert(30);
	rbt.Print();
	//this_thread::sleep_for(1s);

	rbt.Insert(10);
	rbt.Print();
	//this_thread::sleep_for(1s);

	rbt.Insert(20);
	rbt.Print();
	//this_thread::sleep_for(1s);

	rbt.Insert(25);
	rbt.Print();
	//this_thread::sleep_for(1s);

	rbt.Insert(40);
	rbt.Print();
	//this_thread::sleep_for(1s);

	rbt.Insert(50);
	rbt.Print();
	//this_thread::sleep_for(1s);

	rbt.Delete(20);
	rbt.Print();
	this_thread::sleep_for(1s);

	rbt.Delete(10);
	rbt.Print();
	this_thread::sleep_for(1s);
}

image


우선순위 큐와 힙

우선순위 큐 (Priority Queue)

  • 일반 큐와 달리, 요소들이 삽입된 순서가 아니라 우선순위에 따라 처리

  • 가장 높은 우선순위의 요소가 먼저 나감

  • 삽입(Push)과 삭제(Pop)에 평균 O(log n) 시간복잡도

  • 내부 구현은 보통 힙(Heap) 자료구조로 되어 있음

사용 예:

  • CPU 스케줄러

  • 다익스트라 알고리즘

  • 이벤트 처리 시스템

힙 (Heap)

  • 완전 이진 트리의 일종

  • 부모 노드가 자식 노드보다 항상 우선순위가 높거나 낮은 특성을 가짐

  • 두 종류가 있음:

    • 최대 힙 (Max Heap): 부모 ≥ 자식 → 가장 큰 값이 루트

    • 최소 힙 (Min Heap): 부모 ≤ 자식 → 가장 작은 값이 루트

  • 삽입: 마지막 위치에 추가 후 위로 올라가며 정렬 (Heapify-up)

  • 삭제: 루트 제거 후 마지막 노드를 루트로 옮기고 아래로 정렬 (Heapify-down)

시간복잡도

  • 삽입: O(log n)

  • 삭제: O(log n)

  • 최대/최솟값 조회: O(1)

힙 정렬

  • 완전 이진 트리 기반 정렬 알고리즘

  • 최대 힙(Max Heap) 또는 최소 힙(Min Heap)을 이용

  • 제자리 정렬 가능 (in-place), 안정 정렬은 아님

동작 과정

  1. 배열을 힙 구조로 만든다 (heapify)

  2. 루트(최대/최소값)를 배열 끝으로 보내고 힙 크기 감소

  3. 나머지 트리를 다시 heapify

  4. 이 과정을 배열 전체 크기만큼 반복

시간 복잡도

  • 힙 구성: O(n)

  • 정렬(heapify 반복): O(n log n)

  • 총 시간 복잡도: O(n log n) (최악/최선/평균 동일)

PriorityQueue.h

#pragma once
#include <iostream>
#include <vector>

using namespace std;

template<typename T, typename Container = vector<T>, typename Predicate = less<T>>
class PriorityQueue
{
public:
	void push(const T& data)
	{
		// 우선 힙 구조부터 맞춰준다
		_heap.push_back(data);

		// 도장깨기 시작
		int now = static_cast<int>(_heap.size()) - 1;
		// 루트 노드까지
		while (now > 0)
		{
			// 부모 노드와 비교
			int next = (now - 1) / 2;
			if (_predicate(_heap[now], _heap[next]))
				break;

			// 데이터 교체
			::swap(_heap[now], _heap[next]);
			now = next;
		}
	}

	void pop() 
	{
		// 맨 뒤에값 루트로 교체 후 맨 뒤값 제거
		_heap[0] = _heap.back();
		_heap.pop_back();

		int now = 0;

		while (true)
		{
			int left = 2 * now + 1;
			int right = 2 * now + 2;

			// 리프에 도달한 경우
			if (left >= (int)_heap.size())
				break;

			int next = now;

			// 왼쪽과 비교
			if (_predicate(_heap[next], _heap[left]))
				next = left;
			
			// 둘 중 승자를 오른쪽과 비교
			if (right < (int)_heap.size() && _predicate(_heap[next], _heap[right]))
				next = right;

			// 왼쪽,오른쪽 둘 다 현재 값보다 작으면 종료
			if (next == now)
				break;

			::swap(_heap[now], _heap[next]);
			now = next;
		}
	}

	T& top()
	{
		return _heap[0];
	}

	bool empty()
	{
		return _heap.empty();
	}

private:
	Container _heap = {};
	Predicate _predicate = {};
};

 

출력 결과

int main()
{
	PriorityQueue<int, vector<int>, greater<int>> pq;
	//PriorityQueue<int, vector<int>> pq;

	pq.push(100);
	pq.push(300);
	pq.push(200);
	pq.push(500);
	pq.push(400);

	while (pq.empty() == false)
	{
		int value = pq.top();
		pq.pop();

		cout << value << endl;
	}
}

image

댓글을 작성해보세요.

채널톡 아이콘