리스트(list) 또는 선형 리스트(linear list)의 항목들은 순서 또는 위치를 가짐
객체: n개의 element형으로 구성된 순서 있는 컬렉션
연산:
add_last(list, item) ::= 맨 끝에 요소 추가
add_first(list, itme) ::= 맨 앞에 요소 추가
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) ::= 리스트의 모든 요소 표시
add_last(list1, a)
add_last(list1, b)
add(list1, 2, c)
-> 리스트 (a, b, c)
add(list1, 1, d) // 항목의 위치는 0부터 시작한다고 가정
-> 리스트 (a, d, b, c)
delete(list1, 2)
replace(list1, 1, e)
-> 리스트 (a, e, c)
// 리스트 ADT가 존재한다고 가정
// 슈퍼에서 구입할 것을 리스트로 만들어 순서대로 정리하는 프로그램
main(){
int i, n;
ListType list2; // list2 생성, 구현 방법에 따라 약간씩 다름
add_last(&list2, "마요네즈"); // 리스트의 포인터를 전달
add_last(&list2, "빵");
add_last(&list2, "치즈");
add_last(&list2, "우유");
n = get_length(&list2);
printf("쇼핑해야할 항목의 수는 %d입니다.\n",n);
for(i=0;i<n;i++){
printf("%d 항목은 %s입니다.",i,get_entry(&list2,i));
}
}
[출력 결과]
쇼핑해야할 항목의 수는 4입니다.
0 항목은 마요니즈입니다.
1 항목은 빵입니다.
2 항목은 치즈입니다.
3 항목은 우유입니다.
ListType list3;
add_last(&list3, 10);
add_last(&list3, 20);
delete(&list3,0);
// display(&list3);
int n = get_length(&list3);
for(int i=0;i<n;i++){
printf("%d\n", get_entry(&list3,i));
}
배열은 메모리 안에서 순차적으로 공간이 할당된다고 해서 순차적 표현(sequential representation)이라고 함
#include <stdio.h>
#include <stdlib.h>
#define MAX_LIST_SIZE 100 // 배열 최대 크기
typedef int element; // 배열이 저장되는 항목
typedef struct{
element list[MAX_LIST_SIZE]; // 배열 정의
int length; // 현재 배열에 저장된 자료들의 개수
}ArrayListType;
// 모든 함수에 구조체 포인터가 전달됨 -> 전역 변수를 사용하지 않아도 된다는 장점이 있음
// 오류 처리 함수
void error(const 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, 아니면 0 반환
int is_full(ArrayListType *L){
return L->length == MAX_LIST_SIZE;
}
// 리스트 출력
void display(ArrayListType *L){
for(int i=0;i<L->length;i++){
printf("%d\n",L->list[i]);
}
}
// position: 삽입하고자 하는 위치
// item: 삽입하고자 하는 자료
void add(ArrayListType* L, int position, element item)
{
// length 보다 큰 위치에는 삽입할 수 없음
if(!is_full(L) && (position >=0) && (position <= L->length)){
// 삽입 위치 다음에 있는 자료들을 한 칸씩 뒤로 이동
// 맨 뒤쪽 자료부터 한 칸씩 뒤로 이동해야 함
for (int i = L->length - 1; i >= position; i--) {
L->list[i + 1] = L->list[i];
}
L->list[position] = item;
L->length++;
}
}
// position: 삭제하고자 하는 위치
// 반환 값: 삭제되는 자료
element delete(ArrayListType *L, int position){
if(position <0 || position >= L->length){
error("위치 오류");
}
element item = L->list[position];
// 삭제한 위치부터 맨 끝까지 자료를 한 칸씩 앞으로 이동
// 앞에서 뒤로 이동해야 함
for(int i=position;i<(L->length-1);i++){
L->list[i] = L->list[i+1];
}
L->length--;
return item;
}
int main(){
ArrayListType list1;
ArrayListType* plist;
// ArrayListType의 구조체를 정적으로 생성하고 이 구조체를 가리키는
// 포인터를 함수의 매개변수로 전달
init(&list1);
add(&list1, 0, 10);
add(&list1, 0, 20);
add(&list1, 0, 30);
display(&list1);
// ArrayListType의 구조체를 동적으로 생성하고 이 구조체를 가리키는
// 포이터를 함수의 매개변수로 전달
plist = (ArrayListType*)malloc(sizeof(ArrayListType));
init(plist);
add(plist, 0, 10);
add(plist, 0, 20);
add(plist, 0, 30);
display(plist);
free(plist);
return 0;
}
(배열을 이용한 리스트)
장점: 구현이 간단
단점: 크기가 고정
void clear(ArrayListType*L){
for (int i = 0; i < L->length;i++){
L->list[i] = 0;
}
L->length = 0;
}
void replace(ArrayListType*L,int position, element item){
L->list[position] = item;
}
element get_entry(ArrayListType*L,int position){
return L->list[position];
}
int get_length(ArrayListType*L){
return L->length;
}
연결리스트는 동적으로 크기가 변할 수 있고, 삭제나 삽입시에 데이터 이동이 필요없는 연결된 표현(linked representation)(데이터와 링크로 구성, 링크가 데이터들을 연결하는 역할을 함)임.
연결 리스트(linked list): 물리적으로 흩어져 있는 자료들을 서류 연결하여 하나로 묶는 방법
장점: 삽입, 삭제시 데이터 이동이 필요없음, 데이터 저장 공간을 동적으로 생성할 수 있음
단점: 연산의 구현이나 사용방법이 배열에 비해 복잡하여 오류 발생 가능성이 높음, 링크 필드를 위한 추가 공간 필요
노드(node) = 데이터 필드(data field, 저장하고 싶은 데이터) + 링크 필드(link field, 다른 노드를 가리키는 포인터)
-연결 리스트는 노드의 집합
-노드는 어떤 위치에나 있을 수 있으며, 다른 노드로 가기 위해서는 현재 노드가 가지고 있는 포인터를 이용하면 됨
-연결 리스트에서 첫 번째 노드를 알아야 전체 노드를 접근할 수 있음. 따라서, 첫 번째 노드를 가리키고 있는 변수가 필요한데, 이를 헤드 포인터(head pointer)라고 함
-연결 리스트에서 마지막 노드의 일크는 NULL로 설정, 이는 더 이상 연결된 노드가 없다는 것을 의미함
단순 연결 리스트는 노드들이 하나의 링크 필드를 가지며 이 링크 필드를 이용하여 모든 노드들이 연결되어 있음. 마지막 노드의 링크 필드 값은 NULL이 됨
insert_node(L,before,new)
if L = NULL
then L <- new
else new.link <- before.link
before.link <- new
1. 만약 연결 리스트 L이 공백 상태라면
2. 새로운 노드를 첫 번째 노드로 함
3. 그렇지 않으면 new 노드의 링크가 after를 가리키게 하고(after 노드의 주소가 before 노드의 링크에 저장되어 있음)
4. befor의 링크가 new를 가리키게 함.
remove_node(L,before, removed)
if L != NULL
then befor.link <- removed.link
destory(removed)
1. 만약 연결 리스트 L이 공백 상태가 아니라면
2. before 노드의 링크가 after를 가리키게 하고(after 노드의 주소가 removed 노드의 링크에 저장되어 있음)
3. removed 노드를 삭제함
// 단순 연결 리스트 구조체 생성
typedef int element;
typedef struct ListNode{
element data;
struct ListNode* link;
}ListNode;
// 연결 리스트에서는 필요 시에 노드를 동적으로 생성
ListNode *p1;
p1 = (ListNode *)malloc(struct(ListNode));
p1->data = 10;
p1->link = NULL;
// 두 개의 노드를 연결
ListNode *p2;
p2 = (ListNode *)malloc(struct(ListNode));
p2->data = 20;
p2->link = NULL;
p1->link = p2;
// 메모리 해제
free(p1);
free(p2);
// phead: 리스트의 헤드 포인터 head에 대한 포인터
// 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;
}
}
(1) p가 NULL인 경우: 연결 리스트의 첫 번째 노드를 삭제, 헤드 포인터를 변경하고 removed 노드가 차지하고 있는 공간을 시스템에 반환
(2) p가 NULL이 아닌 경우: 먼저 removed 앞의 노드인 p의 링크가 removed 다음 노드를 가리키도록 변경, 추가 작업으로 removed 노드가 차지하고 있는 공간을 시스템에 반환
// phead: 헤드 포인터 head의 포인터
// 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){ // 노드의 링크 값이 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, element item){
ListNode *p = head;
while(p!=NULL){
if(p->data == item) return p; // 탐색 성공
p = p->link;
}
return p; // 탐색 실패 NULL 반환
}
첫 번째 리스트의 맨 끝으로 간 다음, 마지막 노드의 링크가 두 번째 리스트의 맨 처음 노드를 가리키도록 변경하면 됨, head1, head2가 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;
}
}
3개의 포인터를 이용해서 연결 리스트를 순회하면서 링크의 방향을 역순으로 바꾸면 됨, 링크의 방향을 역순으로 바꾸기 전에 미리 뒤의 노드를 알아놓아야 함
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>
// (추가)오류 처리 함수
void error(const char*message){
fprintf(stderr, "%s\n", message);
exit(1);
}
// (추가)노드를 동적으로 생성하는 프로그램
ListNode * create_node(element data, ListNode *link){
ListNode *new_node;
new_node = (ListNode*)malloc(sizeof(ListNode));
if(new_node == NULL) error("memory allocate error");
new_node->data = data;
new_node->link = link;
return new_node;
}
// 테스트 프로그램
int 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(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("Search Success: %d\n", p->data);
return 0;
}
int get_length(ListNode *head){
int cnt = 0;
ListNode* p = head;
while(p!=NULL){
p = p->link;
cnt++;
}
return cnt;
}
원형 연결 리스트: 리스트의 마지막 노드의 링크가 첫 번째 노드를 가리키는 리스트
-> 마지막 노드는 헤드 포인터인 head가 가리키고 있고, 첫 번째 노드는 head->link가 가리키고 있음
장점: 한 노드에서 다른 모든 노드로의 접근이 가능하기 때문에 삭제나 삽입이 단순 연결 리스트보다는 용이해짐
// phead: 리스트의 헤드 포인터의 포인터
// node: 삽입될 노드
void insert_first(ListNode **phead, ListNode *node){
if(*phead == NULL){
*phead = node;
node->link = node;
}else{
node->link = (*phead)->link;
(*phead)->link = node;
}
}
// phead: 리스트의 헤드 포인터의 포인터
// node: 삽입될 노드
void insert_last(ListNode **phead, ListNode *node){
if(*phead == NULL){
*phead = node;
node->link = node;
}else{
node->link = (*phead)->link;
(*phead)->link = node;
*phead = node; // head의 위치만 새로운 노드로 바꿔주면 새로운 노드가 마지막 노드가 됨
}
}
#include <stdio.h>
#include <stdlib.h>
typedef int element;
typedef struct ListNode{
element data;
struct ListNode* link;
}ListNode;
void error(const char* message){
fprintf(stderr, "%s\n", message);
exit(1);
}
// 노드를 동적으로 생성하는 프로그램
ListNode* create_node(element data, ListNode* link)
{
ListNode* new_node;
new_node = (ListNode*)malloc(sizeof(ListNode));
if (new_node == NULL)
error("memory allocate error");
new_node->data = data;
new_node->link = link;
return new_node;
}
// 리스트의 항목 출력
void display(ListNode* head)
{
ListNode* p;
if (head == NULL)
return;
p = head;
do {
p = p->link;
printf("%d->", p->data);
} while (p != head); // 마지막 노드의 링크가 9이 아니기 때문에 리스트의 끝에 도달했는지를 검사하려면 헤드 포인터와 비교해야 함
printf("\n");
}
int main(){
ListNode* list1 = NULL;
// list1 = 20->10->30
insert_first(&list1, create_node(10, NULL));
insert_first(&list1, create_node(20, NULL));
insert_last(&list1, create_node(30, NULL));
display(list1);
return 0;
}
int get_length(ListNode* head)
{
int cnt = 0;
ListNode* p = head;
do {
p = p->link;
cnt++;
} while (p != head);
return cnt;
}
이중 연결 리스트: 하나의 노드가 선행 노드와 후속 노드에 대한 두 개의 링크를 가지는 리스트
장점: 양방향으로 검색이 가능
단점: 공간을 많이 차지하고, 코드가 복잡해짐
-헤드 노드(head node)라는 특별한 노드를 축하는 경우가 많음 (헤드 포인터와 구별해야 함)
-헤드 포인터: 리스트의 첫 번째 노드를 가리키는 포인터
-헤드 노드: 데이터를 가지고 있지 않은 특별한 노드
이중 연결 리스트 노드 = 왼쪽 링크 필드 + 데이터 필드 + 오른쪽 링크 필드
-> 이중 연결 리스트에서 임의의 노드를 가리키는 포인터를 p라 하면, p == p->llink->rlink == p->rlink->llink 관계가 항상 성립됨
// 이중 연결 리스트 구조체 생성
typedef int element;
typedef struct DlistNode{
element data;
struct DlistNode *llink;
struct DlistNode *rlink;
}DlistNode;
// 노드 new_node를 노드 before의 오른쪽에 삽입
void dinsert_node(DlistNode *before, DlistNode *new_node){
new_node->llink = before;
new_node->rlink = before->rlink;
before->rlink->llink = new_node;
before->rlink = new_node;
}
// 노드 removed를 삭제
void dremove_node(DlistNode *phead_node, DlistNode *removed){
if(removed == phead_node) return;
removed->llink->rlink = removed->rlink;
removed->rlink->llink = removed->llink;
free(removed);
}
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
// 이중 연결 리스트 초기화
void init(DlistNode *phead){
phead->llink = phead;
phead->rlink = phead;
}
// 이중 연결 리스트의 노드들 출력
void display(DlistNode *phead){
DlistNode* p;
for (p = phead->rlink; p != phead;p=p->rlink){
printf("<--- | %x | %d | %x | --->\n", p->llink, p->data, p->rlink);
}
printf("\n");
}
int main(){
DlistNode head_node;
DlistNode* p[10];
init(&head_node);
for (int i = 0; i < 5;i++){
p[i] = (DlistNode*)malloc(sizeof(DlistNode));
p[i]->data = i;
// 헤드 노드의 오른쪽에 삽입
dinsert_node(&head_node, p[i]);
}
dremove_node(&head_node, p[4]);
display(&head_node);
return 0;
}
typedef struct ListNode{
int coef; // 계수
int expon; // 지수
struct ListNode *link; // 다음 항을 가리키는 링크
}ListNode;
ex) 2개의 다항식을 더하는 뎃셈 연산 c(x) = a(x) + b(x)를 구현
1. p.expon == q.expon: 두 계수를 더해서 0이 아니면 새로운 항을 만들어 다항식 c에 추가, 그리고 p와 q는 모두 다음 항으로 이동
2. p.expon < q.expon: q가 가리키는 항을 새로운 항으로 복사하여 다항식 c에 추가, 그리고 q만 다음 항으로 이동
3. p.expon > q.expon: p가 가리키는 항을 새로운 항으로 복사하여 다항식 c에 추가, 그리고 p만 다음 항으로 이동
효율적인 계산을 위해서 연결 리스트의 첫 번째 노드와 마지막 노드를 가리키는 포인터를 동시에 사용 -> 이를 헤더 노드를 통해 관리(따라서, 헤드 노드는 이중 연결 리스트)
#include <stdio.h>
#include <stdlib.h>
// 연결 리스트의 노드의 구조
typedef struct ListNode {
int coef;
int expon;
struct ListNode* link;
} ListNode;
// 연결 리스트 헤더
typedef struct ListHeader {
int length;
ListNode* head;
ListNode* tail;
} ListHeader;
// 초기화 함수
void init(ListHeader* plist)
{
plist->length = 0;
plist->head = plist->tail = NULL;
}
// 오류 처리 함수
void error(const char* message){
fprintf(stderr, "%s\n", message);
exit(1);
}
// plist: 연결 리스트의 헤더를 가리키는 포인터
// coef: 계수
// expon: 지수
void insert_node_last(ListHeader *plist, int coef, int expon){
ListNode* tmp = (ListNode*)malloc(sizeof(ListNode));
if(tmp == NULL)
error("memory allocate error");
tmp->coef = coef;
tmp->expon = expon;
tmp->link = NULL;
if(plist->tail == NULL){
plist->head = plist->tail = tmp;
}else{
plist->tail->link = tmp;
plist->tail = tmp;
}
plist->length++;
}
//list3 = list1 + list2
void poly_add(ListHeader *plist1,ListHeader *plist2, ListHeader*plist3){
ListNode* p = plist1->head;
ListNode* q = plist2->head;
int sum;
while(p!=NULL && q!=NULL){
if(p->expon == q->expon){ // p의 차수 == q의 차수
sum = p->coef + q->coef;
if(sum !=0 ){
insert_node_last(plist3, sum, p->expon);
}
p = p->link;
q = q->link;
}else if(p->expon < q->expon){ // p의 차수 < q의 차수
insert_node_last(plist3, q->coef, q->expon);
q = q->link;
} else { // p의 차수 > q의 차수
insert_node_last(plist3, p->coef, p->expon);
p = p->link;
}
}
// p와 q중의 하나가 먼저 끝나게 되면 남아 있는 항들을 모두 결과 다항식으로 복사
if(p!=NULL){
for (; p != NULL; p = p->link) {
insert_node_last(plist3, p->coef, p->expon);
}
}
if(q!=NULL){
for (; q != NULL;q=q->link){
insert_node_last(plist3, q->coef, q->expon);
}
}
}
// 다항식 출력 함수
void poly_display(ListHeader *plist){
ListNode* tmp = plist->head;
for (; tmp != NULL; tmp=tmp->link){
printf("coef: %d, expon: %d\n", tmp->coef, tmp->expon);
}
printf("\n");
}
// 이중 연결 리스트의 응용 프로그램
int main(){
ListHeader list1, list2, list3;
// 연결 리스트 초기화
init(&list1);
init(&list2);
init(&list3);
// 다항식1을 생성(list1)
insert_node_last(&list1, 3, 12);
insert_node_last(&list1, 2, 8);
insert_node_last(&list1, 1, 0);
// 다항식2를 생성(list2)
insert_node_last(&list2, 8, 12);
insert_node_last(&list2, -3, 10);
insert_node_last(&list2, 10, 6);
// 다항식3 = 다항식1 + 다항식2
poly_add(&list1, &list2, &list3);
poly_display(&list3);
return 0;
}
리스트를 위한 구조체
typedef struct{
ListNode *head; // 첫 번째노드를 가리키는 헤드 포인터
int length; // 연결 리스트 내에 존재하는 노드의 개수
}LinkedListType;
// 리스트 생성
LinkedListType list1;
int is_empty(LinkedListType *list){
if(list->head == NULL) return 1;
else return 0;
}
// 리스트의 항목의 개수를 반환
int get_length(LinkedListType *list){
return list->length;
}
add 연산: 새로운 자료를 선형 리스트에 추가하는 함수
1. 동적 메모리 할당을 이용하여 노드를 생성
2. 노드의 데이터 필드에 자료를 복사
3. 기존의 노드들 중에서 지정된 노드를 찾아서 지정된 노드의 뒤에 연결해주면 됨
-> get_node_at함수 정의: 매개변수 pos로 위치를 받아서 그 위치에 해당하는 노드의 주소를 반환, 음수라면 NULL을 반환
// 리스트 안에서 pos 위치의 노드를 반환
ListNode *get_node_at(LinkedListType*list, int pos){
ListNode *tmp = list->head;
if(pos < 0) return NULL;
for(int i=0;i<pos;i++){
tmp = tmp->link;
}
return tmp;
}
// 주어진 위치에 데이터를 삽입
void add(LinkedListType *list, int pos, element data){
ListNode*tmp;
if((pos>=0) && (pos<=list->length)){
ListNode *node = (ListNode*)malloc(sizeof(ListNode));
if(node == NULL) error("memory allocate error");
node->data = data;
tmp = get_node_at(list, pos-1); // 추가할 위치의 선행 노드의 주소 찾음
insert_node(&(list->head),tmp,node);
list->length++;
}
}
// 리스트의 끝에 데이터를 삽입
void add_last(LinkedListType *list, element data){
add(list, get_length(list),data);
}
// 리스트의 처음에 데이터를 삽입
void add_first(LinkedListType *list, element data){
add(list, 0, data);
}
delete 연산: 공백이 아닌 리스트에서 pos 위치의 자료를 삭제함
1. get_node_at 함수를 사용하여 (pos-1) 위치에 해당하는 노드의 주소를 얻음 -> 노드 삭제 시 그 노드의 선행 노드의 주소가 필요하기 때문
2. remove_node 함수를 호출
// 주어진 위치의 데이터를 삭제
void delete(LinkedListType *list, int pos){
if( !is_empty(list) && (pos>=0) && (pos < list->length)){
ListNode*p = get_node_at(list, pos-1);
ListNode*removed = get_node_at(list,pos);
remove_node(&(list->head),p,removed);
list->length--;
}
}
get_entry 연산: 위치 pos에 해당하는 데이터를 반환하는 연산
1. pos가 length보다 작은지 확인
2. 작으면, get_node_at 함수를 이용하여 위치에 해당하는 노드의 주소를 얻음
3. 노드가 가지고 있는 데이터 값을 반환
// 주어진 위치에 해당하는 데이터를 반환
element get_entry(LinkedListType *list, int pos){
if(pos >= get_length(list)){
error("location error");
}
ListNode*p = get_node_at(list, pos);
return p->data;
}
clear 연산: 모든 노드를 지우는 함수
1. 리스트의 길이 만큼 delete 함수를 호출하면 됨
// 모든 노드를 삭제
void clear(LinkedListType *list){
for(int i=0;i<get_length(list);i++){
delete(list, i);
}
}
display 연산: 리스트가 가지고 있는 데이터를 화면에 출력하는 함수
1. list의 헤드 포인터가 가리키는 노드에서부터 NULL을 만날 때까지 노드의 데이터 값을 출력하면됨
// 버퍼 내용을 출력
void display(LinkedListType *list){
ListNode *node = list->head;
printf("(");
for(int i=0;i < get_length(list);i++){
printf("%d ",node->data);
node = node->link;
}
printf(")\n");
}
is_in_list 연산: 연결 리스트에서 데이터 필드가 item 인 노드를 탐색하는 연산
-> 연결 리스트 상의 모든 노드를 검색하여 데이터 필드가 item인 노드를 찾음
1. 헤드 포인터에서부터 시작하여 NULL을 만날 때까지 while 루프를 수행
2. 이때, 헤드 포인터를 직접 사용하면 안되고 반드시 복사하여 사용해야 함 -> 헤드 포인터를 변경하면 연결 리스트사 상실됨
// 데이터 값이 item인 노드를 찾음
int is_in_list(LinkedListType *list, element item){
ListNode *node = list->head; // 헤드 포인터 복사
while(node !=NULL){
if(node->data == item){ // 노드의 데이터가 item이면
break;
}
node = node->link;
}
if(node == NULL) return false;
else return true;
}
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <limits.h>
void error(const char *message){
fprintf(stderr, "%s\n", message);
exit(1);
}
// 리스트를 초기화
void init(LinkedListType *list){
if(list== NULL)
return;
list->length = 0;
list->head = NULL;
}
int main(){
LinkedListType list;
init(&list);
add(&list, 0, 20);
add_last(&list, 30);
add_first(&list, 10);
add_last(&list, 40);
// list = (10,20,30,40)
display(&list);
// list = (10,20,30)
del(&list, 3);
display(&list);
// list = (20,30)
del(&list, 0);
display(&list);
printf("%s\n", is_in_list(&list, 20) == true ? "success" : "fail");
printf("%d\n", get_entry(&list, 0));
return 0;
}
// 리스트 ADT 사용 -> 가능하지만 비효율적
// 리스트의 항목을 순차적으로 방문하면서 1씩 증가
void visit(LinkedListType *list){
for(int i=0;i<get_length(list);i++){
data = get_entry(list,i);
replace(list, pos, data+1);
}
}
// 포인터 사용 -> 효울적인 구현
// 리스트의 항목을 순차적으로 방문하면서 1씩 증가
void visit(LinkedListType *list){
ListNode *node = list->head;
while(node != NULL){
node->data++;
node = node->link;
}
}
라인 에디터(line editor): 라인 단위로 입력이나 삭제를 할 수 있음
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#define MAX_CHAR_PER_LINE 1000
#define MAX_NAME 256
void warning(const char*);
void error(const char*);
#define FALSE 0
#define TRUE 1
typedef struct
{
char a[MAX_CHAR_PER_LINE];
} element;
typedef struct ListNode
{
element data;
struct ListNode* link;
}ListNode;
typedef struct{
ListNode* head;
int length;
} LinkedListType;
void insert_node(ListNode **phead, ListNode *p,ListNode *new_node){
if((*phead) == NULL){
new_node->link = NULL;
(*phead) = new_node;
}else if(p==NULL){
new_node->link = (*phead);
(*phead) = new_node;
}else{
new_node->link = p->link;
p->link = new_node;
}
}
void remove_node(ListNode **phead, ListNode *p, ListNode *removed){
if(p== NULL){
(*phead) = (*phead)->link; // 첫번째 노드 삭제
}else{
p->link = removed->link;
}
free(removed);
}
void init(LinkedListType* list)
{
if (list == NULL)
return;
list->length = 0;
list->head = NULL;
}
ListNode* get_node_at(LinkedListType* list, int pos)
{
ListNode* tmp = list->head;
if (pos < 0)
return NULL;
for (int i = 0; i < pos; i++) {
tmp = tmp->link;
}
return tmp;
}
int get_length(LinkedListType* list)
{
return list->length;
}
void add(LinkedListType* list, int pos, element data)
{
ListNode* tmp;
if ((pos >= 0) && (pos <= list->length)) {
ListNode* node = (ListNode*)malloc(sizeof(ListNode));
if (node == NULL)
error("memory allocate error");
node->data = data;
tmp = get_node_at(list, pos - 1); // 추가할 위치의 선행 노드의 주소 찾음
insert_node(&(list->head), tmp, node);
list->length++;
}
}
void add_last(LinkedListType* list, element data)
{
add(list, get_length(list), data);
}
void add_first(LinkedListType* list, element data)
{
add(list, 0, data);
}
int is_empty(LinkedListType* list)
{
if (list->head == NULL)
return 1;
else
return 0;
}
void del(LinkedListType* list, int pos)
{
if (!is_empty(list) && (pos >= 0) && (pos < list->length)) {
ListNode* p = get_node_at(list, pos - 1);
ListNode* removed = get_node_at(list, pos);
remove_node(&(list->head), p, removed);
list->length--;
}
}
element get_entry(LinkedListType* list, int pos)
{
if (pos >= get_length(list)) {
error("location error");
}
ListNode* p = get_node_at(list, pos);
return p->data;
}
void clear(LinkedListType* list)
{
for (int i = 0; i < get_length(list); i++) {
del(list, i);
}
}
void display(LinkedListType* buffer)
{
ListNode* node = buffer->head;
printf("******************\n");
for (int i = 0; i < buffer->length; i++) {
printf("%s", node->data.a);
node = node->link;
}
printf("******************\n");
}
void warning(const char *message){
fprintf(stderr, "%s\n", message);
}
void error(const char* message)
{
fprintf(stderr, "%s\n", message);
exit(1);
}
void help(){
printf("******************\n");
printf("i: input\n");
printf("d: delete\n");
printf("r: read\n");
printf("w: write\n");
printf("q: quit\n");
printf("******************\n");
}
// 디스크 파일로부터 데이터를 읽음
void read_file(LinkedListType *buffer){
char fname[MAX_NAME];
FILE* fd;
element p;
if(!is_empty(buffer)){
clear(buffer);
}
init(buffer);
printf("File name: ");
scanf("%s", fname);
if((fd = fopen(fname,"r")) == NULL){
warning("Not open");
return;
}
while(fgets(p.a,MAX_CHAR_PER_LINE,fd)){
add_last(buffer, p);
}
fclose(fd);
display(buffer);
}
// 버퍼에 있는 데이터를 디스크 파일에 쓴다.
void write_file(LinkedListType *buffer){
FILE* fd;
char fname[MAX_NAME];
element p;
printf("File name: ");
scanf("%s", fname);
if ((fd = fopen(fname, "w")) == NULL) {
printf("Not open\n");
return;
}
for (int i = 0; i < get_length(buffer);i++){
p = get_entry(buffer, i);
fputs(p.a, fd);
}
fclose(fd);
display(buffer);
}
// 하나의 라인을 지움
void delete_line(LinkedListType *buffer){
int position;
if(is_empty(buffer)){
printf("No delete line\n");
}else{
printf("Write line number: ");
scanf("%d", &position);
del(buffer, position);
}
display(buffer);
}
// 하나의 라인 삽입
void insert_line(LinkedListType *buffer){
int position;
char line[MAX_CHAR_PER_LINE];
element p;
printf("Write line number: ");
scanf("%d", &position);
printf("Write line: ");
fflush(stdin);
fgets(line, MAX_CHAR_PER_LINE, stdin);
strcpy(p.a, line);
add(buffer, position, p);
display(buffer);
}
void do_command(LinkedListType *buffer, char command){
switch (command)
{
case 'i':
insert_line(buffer);
break;
case 'd':
delete_line(buffer);
break;
case 'r':
read_file(buffer);
break;
case 'w':
write_file(buffer);
break;
case 'q':
break;
}
}
// 라인 에디터 메인 프로그램
int main(){
char command;
LinkedListType buffer;
init(&buffer);
do{
help();
command = getchar();
do_command(&buffer,command);
fflush(stdin);
} while (command != 'q');
}
<참고자료>
천인국 · 공용해 · 하상호 지음,『C언어로 쉽게 풀어쓴 자료구조』, 생능출판(2016)