상호 배타적 집합

모집합이 여러 작은 집합들로 나뉜 상황을 표현하는 독특한 트리

상호 배타적 집합(Disjoint-Set)을 표현하는 유니온 파인드(Union-Find) 자료구조

유니온-파인드는 '합집합 찾기'라는 의미를 가지고 있다.

서로소 집합 알고리즘 이라고도 한다. (1외에 공약수 없음)

n 명의 사람 중에서 생일이 같은 사람들끼리 팀을 구성하는 상황을 생각해보자.

생일이 여러 달에 속한 사람은 없다. 따라서 2개 팀에 속한 사람이 없다.

유니온 파인드 자료구조는 상호 배타적인 부분 집합들로 나눠진 원소들을 저장하고 조작하는 자료구조다.

유니온 파인드 연산 3가지

n명의 사람을 0번부터 n-1번 까지의 원소로 표현한다.

각 1개의 원소를 포함하는 n개의 집합을 만든다. (초기화)

두 사람 a, b가 생일이 같다는 것을 확인 할 때마다 두 사람이 포함된 집합이 합쳐진다.(Union 연산)

어떤 원소 b가 주어졌을 때, 이 원소가 속한 집합을 반환한다. (Find 연산)

트리로 상호 배타적 집합 표현하기

  • 한 집합에 속하는 원소를 하나의 트리로 묶어준다.
  • 트리의 루트에 있는 원소를 각 집합의 대표라고 부른다.
  • 각 트리의 루트를 찾은 뒤, 하나를 다른 한쪽의 자손으로 넣으면 된다.각 원소가 포함된 트리의 루트를 찾은 뒤 이들이 같은지 비교하기
  • 루트가 같다면 같은 트리에 속한 것
  • 두 원소가 같은 트리에 속해 있는지 어떻게 확인할까?

트리를 이용한 상호 배타적 집합의 표현

  • Find(찾기) 연산루트는 부모가 없으므로 자기 자신을 가리킨다.
  • 3의 부모는 4, 4의 부모는 5, 5의 부모는 5 자신이다. 재귀함수로 구현한다.
  • 모든 자식 노드가 부모에 대한 포인터를 가져야 한다.
  • Union(합치기) 연산한 노드를 다른 노드의 자식으로 넣기 전에 먼저 양 트리의 루트를 찾아야 한다.
  • 각 트리의 루트를 찾고, 하나를 다른 한쪽의 자손으로 이어준다.

그림으로 보는 Union-Find (벡터로 구현)

  1. 초기화노드 i의 부모 노드를 P[i]에 저장한다.
  2. 처음에 모든 노드의 부모는 자기 자신이다.
  3. 초기화
  4. Union
    )
    4의 부모는 3으로 변경된다.
    )
    Union 연산이 끝난 후, 배열은 다음과 같이 갱신된다.
  5. 1, 2, 3의 부모는 모두 1이므로, 세 정점은 같은 집합에 속한다.
  6. 3의 부모는 2가 된다.
  7. 1, 2, 3이 연결된다면?
  8. 2의 부모는 1로 변경된다.
  9. Union(1, 2) , Union(3, 4) 연산으로 노드를 2개씩 합친다.
  10. Find
  11. 자신이 속한 집합의 루트를 알아내기 위해 재귀 함수가 사용된다.

(코드) 트리로 상호 배타적 집합 구현


struct NaiveDisjointSet{

    vector<int> parent; // 자신의 부모의 번호를 저장  

    NaiveDisjointSet(int n): parent(n){
        for(int i = 0; i < n; i++){ // 처음에는 자기 자신이 자신의 부모다. (혼자니까)
            parent[i] = i; // 자신이 어떤 부모에 포함됬는지 기록 
        }
    } 

    // u 가 속한 트리의 루트의 번호를 찾아서 반환  // 트리의 높이에 비례하는 시간이 걸린다
    int find(int u) const{
        if(u == parent[u]){ // u가 부모라면 
            return u;
        }

        return find(parent[u]);
    } 

    // u가 속한 트리와 v가 속한 트리를 합친다  
    void merge(int u, int v){

        u = find(u); // 양 트리의 루트를 찾아낸다 
        v = find(v);

        if(u == v) return; // u와 v가 같은 트리에 속한 경우 종료. 

        parent[u] = v; 
    } 

}; 

