180213 튜터링 - 연결리스트, 양방향, 원형 연결리스트
< 숙제 review >
1) 팰린드롬인지 판단한는 함수 재귀함수로 구현하기
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #include <stdio.h> #include <string.h> int isP(char str[], int s, int e) { if(s >= e) return 1; return str[s]==str[e] ? isP(str, s+1, e-1) : 0; } int main() { char str[100]; scanf("%s", str); isP(str, 0, strlen(str)-1) ? puts("True") : puts("False"); return 0; } | cs |
>> 첫번째 인덱스와 마지막 인덱스의 문자가 같을때, 그 안의 문자열이 팰린드롬이면 팰린드롬이다.
2) 리스트의 내용을 모두 출력하는 함수 만들기
1 2 3 4 5 6 7 8 | void printList(List *list) { Node *p = list->head; while(p != NULL) { printf("%d\n", p->data); p = p->next; } } | cs |
< Tutoring >
< 연결리스트의 전체노드 삭제 >
1 2 3 4 5 6 7 8 9 10 | void deleteList(List *list) { Node *p = list->head; while(p != NULL) { Node *tmp = p; p = p->next; free(tmp); } init(list); } | cs |
>> 위 예제의 응용에 불과하다고 생각함.
>> 동적할당으로 생성된 변수는 free() 를 통해 해제를 시킬 수 있다.
>> 9번은 다 지우고 난 다음 초기화를 다시 해주는 역할 정도..
< 연결리스트에서 특정 노드의 삭제 >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | void deleteNode(List *list, int pos) { int k = 1; Node *prev, *cur = list->head; if(pos == 1) { list->head = list->head->next; free(cur); return ; } while(k < pos) { k++; prev = cur; cur = cur->next; } prev->next = cur->next; free(cur); } | cs |
>> 11~14 에서 첫번째 노드가 1번 노드라고 할 때, k번째 노드는 cur 가 가리키게 된다.
>> 하지만 위의 코드는 pos 의 값이 노드의 갯수보다 큰 값이 들어오면 에러가 발생한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | void deleteNode2(List *list, int pos) { int k = 1; Node *prev, *cur = list->head; if(cur == NULL) return ; if(pos == 1) { list->head = list->head->next; free(cur); return ; } while(cur != NULL && k < pos) { k++; prev = cur; cur = cur->next; } if(cur == NULL) { puts("Position does not exist."); return ; } prev->next = cur->next; free(cur); } | cs |
>> 어떤 부분들이 바뀌었는지 비교해봐 얘들아.. ㅎㅎ
>> 그외 List 주머니에 사이즈 관련 변수 하나 만들어서 노드의 개수를 카운트 해나가면서
pos 값이 그 사이즈 안에 있는 값인지 체크하는 방법으로 해도 되고, 상황에 맞게 하면 될듯.!!
< 원형연결리스트의 삽입 (앞에) >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | #include <stdio.h> #include <stdlib.h> typedef struct node { int data; struct node *next; } Node; typedef struct list { Node *head; } List; void init(List *list) { list->head = NULL; } void CircularListAdd(List *list, int data) { Node *p, *newNode = (Node*)malloc(sizeof(Node)); newNode->data = data; if(list->head == NULL) { list->head = newNode; newNode->next = newNode; return ; } p = list->head; while(p->next != list->head) p = p->next; p->next = newNode; newNode->next = list->head; //list->head = newNode; } void printCircularList(List *list) { Node *cur = list->head; if(cur == NULL) return ; do { printf("%d\n", cur->data); cur = cur->next; } while(cur != list->head); } int main() { List list; init(&list); CircularListAdd(&list, 1); CircularListAdd(&list, 3); CircularListAdd(&list, 7); CircularListAdd(&list, 5); printCircularList(&list); return 0; } | cs |
>> 18 : 원형 연결리스트의 데이터를 삽입하는 함수.
>> 38 : 원형 연결리스트의 데이터를 출력하는 함수
>> 60 의 결과는 1, 3, 7, 5
>> 35 의 주석을 해제했을 때, 60의 결과는 5, 7, 3, 1
>> 즉 35가 없으면 뒤로 삽입, 있으면 앞으로 삽입을 하는 결과가 만들어진다.
>> 다만 위의 코드에서는 삽입을 할때, 항상 맨 마지막 노드를 찾아야 하는 번거로움(?)이 있다.
하지만 헤드포인터를 테일이라고 생각하고 코드를 작성하면 그럴필요가 없어진다..!!
1 2 3 4 5 6 7 8 9 10 11 12 13 | void CircularListAdd2(List *list, int data) { Node *newNode = (Node*)malloc(sizeof(Node)); newNode->data = data; if(list->head == NULL) { list->head = newNode; newNode->next = newNode; return ; } newNode->next = list->head->next; list->head->next = newNode; //list->head = newNode; } | cs |
>> head 를 tail 이라고 생각하면 된다.
>> 12 가 없으면 앞으로 삽입, 12가 있으면 뒤로 삽입하는 코드가 된다.
< 원형연결리스트의 노드들 중 홀수의 갯수 세기 >
- printCircularList() 에서 홀수인지만 판단한는 조건만 추가하고 계산 후 그 값을 리턴하면 된다.
- 그러니 우리는 if(cur->data & 1) 이라는 표현에 대해 알아보자.
>> & 는 비트 연산자로 각 비트별 AND 연산을 수행한다.
>> 참고로 모든 홀수의 마지막 비트(2^0)은 항상 1 이다.
모든 짝수의 마지막 비트는 항상 0 이다.
>> 1 은 8비트 기준 00000001 이다.
>> 그러므로 1과 & 연산을 하면, 마지막 비트를 제외한 비트는 1이든 0이든 AND 연산의 결과는
0이 나온다. 그렇기 때문에 홀수와 1에 대해 &(AND) 연산을 하면 항상 1이 나온다.
>> 조건식에서 1은 참을 의미한다..
cf) -1 은 비트로 표현하면 1로만 구성이 된다. 8비트라면 11111111 이 된다.
>> 1을 더하면 0이 되므로 증명 끝..
>> -2는 -1에서 1을 빼면 된다. 그러므로 -2는 8비트기준 11111110 이 된다.
>> -3은 -1에서 2를 빼면 된다. 그러므로 -3은 8비트 기준 11111101 이 된다.
>> 작은 수들은 굳이 2의 보수 취하지 않고도 이런식으로 구할 수 있다.
< 양방향 연결리스트의 삽입 >
- 우리가 지금까지 했던 연결리스트는 한방향으로만 연결이 되어 있다.
하지만, 양방향 연결리스트는 말 그대로 양방향으로 연결이 되어 있다. 그러므로 prev 같은 노드
포인터가 필요없어진다.
- 노드에 대한 디자인도 새로 해야한다.
1 2 3 4 5 | typedef struct node { int data; struct node *next; struct node *prev; } Node; | cs |
>> 4 : 이전 노드를 가리키기 위한 포인터가 추가되었다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | void insertNode(List *list, int data) { Node *newNode = (Node*)malloc(sizeof(Node)); newNode->data = data; newNode->next = NULL; newNode->prev = NULL; if(list->head == NULL) list->head = newNode; else { newNode->next = list->head; list->head->prev = newNode; list->head = newNode; } } | cs |
>> 앞으로 삽입하는 코드.
>> 6, 12번 줄이 추가가 된다.
< 더미노드를 이용한 양방향 연결리스트의 삽입 >
- 더미노드를 사용하면 list->head 가 가리키는 노드가 바뀌는 것에 대한 염려를 안해도 된다.
- 더미노드를 사용하면 init() 함수를 수정해주어야 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | void init(List *list) { list->head = (Node*)malloc(sizeof(Node));; list->head->prev = NULL; list->head->next = NULL; } void insertNode(List *list, int data) { Node *newNode = (Node*)malloc(sizeof(Node)); newNode->data = data; newNode->next = list->head->next; list->head->next = newNode; newNode->prev = list->head; } | cs |
>> 그리고 삽입코드는 위와 같이 쓸 수 있다.
< 숙제 >
- 복습잘하기
- 이중연결리스트에서 position 값이 주어졌을 때, 해당 위치에 노드를 삭제하는 함수 만들기
- 양의 정수를 입력 받았을 때, 3자리 마다 , 를 찍어서 출력해주는 함수(재귀로)
input : 1000000 >> output : 1,000,000
- 스택, 중위식을 후위식으로 표기하는 방법, 후위식을 계산하는 방법 예습해오기
'Tutoring > 17-2 Data Structure (Winter)' 카테고리의 다른 글
180301 튜터링 - 큐(Queue) (0) | 2018.03.01 |
---|---|
180301 튜터링 - 스택의 응용 (수정:180514) (0) | 2018.03.01 |
180220 튜터링 - 스택 (0) | 2018.02.21 |
180206 튜터링 - 연결리스트 (0) | 2018.02.06 |
180130 튜터링 - 재귀함수 만들기, 분석하기 (0) | 2018.02.06 |