-> 블로그 이전

[Data Structure] 단순 연결 리스트

2021. 12. 9. 22:00Major`/자료구조

단순 연결 리스트 (Singly Linked List)

- 단방향 연결

- 마지막 노드의 링크값 = NULL

- 헤드 포인터 : 연결 리스트의 맨 처음 노드를 가리키는 포인터

 

단순 연결 리스트 ADT

void insert_node(s_listnode **head, s_listnode *pre, s_listnode *new_node) 

  • 삽입연산

void remove_node(s_listnode **head, s_listnode *pre, s_listnode *removed) 

  • 삭제연산

void print_list(s_listnode *head) 

  • 리스트 순서대로 출력

s_listnode *search(s_listnode *head, int value) 

  • 리스트안에 존재하는 value 찾아내기

s_listnode *concat(s_listnode *head1, s_listnode *phead2) 

  • 리스트 2개 연결하는 합병연산

 

노드 정의/생성/삭제

- 정의 : 자기 참조 구조체 이용

- 생성 : malloc()을 호출하여 동적 메모리로 생성

- 삭제 : free()를 호출하여 동적 메모리 해제

 

● 노드 정의

typedef int element;

typedef struct {
	element data;
	struct s_listnode* link; // 다음 노드를 가리키는 링크 포인터
	// ex) p->link : p의 다음 노드, p의 다음노드에 접근하는 방법
}s_listnode;

 

● 노드 생성

s_listnode* p1 = (s_listnode*)malloc(sizeof(s_listnode));
p1->data = 10;
p1->link = NULL;

 

● 노드 연결

s_listnode* p2 = (s_listnode*)malloc(sizeof(s_listnode));
p2->data = 20;
p2->link = NULL;
p1->link = p2;
  • p1->link = p2;를 통해서 p1~>p2 연결

 

 

단순 연결 리스트 ADT 구현

1. 삽입연산

 - void insert_node(s_listnode **phead, s_listnode *pre, s_listnode *new_node)

  • 헤드포인터가 함수 안에서 변경되기 때문에 헤드포인터의 포인터가 필요

 

① head가 NULL인 경우 (공백리스트)

  1. new_node가 첫 번째 노드로 설정
  2. 마지막 노드이기 때문에 new_node의 link는 NULL를 가리키게 한다
  3. head의 주소에 new_node를 넣어준다

 

② pre가 NULL인 경우 (리스트 맨 앞에 new_node 삽입)

  1. 이전 노드가 NULL → new_node가 리스트의 맨 앞 노드로 설정
  2. new_node의 link는 다음 노드의 주소를 가리키게 한다 → 다음 노드의 주소를 가리키는 것 = head
  3. head의 주소에 new_node를 넣어준다

 

③ head, p가 NULL이 아닐 경우 (리스트 중간에 new_node 삽입)

  1. new_node의 link는 다음 노드의 주소를 가리키게 한다 → 다음 노드의 주소를 가리키는 것 = pre->link
  2. pre가 new_node를 가리키게 한다

 

void insert_node(s_listnode** head, s_listnode* pre, s_listnode* new_node) {
	if (*head == NULL) { // 공백리스트 인 경우
		new_node->link = NULL; // 마지막 노드이기 때문에 new_node의 link는 NULL를 가리키게 한다
		*head = new_node; // head의 주소에 new_node를 넣어준다
	}
	else if (pre == NULL) { // pre가 NULL이면 new_node는 첫번째 노드로 삽입
		new_node->link = *head; 
		// new_node의 link는 다음 노드의 주소를 가리키게 한다
		// 다음 노드의 주소를 가리키는 것 = head
		*head = new_node; // head의 주소에 new_node를 넣어준다
	}
	else { // new_node는 pre 다음에 삽입
		new_node->link = pre->link; // new_node의 link는 다음 노드의 주소를 가리키게 한다
		pre->link = new_node; // pre가 new_node를 가리키게 한다
	}
}

void insert_last(s_listnode** head, s_listnode* new_node) {
	s_listnode* p = *head;
	while (p->link != NULL) {
		p = p->link; // 마지막 노드까지 도달
	}
	p->link = new_node; // p가 new_node를 가리키게 한다
}

void insert_first(s_listnode** head, s_listnode* new_node) {
	new_node->link = *head;
	*head = new_node;
}

 

 

2. 삭제연산

- void remove_node(s_listnode **head, s_listnode *pre, s_listnode *removed)

  • free(removed)를 통해서 removed 동적 할당 해제 → removed 삭제

 

① pre가 NULL인 경우

  1. 맨 앞 노드 삭제
  2. → 헤드포인터 변경
  3. head의 주소에 head의 주소의 link를 넣어준다

 

② pre가 NULL이 아닌 경우

  • 중간 노드 삭제
void remove_node(s_listnode** head, s_listnode* pre, s_listnode* removed) {
	if (pre == NULL) {
		*head = (*head)->link;
	}
	else {
		pre->link = removed->link;
	}
	free(removed);
}

 

 

3. 탐색연산

- s_listnode *search(s_listnode *head, int value)

  • 반환값 = value를 가지고 있는 노드의 주소
s_listnode* search(s_listnode* head, element value) {
	// list에 head가 있나 탐색하는 함수
	// 반환값 = value를 가지고 있는 노드의 주소
	s_listnode* p = head;
	while (p != NULL) { // p가 NULL이 될 때 까지
		if (p->data == value) return p;
		p = p->link; // data와 value가 다르면 계속 다음 노드를 가리키기
	}
	return p; // 탐색 실패하면 NULL 리턴
}

 

 

4. 합병연산

- head1과 head2를 연결

  • head1이 NULL → return head2
  • head2이 NULL → return head1
s_listnode* concat(s_listnode* head1, s_listnode* head2) {
	s_listnode* p;
	if (head1 == NULL) return head2;
	else if (head2 == NULL) return head1;
	else {
		p = head1;
		while (p->link != NULL)
			p = p->link; // head1의 마지막 노드까지 도달하기
		p->link = head2; // 마지막 노드가 head2를 가리키게 한다 == head1과 head2 연결
		return head1;
	}
}

 

 

5. Full Code (LinkedList)

#include <stdio.h>
#include <stdlib.h>
// 단순 연결 리스트

typedef int element;

typedef struct {
	element data;
	struct s_listnode* link; // 다음 노드를 가리키는 링크 포인터
	// ex) p->link : p의 다음 노드, p의 다음노드에 접근하는 방법
}s_listnode;

void insert_node(s_listnode** head, s_listnode* pre, s_listnode* new_node) {
	if (*head == NULL) { // 공백리스트 인 경우
		new_node->link = NULL; // 마지막 노드이기 때문에 new_node의 link는 NULL를 가리키게 한다
		*head = new_node; // head의 주소에 new_node를 넣어준다
	}
	else if (pre == NULL) { // pre가 NULL이면 new_node는 첫번째 노드로 삽입
		new_node->link = *head; 
		// new_node의 link는 다음 노드의 주소를 가리키게 한다
		// 다음 노드의 주소를 가리키는 것 = head
		*head = new_node; // head의 주소에 new_node를 넣어준다
	}
	else { // new_node는 pre 다음에 삽입
		new_node->link = pre->link; // new_node의 link는 다음 노드의 주소를 가리키게 한다
		pre->link = new_node; // pre가 new_node를 가리키게 한다
	}
}

void insert_last(s_listnode** head, s_listnode* new_node) {
	s_listnode* p = *head;
	while (p->link != NULL) {
		p = p->link; // 마지막 노드까지 도달
	}
	p->link = new_node; // p가 new_node를 가리키게 한다
}

void insert_first(s_listnode** head, s_listnode* new_node) {
	new_node->link = *head;
	*head = new_node;
}

void remove_node(s_listnode** head, s_listnode* pre, s_listnode* removed) {
	if (pre == NULL) {
		*head = (*head)->link; // head의 주소에 head의 주소의 link를 넣어준다
	}
	else {
		pre->link = removed->link;
	}
	free(removed);
}

void print_list(s_listnode* head) {
	s_listnode* p = head;

	if (p == NULL)
		printf("Error : List is empty\n\n");
	else {
		while (p != NULL) { // p가 NULL이 되는 순간 (마지막 노드에 도착) 탈출
			printf("%d -> ", p->data);
			p = p->link; // 다음 p의 주소 가리키기
		}
		printf("NULL\n\n");
	}
}

s_listnode* search(s_listnode* head, element value) {
	// list에 head가 있나 탐색하는 함수
	// 반환값 = value를 가지고 있는 노드의 주소
	s_listnode* p = head;
	while (p != NULL) { // p가 NULL이 될 때 까지
		if (p->data == value) return p;
		p = p->link; // data와 value가 다르면 계속 다음 노드를 가리키기
	}
	return p; // 탐색 실패하면 NULL 리턴
}

s_listnode* concat(s_listnode* head1, s_listnode* head2) {
	s_listnode* p;
	if (head1 == NULL) return head2;
	else if (head2 == NULL) return head1;
	else {
		p = head1;
		while (p->link != NULL)
			p = p->link; // head1의 마지막 노드까지 도달하기
		p->link = head2; // 마지막 노드가 head2를 가리키게 한다 == head1과 head2 연결
		return head1;
	}
}

int main(void) {
	s_listnode* head1 = NULL;
	s_listnode* p1 = (s_listnode*)malloc(sizeof(s_listnode));
	p1->data = 30; p1->link = NULL;
	s_listnode* p2 = (s_listnode*)malloc(sizeof(s_listnode));
	p2->data = 60; p2->link = NULL;
	s_listnode* p3 = (s_listnode*)malloc(sizeof(s_listnode));
	p3->data = 70; p3->link = NULL;

	s_listnode* head2 = NULL;
	s_listnode* p4 = (s_listnode*)malloc(sizeof(s_listnode));
	p4->data = 300; p4->link = NULL;
	s_listnode* p5 = (s_listnode*)malloc(sizeof(s_listnode));
	p5->data = 600; p5->link = NULL;
	s_listnode* p6 = (s_listnode*)malloc(sizeof(s_listnode));
	p6->data = 700; p6->link = NULL;
	s_listnode* p7 = (s_listnode*)malloc(sizeof(s_listnode));
	p7->data = 1000; p7->link = NULL;
	s_listnode* p8 = (s_listnode*)malloc(sizeof(s_listnode));
	p8->data = 5000; p8->link = NULL;


	printf("---head1---\n");
	printf(">>> insert p1(30) : pre = NULL\n");
	insert_node(&head1, NULL, p1);
	print_list(head1);

	printf(">>> insert p2(60) : pre = NULL\n");
	insert_node(&head1, NULL, p2);
	print_list(head1);

	printf(">>> insert p3(70) : pre = p1\n");
	insert_node(&head1, p1, p3);
	print_list(head1);

	if (search(head1, 50) != NULL)
		printf("리스트에 50이 있습니다!!\n\n");
	else
		printf("리스트에 50이 없습니다.\n\n");
	if (search(head1, 30) != NULL)
		printf("리스트에 30이 있습니다!!\n\n");
	else
		printf("리스트에 30이 없습니다.\n\n");

	printf("---head2---\n");
	printf(">>> insert p4(300) : pre = NULL\n");
	insert_node(&head2, NULL, p4);
	print_list(head2);

	printf(">>> insert p5(600) : pre = p4\n");
	insert_node(&head2, p4, p5);
	print_list(head2);

	printf(">>> insert p6(700) : pre = p5\n");
	insert_node(&head1, p5, p6);
	print_list(head2);

	printf(">>> insert_last p7(1000)\n");
	insert_last(&head2, p7);
	print_list(head2);

	printf(">>> insert_first p8(5000)\n");
	insert_first(&head2, p8);
	print_list(head2);

	printf(">>> remove_node p6(700) : pre = p5\n");
	remove_node(&head2, p5, p6);
	print_list(head2);

	printf(">>> concat(head1, head2)\n");
	concat(head1, head2);
	print_list(head1);

	return 0;
}