union은 예약어이므로 함수명으로 사용할 수 없다.

상호 배타적 집합의 최적화

  • 트리를 사용하니까 연산 순서에 따라 한쪽으로 기울어질 수도 있다는 문제가 있다.
  • 그러면 Find 연산도 Union 연산도 시간 복잡도 O(n)이 된다.

랭크에 의한 합치기(union-by-rank)

기울어진 트리를 피하기 위해, 항상 높이가 더 낮은 트리를 더 높은 트리 밑에 집어넣자.

(코드) 최적화된 상호 배타적 집합의 구현

rank[] 는 해당 노드가 한 트리의 루트인 경우, 해당 트리의 높이를 저장하는 배열

두 노드를 합칠 때, 높이가 낮은 쪽을 높은 쪽 트리의 서브트리로 포함시킨다.

두 트리의 높이가 같다면, 결과 트리의 높이를 1 증가시킨다.


struct OptimizedDisjointSet{

    vector<int> parent; // 자신의 부모의 번호를 저장  
    vector<int> rank;   // 높이를 저장  

    OptimizedDisjointSet(int n): parent(n), rank(n, 1){
        for(int i = 0; i < n; i++){
            parent[i] = i;
        }
    } 

    // u 가 속한 트리의 루트의 번호를 찾아서 반환 
    int find(int u) const{
        if(u == parent[u]){ // u가 부모라면 
            return u;
        }

        return parent[u] = find(parent[u]); // 재귀호출 하면서 parent벡터 업데이트
    } 

