1. 리스트 추상 데이터 타입
- 리스트(list) 또는 선형 리스트(linear list)는 자료를 정리하는 방법 중의 하나이다.
- 항목들 간에 순서가 있다.
- 리스트 ADT
객체 : n개의 element형으로 구성된 순서 있는 컬렉션
[ 연산 ]
- add_last(list, item) : 맨 끝에 요소를 추가한다.
- add_first(list, item) : 맨 앞에 요소를 추가한다.
- add(list, pos, item) : pos 위치에 요소를 추가한다.
- delete(list, pos) : pos 위치의 요소를 제거한다.
- clear(list) : 리스트의 모든 요소를 제거한다.
- replace(list, pos, item) : pos 위치의 요소를 item으로 바꾼다.
- is_in_list(list, item) : item이 리스트 안에 있는지를 검사한다.
- get_entry(list, pos) : pos 위치의 요소를 반환한다.
- get_length(list) : 리스트의 길이를 구한다.
- is_empty(list)
- is_full(list)
- display(list)
2. 배열로 구현된 리스트
- 삽입과 삭제 시에 상당한 오버헤드가 따른다.
#define MAX_LIST_SIZE 100 // 배열의 최대크기
typedef int element;
typedef struct {
int list[MAX_LIST_SIZE]; // 배열 정의
int length; // 현재 배열에 저장된 자료들의 개수
} ArrayListType;
// 오류 처리 함수
void error(char *message)
{
fprintf(stderr, "%s\n", message);
exit(1);
}
// 리스트 초기화
void init(ArrayListType *L)
{
L->length = 0;
}
// 리스트가 비어 있으면 1을 반환
// 그렇지 않으면 0을 반환
int is_empty(ArrayListType *L)
{
return L->length == 0;
}
// 리스트가 가득 차 있으면 1을 반환
// 그렇지 많으면 1을 반환
int is_full(ArrayListType *L)
{
return L->length == MAX_LIST_SIZE;
}
// 리스트 출력
void display(ArrayListType *L)
{
int i;
for (i = 0; i<L->length; i++)
printf("%d\n", L->list[i]);
}
// position: 삽입하고자 하는 위치
// item: 삽입하고자 하는 자료
void add(ArrayListType *L, int position, element item)
{
if (!is_full(L) && (position >= 0) &&
(position <= L->length)) {
int i;
for (i = (L->length - 1); i >= position; i--)
L->list[i + 1] = L->list[i];
L->list[position] = item;
L->length++;
}
}
// position: 삭제하고자 하는 위치
// 반환값: 삭제되는 자료
// position: 삭제하고자 하는 위치
// 반환값: 삭제되는 자료
element del(ArrayListType *L, int position)
{
int i;
element item;
if (position < 0 || position >= L->length)
error("위치 오류");
item = L->list[position];
for (i = position; i<(L->length - 1); i++)
L->list[i] = L->list[i + 1];
L->length--;
return item;
}
//
void main()
{
ArrayListType list1;
ArrayListType *plist;
// ListType를 정적으로 생성하고 ListType를 가리키는
// 포인터를 함수의 파라미터로 전달한다.
init(&list1);
add(&list1, 0, 10);
add(&list1, 0, 20);
add(&list1, 0, 30);
display(&list1);
// ListType를 동적으로 생성하고 ListType를 가리키는
// 포인터를 함수의 파라미터로 전달한다.
plist = (ArrayListType *)malloc(sizeof(ArrayListType));
init(plist);
add(plist, 0, 10);
add(plist, 0, 20);
add(plist, 0, 30);
display(plist);
free(plist);
}
3. 연결 리스트
- 배열의 단점은 크기가 고정된다는 것이다. 그리고 삽입/삭제 시 기존의 데이터들을 이동하여야 한다.
- 연결된 표현(linked representation)은 다른 여러 가지의 동적 자료구조, 즉 스택, 큐, 트리, 그래프등을 구현하는 데 널리 쓰인다.
1) 단순 연결 리스트(Singly linked list)
- 첫 번째 노드를 가리키는 하나의 포인터만 있으면 충분한다.
- 보통 이 포인터는 헤드 포인터(head pointer)라고 불린다.
(1) 삽입 연산
- head가 NULL인 경우 : head값을 변경
- p(선행노드 포인터)가 NULL인 경우 : 리스트의 맨 앞에 삽입
- head와 p가 NULL이 아닌 경우 : p 뒤에 삽입
// phead: 리스트의 헤드 포인터의 포인터
// p : 선행 노드
// new_node : 삽입될 노드
void insert_node(ListNode **phead, ListNode *p, ListNode *new_node)
{
if (*phead == NULL) { // 공백리스트인 경우
new_node->link = NULL;
*phead = new_node;
}
else if (p == NULL) { // p가 NULL이면 첫번째 노드로 삽입
new_node->link = *phead;
*phead = new_node;
}
else { // p 다음에 삽입
new_node->link = p->link;
p->link = new_node;
}
}
(2) 삭제 연산
- p가 NULL인 경우 : 첫 번째 노드를 삭제
- p가 NULL이 아닌 경우
// phead : 헤드 포인터에 대한 포인터
// p: 삭제될 노드의 선행 노드
// removed: 삭제될 노드
void remove_node(ListNode **phead, ListNode *p, ListNode *removed)
{
if (p == NULL)
*phead = (*phead)->link;
else
p->link = removed->link;
free(removed);
}
(3) 그 외 몇 가지 함수
- concat, reverse
ListNode *concat(ListNode *head1, ListNode *head2)
{
ListNode *p;
if( head1 == NULL ) return head2;
else if( head2 == NULL ) return head1;
else {
p = head1;
while( p->link != NULL )
p = p->link;
p->link = head2;
return head1;
}
}
ListNode *reverse(ListNode *head)
{
// 순회 포인터로 p, q, r을 사용
ListNode *p, *q, *r;
p = head; // p는 역순으로 만들 리스트
q = NULL; // q는 역순으로 만들 노드
while (p != NULL){
r = q; // r은 역순으로 된 리스트. r은 q, q는 p를 차례로 따라간다.
q = p;
p = p->link;
q->link =r; // q의 링크 방향을 바꾼다.
}
return q; // q는 역순으로 된 리스트의 헤드 포인터
}
[ 전체 테스트 프로그램 ]
#include <stdio.h>
#include <stdlib.h>
typedef int element;
typedef struct ListNode {
element data;
struct ListNode *link;
} ListNode;
// phead: 리스트의 헤드 포인터의 포인터
// p : 선행 노드
// new_node : 삽입될 노드
void insert_node(ListNode **phead, ListNode *p, ListNode *new_node)
{
if (*phead == NULL) { // 공백리스트인 경우
new_node->link = NULL;
*phead = new_node;
}
else if (p == NULL) { // p가 NULL이면 첫번째 노드로 삽입
new_node->link = *phead;
*phead = new_node;
}
else { // p 다음에 삽입
new_node->link = p->link;
p->link = new_node;
}
}
// phead : 헤드 포인터에 대한 포인터
// p: 삭제될 노드의 선행 노드
// removed: 삭제될 노드
void remove_node(ListNode **phead, ListNode *p, ListNode *removed)
{
if (p == NULL)
*phead = (*phead)->link;
else
p->link = removed->link;
free(removed);
}
void display(ListNode *head)
{
ListNode *p = head;
while (p != NULL) {
printf("%d->", p->data);
p = p->link;
}
printf("\n");
}
void display_recur(ListNode *head)
{
ListNode *p = head;
if (p != NULL) {
printf("%d->", p->data);
display_recur(p->link);
}
}
ListNode *search(ListNode *head, int x)
{
ListNode *p;
p = head;
while (p != NULL) {
if (p->data == x) return p; // 탐색 성공
p = p->link;
}
return p; // 탐색 실패일 경우 NULL 반환
}
// 으로 변경
ListNode *concat(ListNode *head1, ListNode *head2)
{
ListNode *p;
if (head1 == NULL) return head2;
else if (head2 == NULL) return head1;
else {
p = head1;
while (p->link != NULL)
p = p->link;
p->link = head2;
return head1;
}
}
// 로 변환
ListNode *reverse(ListNode *head)
{
// 순회 포인터로 p, q, r을 사용
ListNode *p, *q, *r;
p = head; // p는 역순으로 만들 리스트
q = NULL; // q는 역순으로 만들 노드
while (p != NULL) {
r = q; // r은 역순으로 된 리스트. r은 q, q는 p를 차례로 따라간다.
q = p;
p = p->link;
q->link = r; // q의 링크 방향을 바꾼다.
}
return q; // q는 역순으로 된 리스트의 헤드 포인터
}
// 노드를 동적으로 생성하는 프로그램
ListNode *create_node(element data, ListNode *link)
{
ListNode *new_node;
new_node = (ListNode *)malloc(sizeof(ListNode));
if (new_node == NULL) {
fprintf(stderr, "메모리 할당 에러\n");
exit(1);
}
new_node->data = data;
new_node->link = link;
return new_node;
}
// 테스트 프로그램
void main()
{
ListNode *list1 = NULL, *list2 = NULL;
ListNode *p;
// list1 = 30->20->10
insert_node(&list1, NULL, create_node(10, NULL));
insert_node(&list1, NULL, create_node(20, NULL));
insert_node(&list1, NULL, create_node(30, NULL));
display_recur(list1);
// list1 = 20->10
remove_node(&list1, NULL, list1);
display(list1);
// list2 = 80->70->60
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 = list1 + list2
list1 = concat(list1, list2);
display(list1);
// list1을 역순으로
list1 = reverse(list1);
display(list1);
// list1에서 20 탐색
p = search(list1, 20);
printf("탐색성공: %d\n", p->data);
}
※ 출처 - 천인국, 공용해, 하상호, 「C언어로 쉽게 풀어쓴 자료구조」, 생능출판, 2009년