연결 리스트에 대한 문제풀이 내용이다.
연결 리스트(Linked List)란?
- 각 노드가 데이터와 포인터를 가지고 한 줄로 연결되어 있는 방식으로 데이터를 저장하는 자료구조이다.
- 메모리를 동적 할당을 기반으로 구현하여 데이터를 효율적으로 관리할 수 있는 장점이 있다. (배열 한계 극복)
[데이터 | 다음 노드의 주소] → [데이터 | 다음 노드의 주소] → [데이터 | 마지막 NULL]
구조체 리스트 노드를 생성하고 삽입하고 삭제하는 것부터 연습해보고,
리스트 탐색, 합병, 역순, 대체 및 삽입의 위치를 맨앞, 맨뒤 조정하여 함수를 구현해보자.
연결 리스트(주석과 함께 한줄씩 분석)
#include <stdio.h>
#include <stdlib.h>
typedef int element;
typedef struct ListNode{
element data; // 데이타 (값)
struct ListNode *link; // 링크 (주소) 다음 노드의 data 의 주소가 저장 됨
} ListNode;
void error(char *message)
{
fprintf(stderr, "%s\n", message);
}
ListNode *create_node(element data, ListNode *link)
{
ListNode *new_node;
new_node = (ListNode *)malloc(sizeof(ListNode)); // 구조체 포인터 메모리 만큼 동적할당
if(new_node == NULL) error("메모리 할당 에러"); // 생성된 노드가 NULL 이면 에러 메시지
new_node->data = data;
new_node->link = link; // 구조체포인터 new_node 의 data와 link
return new_node; // 그리고 생성된 노드를 리턴
}
void insert_node(ListNode **phead, ListNode *p, ListNode *new_node) // 삽입 (리스트, NULL, creat_node(값, NULL))
{
if (*phead == NULL) // head의 주소가 NULL 이라면 // 앞에 삽입
{
new_node->link = NULL; // new_node 의 link가 NULL을 가리키게 하고
*phead = new_node; // head의 주소에는 new_node를 넣어준다
}
else if(p == NULL) // 뒤에 삽입할 때
{
new_node->link = *phead; // new_node 의 link가 다음 노드의 주소를 가리킴
*phead = new_node; // head의 주소에 new_node를 넣어준다
}
else // 중간에 삽입할 때
{
new_node->link = p->link;
p->link = new_node;
}
}
void remove_node(ListNode **phead, ListNode *p, ListNode *removed) // 노드 제거 (&list, NULL, list)
{
if( p == NULL )
*phead = (*phead)->link; // head의 주소에 head의 주소의 link를 넣어준다
else
p->link = removed->link;
free(removed); // 동적할당 해제
}
void display(ListNode *head)
{
ListNode *p = head;
if(head == NULL) // 리스트가 비어 있을 때
printf("List is empty\n");
else{
while(p != NULL) // 마지막 p가 NULL이 되는 순간 탈출
{
printf("%d->", p->data); // 값을 출력하고
p = p->link; // 다음 p의 주소를 가리킴
}
printf("\n");
}
}
ListNode *search(ListNode *head, element x)
{
ListNode *p;
p = head; // 시작 구조체 포인터 p 에 head 넣어줌
while(p!=NULL) // p가 NULL 이 될 때 까지
{
if(p->data == x) return p; // 값이 탐색할 값과 같은지 확인 후 같으면 p 리턴
p = p->link; // 아니면 계속 다음 노드를 가리킴
}
return p; // 결과적으로 p 리턴
}
ListNode *concat(ListNode *head1, ListNode *head2) // 두개의 리스트를 합병
{
ListNode *p;
if(head1 == NULL) return head2; // 만약 첫번째 리스트에 아무것도 없으면 두번째 리스트만 리턴
else if(head2 == NULL) return head1; // 두번째 리스트가 없으면 첫번째 리스트만 리턴
else // 둘 다 존재 하면
{
p = head1; // p 에 list1 의head 넣고
while(p->link != NULL) // 다음 노드를 계속 가리키다가
p = p->link;
p->link = head2; // NULL 이 되면 list2 의 head를 넣고
return head1; // 첫번째 리스트의 head를 리턴
}
}
ListNode *reverse(ListNode *head){
ListNode *p, *q, *r; // 순회 포인터로 p, q, r 사용
p = head; // 역순으로 만들 리스트
q = NULL; // 역순으로 만들 노드
while(p != NULL){
r = q; // p q r 이 이동하면서 서로 역방향을 가리킴 q 의 앞과 뒤를 검사 해야해서 3개
q = p;
p = p->link;
q->link =r;
}
return q;
}
void add_last(ListNode **head, ListNode *new_node) // 마지막에 삽입
{
ListNode *pNode = *head;
while(pNode->link != NULL) // 마지막까지 진행
{
pNode = pNode->link;
}
pNode->link = new_node; // 마지막 NULL 을 가리키던 링크가 새로운 노드를 받음
}
void add_first(ListNode **head, ListNode *new_node) // 첫번째에 삽입
{
ListNode *pNode = *head; // 구조체 노드를 생성해 head의 주소를 넣어줌
*head = new_node; // head의 주소에 new_node 넣어줌
(*head)->link = pNode; // head 링크가 머리를 가리킴
}
void clear(ListNode **head)
{
ListNode *pNode= *head;
ListNode *pPrev;
if(pNode->link != NULL) // pNode의 주소가 NULL이 아니면
{
while(pNode->link != NULL) // 다음 노드로 넘겨주면서 Prev에 Node 넣어줌
{
pPrev = pNode;
pNode = pNode->link;
}
free(pNode); // Node 동적할당 해제
pPrev->link = NULL; // 받은 모든 요소들 NULL;
clear(head); // 재귀 head로 시작해서 모두 지움
}
else // 이미 다 지워져 있으면 그냥 동적할당 해제
{
free(*head);
*head = NULL;
}
}
void replace(ListNode *pNode, int pos, int data) // 어떤 list의 pos의 replace할 값
{
int i;
for(i=1;i<pos;i++) // 대체할 값이 있는 노드까지 증가
{
pNode = pNode->link; // 계속 다음 노드의 주소를 가리킴
}
pNode->data = data; // 그 노드의 값을 입력한 데이터로 변경
}
int get_entry(ListNode *pNode, int pos) // list의 pos에 위치한 요소
{
int i;
for(i=1;i<pos;i++){
pNode = pNode->link; // pos까지 계속 증가 시킨다음
}
return pNode->data; // 그 값을 리턴
}
int is_empty(ListNode *head)
{
if(head == NULL) // head 가 비어 있느냐
return 1;
return 0;
}
int get_length(ListNode *pNode) // 노드주소가 NULL이 될때까지 탐색 하면서 갯수 셈
{
int i;
for(i=1;pNode->link != NULL;i++, pNode=pNode->link){}
return i;
}
void main(){
ListNode *list1 = NULL, *list2= NULL;
ListNode *p;
insert_node(&list1, NULL, create_node(10, NULL));
insert_node(&list1, NULL, create_node(20, NULL));
insert_node(&list1, NULL, create_node(30, NULL));
remove_node(&list1, NULL, list1);
insert_node(&list2, NULL, create_node(60, NULL));
insert_node(&list2, NULL, create_node(70, NULL));
insert_node(&list2, NULL, create_node(80, NULL));
display(list2);
list1 = concat(list1, list2);
display(list1);
list1 = reverse(list1);
display(list1);
p = search(list1, 20);
printf("탐색성공 : %d\n", p->data);
printf("맨앞에 100추가\n");
add_first(&list1, create_node(100, NULL));
display(list1);
printf("맨뒤에 200추가\n");
add_last(&list1, create_node(200, NULL));
display(list1);
printf("리스트1의 2번째요소를 700으로 치환\n");
replace(list1, 2, 700);
display(list1);
printf("리스트1의 2번째 요소 : %d\n", get_entry(list1, 2));
printf("리스트1의 길이 : %d\n", get_length(list1));
clear(&list1);
}
다음 7장에서는 심화학습으로 연결 리스트를 응용하여 스택과 큐를 구현해보자. (7장 링크)
'학습공간 > C, 자료구조와 알고리즘' 카테고리의 다른 글
[8장] 트리 (0) | 2019.11.25 |
---|---|
[7장] 연결 리스트 II (0) | 2019.11.23 |
[5장] 큐 (0) | 2019.11.22 |
[4장] 스택 (0) | 2019.11.22 |
[3장] 배열, 구조체, 포인터 (0) | 2019.11.21 |