    // u가 속한 트리와 v가 속한 트리를 합친다  
    void merge(int u, int v){

        u = find(u); // 양 트리의 루트를 찾아낸다 
        v = find(v);

        if(u == v) return; // u와 v가 같은 트리에 속한 경우 종료. 

        // v를 u의 자식으로 넣는다. 
        if(rank[u] > rank[v]) swap(u, v); 

        // u를 v의 자식으로 넣는다. 
        parent[u] = v; 

        // 높이가 같으면 결과 트리의 높이를 1 증가 
        if(rank[u] == rank[v]) ++rank[v];  

}; 

[출처]

트리를 이용한 상호 배타적 집합 그림 출처

유니온 파인드 그림 출처

유니온 파인드 개념

알고리즘 문제해결전략 (구종만 저) 트리 챕터 상호배타적집합 코드

728x90

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

이진 탐색 트리 Binary Search Tree (linked list)  (0) 2020.03.18
이진 탐색  (0) 2020.03.18
수식 이진 트리 (Expression Binary Tree)  (0) 2020.03.11
이진 트리  (0) 2020.03.11
트리 기초  (0) 2020.03.11

수식 트리 

 

수식을 표현하는 이진트리 입니다. 

 

수식트리는 아래의 두 가지 규칙을 따릅니다.

 

피연산자는 잎노드 이다. 

연산자는 루트노드 또는 가지노드이다.  

 

 

수식 트리 만들기 - Build() 함수 

 

연산자의 경우, 토큰으로 새 노드를 생성하여 연결합니다.

그리고 Build() 함수를 재귀호출 하여 다음에 오는 노드 두개를 새 노드의 자식으로 연결합니다. 

 

피연산자의 경우, 토큰으로 새 노드를 생성합니다. 

 

// 수식 트리 구축하기 
void BuildTree(char* postfixExp, Node** node) {

	// 맨 뒤 부터 하나씩 토큰을 읽는다. 
	int len = strlen(postfixExp);
	char token = postfixExp[len - 1];
	postfixExp[len-1] = '\0';

	switch (token) {
		//연산자라면, 2번 BuildTree 재귀호출.
	case '+': case '-': case '*': case '/':
		(*node) = CreateNode(token); //노드에 연결 
		BuildTree(postfixExp, &(*node)->right); // 식을 '뒤부터'읽으니까 오른쪽부터.
		BuildTree(postfixExp, &(*node)->left);
		break;
		
	default: //피연산자라면 노드 생성 
		(*node) = CreateNode(token);
		break;
	}
}

 

 

수식 트리 계산하기 - Calculate() 함수

 

연산자인 경우, 두 자식의 아래 노드들의 연산 결과가 먼저 필요하므로,

두 자식을 파라미터로 넘겨서 Calculate() 함수를 재귀 호출합니다.

  

피연산자인 경우, 토큰에 담겨있던 수를 double형으로 변환하여 반환합니다. 

 

double Calculate(Node* tree) {
	
	double left, right, result = 0;
	char tempChar[2];

	if (tree == NULL) {
		return 0;
	}

	switch (tree->data) {
		case '+': case '-': case '*': case '/':
			left = Calculate(tree->left);
			right = Calculate(tree->right);

			if (tree->data == '+') { 
				result = left + right; 
			}
			else if (tree->data == '-') {
				result = left - right;
			}
			else if (tree->data == '*') {
				result = left * right;
			}
			else if (tree->data == '/') {
				result = left / right;
			}
			break;
		default: //피연산자인 경우,
			memset(tempChar, 0, sizeof(tempChar));
			tempChar[0] = tree->data;
			result = atof(tempChar);
			break;
	}

	return result;
}

 

728x90

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

이진 탐색  (0) 2020.03.18
분리 집합 (유니온파인드) c++  (0) 2020.03.18
이진 트리  (0) 2020.03.11
트리 기초  (0) 2020.03.11
링크드 큐  (0) 2020.03.10

이진 트리란?

 

모든 노드가 2개의 자식을 가진 트리입니다.

 

 

이진 트리의 순회 

 

전위 순회, 중위 순회, 후위 순회 세 가지 방식이 있습니다.  

'루트 노드' 중심으로 생각하면 어렵지 않습니다. 

루트 노드를 가장 먼저 가면 전위 순회.

중간에 루트노드에 가면 중위 순회.

루트 노드를 맨 마지막에 가면 후위 순회입니다. 

 

 

 

전위 순회 

 

루트 -> 왼쪽 -> 오른쪽  순으로 방문합니다. 

 

/* 전위 순회 */
void Preorder(Node* node) {

	// 루트 -> 왼쪽 -> 오른쪽 

	if (NULL == node) {
		return;
	}

	printf("%d\n", node->data);
	
	Preorder(node->leftChild);

	Preorder(node->rightSibling);

}

 

중위 순회 

 

왼쪽 -> 루트 -> 오른쪽 순으로 방문합니다. 

 

/* 중위 순회 */
void Inorder(Node* node) {

	// 왼쪽 -> 루트 -> 오른쪽

	if (NULL == node) {
		return;
	}

	Inorder(node->leftChild);

	printf("%d\n", node->data);

	Inorder(node->rightSibling);

}

 

후위 순회

 

왼쪽 -> 오른쪽 -> 루트  순으로 방문합니다. 

/* 후위 순회 */
void Postorder(Node* node) {

	// 왼쪽 -> 오른쪽 -> 루트

	if (NULL == node) {
		return;
	}

	Postorder(node->leftChild);
	
	Postorder(node->rightSibling);

	printf("%d\n", node->data);

}

 

728x90

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

분리 집합 (유니온파인드) c++  (0) 2020.03.18
수식 이진 트리 (Expression Binary Tree)  (0) 2020.03.11
트리 기초  (0) 2020.03.11
링크드 큐  (0) 2020.03.10
순환 큐  (0) 2020.03.09

트리란? 

 

뿌리에서 시작하여 가지, 잎으로 이루어진 나무와 비슷한 자료구조. 

운영체제의 파일 시스템, DOM 구조가 '트리'를 활용합니다. 

 

 

 

트리의  구성 요소 

 

부모 : 1번 노드는 2,3,4번 노드의 부모입니다. 

자식 : 2,3,4번 노드는 1번 노드의 자식입니다. 

 

루트(root) : 트리의 맨 꼭대기 '1'번 노드를 루트 노드라고 합니다.

가지 : 트리와 잎노드 사이의 모든 노드들을 뜻합니다. 

잎 노드(terminal node): 가장 끝의 노드, 단말 노드입니다. 

 

경로 : 3번 노드에서 10번 노드까지의 '경로'는 3, 7, 10 입니다. 

길이 : 3번 노드에서 10번 노드까지 2번 이동해야 하므로, 길이는 2입니다. 

노드의 깊이 : 루트 노드에서 해당 노드까지의 길이입니다. 예를 들어, 6번 노드의 깊이는 2입니다.  

(루트의 깊이는 0 입니다. )

 

레벨 : 깊이가  같은 노드의 집합입니다. 예를 들어, 2, 3, 4번 노드는 깊이가 1로 같으므로 레벨1 입니다. 

차수 : 자식 노드의 개수 입니다. 

트리의 차수 : 차수가 최대인 노드의 차수. 아래 그림에서는 1번 노드가 자식이 3개여서, 트리의 차수는 3입니다. 

루트 노드와 잎 노드를 까맣게 표시해 보았다!

 


 

트리 구현

 

왼쪽 자식-오른쪽 형제 표현법(Left Child Right Sibling)을 이용하여 노드를 만들고 부모 자식을 연결합니다. 

 

 

노드

 

링크드 리스트랑 똑같습니다.

typedef struct Node {
	struct Node* leftChild;
	struct Node* rightSibling;
	int data;
};

 

노드 생성 및 메모리 해제 

 

왼쪽 자식을 가리키는 포인터와 오른쪽 형제를 가리키는 포인터를 초기화 해줍니다.  

Node* CreateNode(int data) {

	Node* newNode = (Node*)malloc(sizeof(Node));
	newNode->leftChild = NULL;
	newNode->rightSibling = NULL;
	newNode->data = data;

	return newNode;
}

 

루트의 왼쪽 자식들 오른쪽 자식들을 free()하고, 루트 자신을 메모리 해제한다. 

/* 트리의 노드 전부 삭제 후, 루트 삭제. */
void FreeTree(Node* rootNode) {

	if (NULL != rootNode->leftChild) {
		FreeTree(rootNode->leftChild);
	}

	if (NULL != rootNode->rightSibling) {
		FreeTree(rootNode->rightSibling);
	}

	rootNode->leftChild = NULL;
	rootNode->rightSibling = NULL;

	FreeNode(rootNode);
}

 

 

자식 노드 연결 

 

부모 노드와 자식 노드 포인터를 매개변수로 입력 받습니다. 

자식이 없다면, 자식에 할당해줍니다. 

자식이 있다면, rightSibling 포인터를 이용하여 마지막 자식을 찾아내서 새 자식(?)을 연결합니다. 

/* 부모에게 자식 연결 */
void ConnectChild(Node** parent, Node* child) {

	if (NULL == (*parent)->leftChild) { // 자식이 없다면, 
		(*parent)->leftChild = child;
	}
	else {// 자식이 있다면, 끝을 찾아서 연결한다.
		Node* lastChild = (*parent)->leftChild;

		while (NULL != lastChild->rightSibling) {
			lastChild = lastChild->rightSibling;
		}

		lastChild->rightSibling = child;
	}
}

 

 

트리를 순회하며 출력 

 

자식과 형제를 출력하기 위해 함수를 재귀 호출 합니다.  

/* 트리 순회하며 출력 */
void Print(Node* node, int depth) {

	int num = 0;

	// 깊이 만큼 들여쓰기 
	for (num = 0; num < depth; num++) {
		printf(" ");
	}

	// 데이터 출력 
	printf("%d\n", node->data);

	// depth를 더해서 자식노드를 출력하도록 함수 재귀 호출 
	if (NULL != node->leftChild) {
		Print(node->leftChild, depth++);
	}

	// 형제노드를 출력하도록 함수 재귀 호출 (depth 유지.)
	if (NULL != node->rightSibling) {
		Print(node->rightSibling, depth);
	}
	
}

 

728x90

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

수식 이진 트리 (Expression Binary Tree)  (0) 2020.03.11
이진 트리  (0) 2020.03.11
링크드 큐  (0) 2020.03.10
순환 큐  (0) 2020.03.09
링크드 리스트로 구현하는 스택  (0) 2020.02.19

링크드 큐 

 

링크드 리스트로 만든 큐입니다.

배열로 만든 순환 큐와는 달리 공백/포화를 구분할 필요가 없습니다. 

 

head 노드에서 제거하고, tail 노드에서 삽입합니다. 

 

 

 

노드 선언과 큐 

 

노드는 링크드 리스트의 노드와 똑같습니다. 

링크드 큐 구조체는 전단과 후단을 가리킬 포인터와 총 노드 개수 변수를 가집니다. 

typedef struct Node {
	char* data;
	struct Node* nextNode;
};

typedef struct LQ {
	struct Node* front;
	struct Node* rear;
	int nodeCnt;
};

 

노드 생성과 메모리 해제

void CreateQueue(LQ** lq) {

	(*lq) = (LQ*)malloc(sizeof(LQ));
	(*lq)->front = NULL;
	(*lq)->rear = NULL;
	(*lq)->nodeCnt = 0;
}

 

삽입

/* 후단에 삽입 */
void Enqueue(LQ* lq, Node* newNode) {

	if (NULL == lq->front) {
		lq->front == newNode;
		lq->rear == newNode;
	}
	else {
		lq->rear->nextNode = newNode;
		lq->rear = newNode;
	}

	lq->nodeCnt++;

}

 

제거 

/* 전단에서 제거 */
Node* Dequeue(LQ* lq) {

	Node* out = lq->front;

	if (NULL == lq->front->nextNode) {
		lq->front = NULL;
		lq->rear = NULL;
	}
	else {
		lq->front = lq->front->nextNode;
	}

	lq->nodeCnt--;

	return out;
}

 

728x90

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

이진 트리  (0) 2020.03.11
트리 기초  (0) 2020.03.11
순환 큐  (0) 2020.03.09
링크드 리스트로 구현하는 스택  (0) 2020.02.19
배열로 구현하는 스택  (0) 2020.02.18

 

큐는 먼저 들어온 일부터 먼저 처리하는 자료구조 입니다. 

선입 선출, First In First Out (FIFO) 라고 합니다. 

 

 

 

큐의 기능 

 

큐의 가장 앞 요소를 전단, 마지막 요소를 후단이라고 합니다. 

(스택은 최상위 노드가 있는 입구에서만 삽입/제거가 수행됩니다. )

큐는 전단에서 Dequeue (제거) 되고, 후단에서만 Enqueue (삽입) 됩니다. 

 

 

 

순환 큐 

 

유의할 점은 큐가 비었는지 가득찼는지 구분을 위해 큐를 실제 용량보다 1 크게 만드는 것입니다. 

 

용량이 7인 큐를 만든다고 하면, 

구현할 때는 8인 큐를 만듭니다. 

 

비었을 때는 전단과 후단이 같은 곳을 가리킵니다 

큐가 꽉 찼을 때는 후단 바로 다음이 전단이 됩니다. 

이렇게 후단과 전단 사이에 공간을 두어 큐의 상태를 구분합니다. 

 

후단은 실제 데이터가 있는 칸의 바로 다음칸을 가리키고 있음을 유의해야 합니다.

후단 인덱스는 항상 빈 칸을 가리키고 있어야 합니다. 

 


 

순환 큐 구현

 

노드와 큐 구조체를 선언합니다.

typedef struct Node {
	int data;
};

typedef struct Cqueue {
	int capacity;
	int front;
	int rear;
	Node* nodeArray;
};

Cqueue구조체에서 nodeArray 포인터는 자유저장소에 존재하는 배열의 첫번째 칸을 가리킵니다. 

공백과 포화를 구분하기 위해 실제 capacity + 1하기 위한 '더미 노드'를 하나 둡니다.  

 

 

 

큐를 만들고 메모리 해제하는 함수 

 

void CreateCqueue(Cqueue** cqueue, int capacity) {

	(*cqueue) = (Cqueue*)malloc(sizeof(Cqueue));

	(*cqueue)->capacity = capacity;
	(*cqueue)->front = 0;
	(*cqueue)->rear = 0;
	(*cqueue)->nodeArray = (Node*)malloc(sizeof(Node) * (capacity + 1));

}

 

nodeArray 라는 이름의 배열은 사용자가 요구한 용량보다 1개 크게 만들어집니다. 더미 노드를 포함하기 때문입니다. 

 

void FreeCqueue(Cqueue* cqueue) {

	free(cqueue->nodeArray);
	free(cqueue);
}

 

 

삽입(Enqueue)

 

뒤에서 삽입하니까 rear를 관리합니다. 

 

rear는 항상 삽입'할' 타겟 인덱스를 가리키고 있습니다.

즉, 빈 공간을 가리키고 있다는 뜻입니다. 

capacity + 1인 인덱스는 공백/포화를 구분할 더미노드의 위치입니다. 

void Enqueue(Cqueue* cqueue, int data) {

	int position = 0;

	if (cqueue->capacity + 1 == cqueue->rear) {
		position = cqueue->rear;
		cqueue->rear = 0;
	}
	else {
		position = cqueue->rear++;
	}

	cqueue->nodeArray[position].data = data;
}

 

 

제거(Dequeue)

 

앞에서 제거하니까 front를 관리합니다. 

 

front와 capacity가 같은 상황은 전단이 배열 맨 끝에 와있다는 것입니다. 

노드를 하나 제거 하면, 인덱스를 0으로 돌려놔야 합니다. 

이 경우를 제외하면 front를 하나 감소시키면 됩니다.

int Dequeue(Cqueue* cqueue) {

	int position = cqueue->front;

	if (cqueue->front == cqueue->capacity ) {
		cqueue->front = 0;
	}else{
		cqueue->front++;
	}
	
	return cqueue->nodeArray[position].data;
}

 

 

큐의 공백과 포화 확인하기 

 

공백 : front 와 rear 가 같은 곳을 가리키고 있습니다. 

int IsEmpty(Cqueue* cqueue) {
	return 	(cqueue->front == cqueue->rear);
}

 

포화: rear의 바로 다음이 front 입니다. 또는 rear에서 front를 빼면 그 길이가 capacity가 됩니다. 

int IsFull(Cqueue* cqueue) {
	if (cqueue->front < cqueue->rear) {
		return (cqueue->rear - cqueue->front == cqueue->capacity);
	}
	else {
		return (cqueue->front == cqueue->rear + 1);
	}
}

 

 

메인함수 - 삽입/제거 해보기 

 

5개를 저장할 수 있는 배열을 만들고, 8번 삽입, 6번 제거해봤습니다. 

삽입 전에 포화인지 검사하고 포화이면 탈출합니다. 

제거 전에 비었는지 검사하고 비었으면 탈출합니다. 

 

콘솔 결과

 

int main(void) {

	int i = 0; 
	Cqueue* queue;

	// 5개를 저장할 수 있는 배열 
	CreateCqueue(&queue, 5);

	// 8번 삽입 
	for (int cnt = 1; cnt < 9; cnt++) {
		if (!IsFull(queue)) {
			printf("%d를 삽입.\n", cnt);
			Enqueue(queue, cnt);
		}
		else {
			printf("큐가 꽉 찼습니다.\n");
			break;
		}
	}

	// 6번 제거
	for (int cnt = 1; cnt < 7; cnt++) {
		if (!IsEmpty(queue)) {
			printf("%d 를 제거.\n", Dequeue(queue));
		}
		else {
			printf("큐가 비었습니다.\n");
			break;
		}
	}

	return 0;
}
728x90

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

트리 기초  (0) 2020.03.11
링크드 큐  (0) 2020.03.10
링크드 리스트로 구현하는 스택  (0) 2020.02.19
배열로 구현하는 스택  (0) 2020.02.18
환형 링크드 리스트  (0) 2020.02.17

스택과 스택의 노드 

 

링크드 리스트는 배열과 달리 인덱스로 노드에 접근할 수 없습니다. 

리스트를 쓰면 배열과는 달리 용량에 제한을 두지 않아도 된다는 장점이 있습니다. 

그러나 '인덱스'가 없기 때문에 자신의 위에 위치하는 노드를 가리키는 포인터가 필요합니다. 

( 자신의 위에 위치한다: 스택 구조에서 자기 노드를 기준으로 최상위 노드 방향을 '위'라고 한 것입니다. )

typedef struct Node {
	char* data;
	struct Node* nextNode;
}Node;

 

스택의 구조체입니다. 

배열과는 다르게 최대길이, 최상위 노드의 인덱스가 필요 없습니다. 

그 대신 리스트의 헤드와 테일을 가리키는 포인터가 필요합니다. 

스택의 구조체에서 

헤드는 리스트의 '시작점'을 뜻하고, 테일은 '최상위 노드'를 가리킵니다. 

 

typedef struct LLStack {
	Node* list;
	Node* top;
}LLStack;

 

 

스택생성

 

먼저, 스택을 자유저장소(==스택메모리)에 생성하는 CreateStack() 함수를 만듭니다. 

 

void CreateStack(LLStack** stack) {

	(*stack) = (LLStack*)malloc(sizeof(LLStack));
	(*stack)->list = NULL;
	(*stack)->top = NULL;

}

 

 

노드 생성 

노드 만큼의 메모리를 할당 합니다. 

문자열의 길이와 NULL문자에 필요한 1을  포함한 길이의 메모리를 할당받습니다. 

그곳을 newNode의 data가 가리키도록 합니다. 

자유저장소에 문자열을 저장해야 하므로, strcpy()를 이용하여 저장합니다. 

strcpy() 와 strlen() 함수를 쓰려면 #include <string.h> 선언이 필요합니다.  

Node* CreateNode(char* data) {

	Node* newNode = (Node*)malloc(sizeof(Node));
	newNode->data = (char*)malloc(strlen(data) + 1);
	strcpy(newNode->data, data);
	newNode->nextNode = NULL;

	return newNode;
}

 

 

노드를 메모리에서 해제 

문자열을 가리키는 포인터를 메모리에서 해제 후, 노드를 메모리에서 해제합니다. 

void FreeNode(Node* targetNode) {
	free(targetNode->data);
	free(targetNode);
}

 

스택을 메모리에서 해제 

 

스택 제거에 앞서 노드들을 메모리에서 전부 해제합니다.

마지막으로 스택을 메모리 해제 합니다. 

// 스택의 메모리해제 
void FreeStack(LLStack* targetStack) {

	while (!IsEmpty(targetStack)) {
		Node* popped = Pop(targetStack);
		FreeNode(popped);
	}

	free(targetStack);
}

 

 

 

Push 연산 

 

스택이 비었다면 새 노드가 헤드노드가 됩니다. 

그렇지 않다면, 테일 노드를 찾아서 그 뒤에 새 노드를 붙입니다. 

/* Push */
void Push(LLStack* stack, Node* newNode) {
	printf("[ Push ] %s 삽입 \n", newNode->data);

	if (NULL == stack->list) { // 스택이 비었다면, 뉴노드가 헤드.
		stack->list = newNode;
	}
	else {
		// 테일 노드를 찾아서, 그 뒤에 뉴 노드를 붙인다.

		Node* tempNode = stack->list;

		while (NULL != tempNode->nextNode) {
			tempNode = tempNode->nextNode;
		}

		tempNode->nextNode = newNode;
	}

	stack->top = newNode;
}

 

 다음과 같이 'top' 뒤에 새노르를 추가해도 됩니다. 

void Push(LLStack* stack, Node* newNode) {

	if (stack->top == NULL) {
		stack->list = newNode;
	}
	else {  // 테일 뒤에 붙인다 
		stack->top->nextNode = newNode;
	}

	stack->top = newNode;
}

 

 

 

Pop 연산

 

Pop 연산은 두 가지 경우를 처리합니다.  

헤드가 탑인 경우 (스택에 노드가 하나 밖에 없다)

탑 직전 노드를 찾아야 하는 경우 (탑 직전 노드를 탑 노드로 갱신해준다)

Node* Pop(LLStack* stack) {

	Node* topNode = stack->top;
	   
	Node* current = stack->list;

	if (current == topNode) { // 헤드가 탑이라면, 1개 남은 노드를 제거하는 상황.
		stack->top = NULL;
		stack->list = NULL;
	}
	else { 	// top의 직전 노드를 찾는다

		while (current->nextNode != stack->top) {
			current = current->nextNode;
		}

		current->nextNode = NULL;
		stack->top = current;		//top 갱신
	}
	
	return topNode;
}

 

 

스택이 비어있는지 확인 

int IsEmpty(LLStack* stack) {
	return (NULL == stack->list);
}

 

 

스택에 노드가 몇개 있는지 확인 

int GetSize(LLStack* stack) {
	
	Node* temp = stack->list;
	int count = 0;

	while (temp != NULL) {

		temp = temp->nextNode;
		count++;

	}

	return count;
}

 

728x90

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

링크드 큐  (0) 2020.03.10
순환 큐  (0) 2020.03.09
배열로 구현하는 스택  (0) 2020.02.18
환형 링크드 리스트  (0) 2020.02.17
더블 링크드 리스트  (0) 2020.02.14

+ Recent posts