C언어에서 배열을 활용한 기본 자료구조 이해하기

C언어에서 배열은 가장 기본적이고 중요한 자료구조 중 하나로, 데이터를 연속적으로 저장하며 인덱스를 통해 접근할 수 있는 구조입니다. 본 기사에서는 배열의 기초 개념을 시작으로 이를 활용해 스택, 큐, 정렬 및 검색 알고리즘을 구현하는 방법을 다룹니다. 또한 배열을 사용한 실전 예시와 연습 문제를 통해 배열 기반 자료구조의 활용 능력을 키워보세요.

배열의 기초 개념 이해


배열은 동일한 데이터 타입의 여러 요소를 연속된 메모리 공간에 저장하는 자료구조입니다. 이를 통해 대량의 데이터를 효과적으로 관리하고, 반복 구조를 활용한 처리도 가능하게 만듭니다.

배열의 정의와 메모리 구조


배열은 메모리에서 연속된 위치를 차지하며, 각 요소는 고유한 인덱스로 접근할 수 있습니다.
예를 들어, 정수형 배열 int arr[5]는 5개의 정수 데이터를 저장할 수 있으며, 인덱스는 0부터 4까지 사용됩니다.

메모리 구조 예시


“`plaintext
주소: 1000 1004 1008 1012 1016
값: 10 20 30 40 50

<h3>배열의 선언 및 초기화</h3>  
배열은 다음과 같은 형태로 선언됩니다:  

c
int arr[5]; // 크기가 5인 정수형 배열 선언

초기화는 선언 시 값을 직접 대입하거나, 실행 중에 동적으로 할당할 수 있습니다.  

c
int arr[5] = {10, 20, 30, 40, 50}; // 선언과 동시에 초기화

<h3>배열의 특징</h3>  
1. **고정 크기**: 배열의 크기는 선언 시 고정되며, 실행 중 변경할 수 없습니다.  
2. **빠른 접근 속도**: 인덱스를 통해 상수 시간(O(1))에 요소를 접근할 수 있습니다.  
3. **연속된 메모리**: 배열은 연속된 메모리 공간을 사용하므로, 효율적인 데이터 관리를 제공합니다.  

배열의 기본 개념을 이해함으로써, 다양한 데이터 처리 작업에서 배열의 잠재력을 극대화할 수 있습니다.
<h2>1차원 배열과 2차원 배열의 차이</h2>  

배열은 데이터의 구조와 차원에 따라 다양하게 활용될 수 있습니다. 1차원 배열은 단순히 데이터를 연속적으로 저장하는 구조인 반면, 2차원 배열은 행(row)과 열(column)로 데이터를 구성하여 더 복잡한 형태의 데이터 표현이 가능합니다.  

<h3>1차원 배열</h3>  
1차원 배열은 데이터를 선형 구조로 저장합니다.  
예시:  

c
int arr[5] = {10, 20, 30, 40, 50};

1차원 배열은 특정 인덱스를 통해 요소에 접근하며, `arr[2]`는 값 `30`을 반환합니다.  

<h4>1차원 배열의 활용 예시</h4>  
- 학생들의 점수 저장  
- 리스트 기반 데이터 처리  
- 단일 열 데이터 관리  

<h3>2차원 배열</h3>  
2차원 배열은 행과 열로 데이터를 저장하며, 행렬 구조로 표현됩니다.  
예시:  

c
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};

`arr[1][2]`는 행 1,2에 해당하는 값 `7`을 반환합니다.  

<h4>2차원 배열의 활용 예시</h4>  
- 행렬 계산  
- 게임의 체스판 같은 2D 데이터 표현  
- 이미지 데이터 관리  

<h3>1차원 배열과 2차원 배열의 비교</h3>  
| 특성             | 1차원 배열              | 2차원 배열                   |  
|-------------------|-------------------------|------------------------------|  
| **구조**          | 선형                   |(row)과 열(column)로 구성  |  
| **메모리 할당**   | 단일 연속 메모리        | 연속 메모리의 2D 블록        |  
| **활용 사례**     | 리스트, 점수표          | 행렬, 그래픽 데이터          |  
| **접근 방식**     | 단일 인덱스 사용        | 두 개의 인덱스 사용          |  

배열의 차원을 적절히 활용하면 데이터 구조의 표현력을 높이고, 특정 작업에 맞는 효율적인 처리를 구현할 수 있습니다.  
<h2>배열을 활용한 스택 구현</h2>  

스택(Stack)LIFO(Last In, First Out) 원칙을 따르는 자료구조로, 배열을 사용하여 간단하게 구현할 수 있습니다. 배열 기반 스택은 정적 크기를 가지며, 추가(push) 및 제거(pop) 연산을 효율적으로 처리할 수 있습니다.  

<h3>스택의 기본 연산</h3>  
1. **Push**: 스택에 요소를 추가합니다.  
2. **Pop**: 스택에서 요소를 제거하고 반환합니다.  
3. **Peek**: 스택의 최상위 요소를 확인합니다.  
4. **IsEmpty**: 스택이 비었는지 확인합니다.  

<h3>배열 기반 스택 구현</h3>  
다음은 C언어를 사용한 배열 기반 스택의 구현 예시입니다.  

c

include

include

define MAX 100 // 스택의 최대 크기

typedef struct {
int data[MAX];
int top; // 스택의 최상위 요소를 가리킴
} Stack;

// 스택 초기화
void initStack(Stack* s) {
s->top = -1;
}

// 스택이 비었는지 확인
bool isEmpty(Stack* s) {
return s->top == -1;
}

// 스택이 가득 찼는지 확인
bool isFull(Stack* s) {
return s->top == MAX – 1;
}

// 스택에 요소 추가
void push(Stack* s, int value) {
if (isFull(s)) {
printf(“Stack Overflow\n”);
return;
}
s->data[++(s->top)] = value;
}

// 스택에서 요소 제거 및 반환
int pop(Stack* s) {
if (isEmpty(s)) {
printf(“Stack Underflow\n”);
return -1; // 오류 코드
}
return s->data[(s->top)–];
}

// 스택 최상위 요소 확인
int peek(Stack* s) {
if (isEmpty(s)) {
printf(“Stack is Empty\n”);
return -1;
}
return s->data[s->top];
}

// 스택 상태 출력
void printStack(Stack* s) {
if (isEmpty(s)) {
printf(“Stack is Empty\n”);
return;
}
for (int i = 0; i <= s->top; i++) {
printf(“%d “, s->data[i]);
}
printf(“\n”);
}

int main() {
Stack s;
initStack(&s);

push(&s, 10);
push(&s, 20);
push(&s, 30);
printStack(&s);

printf("Popped: %d\n", pop(&s));
printStack(&s);

printf("Top Element: %d\n", peek(&s));

return 0;

}

<h3>주요 코드 설명</h3>  
1. **초기화**: `initStack` 함수는 스택의 초기 상태를 설정합니다.  
2. **추가 및 제거**: `push`와 `pop` 연산으로 스택의 데이터를 관리합니다.  
3. **유효성 검사**: `isEmpty`와 `isFull`로 스택의 상태를 점검합니다.  

<h3>활용 예시</h3>  
스택은 함수 호출 기록 저장, 괄호 검증, 깊이 우선 탐색(DFS) 등의 작업에 활용됩니다. 배열을 이용한 구현은 간단하지만, 메모리 크기 제한이 있다는 점에 유의해야 합니다.  
<h2>배열을 활용한 큐 구현</h2>(Queue)FIFO(First In, First Out) 원칙을 따르는 자료구조로, 배열을 사용하여 효율적으로 구현할 수 있습니다. 큐는 삽입(enqueue)과 삭제(dequeue) 연산을 통해 데이터를 관리합니다.  

<h3>큐의 기본 연산</h3>  
1. **Enqueue**: 큐의 끝에 요소를 추가합니다.  
2. **Dequeue**: 큐의 앞에서 요소를 제거하고 반환합니다.  
3. **IsEmpty**: 큐가 비었는지 확인합니다.  
4. **IsFull**: 큐가 가득 찼는지 확인합니다.  

<h3>배열 기반 큐 구현</h3>  
다음은 C언어를 사용한 배열 기반 큐의 구현 예시입니다.  

c

include

include

define MAX 100 // 큐의 최대 크기

typedef struct {
int data[MAX];
int front; // 큐의 앞을 가리킴
int rear; // 큐의 뒤를 가리킴
} Queue;

// 큐 초기화
void initQueue(Queue* q) {
q->front = 0;
q->rear = -1;
}

// 큐가 비었는지 확인
bool isEmpty(Queue* q) {
return q->front > q->rear;
}

// 큐가 가득 찼는지 확인
bool isFull(Queue* q) {
return q->rear == MAX – 1;
}

// 큐에 요소 추가
void enqueue(Queue* q, int value) {
if (isFull(q)) {
printf(“Queue Overflow\n”);
return;
}
q->data[++(q->rear)] = value;
}

// 큐에서 요소 제거 및 반환
int dequeue(Queue* q) {
if (isEmpty(q)) {
printf(“Queue Underflow\n”);
return -1; // 오류 코드
}
return q->data[(q->front)++];
}

// 큐 상태 출력
void printQueue(Queue* q) {
if (isEmpty(q)) {
printf(“Queue is Empty\n”);
return;
}
for (int i = q->front; i <= q->rear; i++) {
printf(“%d “, q->data[i]);
}
printf(“\n”);
}

int main() {
Queue q;
initQueue(&q);

enqueue(&q, 10);
enqueue(&q, 20);
enqueue(&q, 30);
printQueue(&q);

printf("Dequeued: %d\n", dequeue(&q));
printQueue(&q);

return 0;

}

<h3>주요 코드 설명</h3>  
1. **초기화**: `initQueue` 함수로 큐의 초기 상태를 설정합니다.  
2. **삽입 및 삭제**: `enqueue`와 `dequeue`를 통해 데이터를 추가하거나 제거합니다.  
3. **상태 점검**: `isEmpty`와 `isFull`로 큐의 상태를 확인합니다.  

<h3>배열 기반 큐의 제한</h3>  
- 큐의 크기는 고정되어 있어, 요소 추가 시 오버플로(Overflow)가 발생할 수 있습니다.  
- 삭제된 요소로 인해 사용되지 않는 메모리 공간이 생길 수 있습니다.  

<h3>활용 예시</h3>  
큐는 프린터 작업 대기열, 프로세스 관리, BFS(너비 우선 탐색) 등의 작업에 활용됩니다. 간단한 데이터 처리에는 배열 기반 큐가 적합하며, 동적 크기가 필요한 경우 링크드 리스트 기반 구현을 고려해야 합니다.  
<h2>순환 큐의 구현과 활용</h2>  

순환 큐(Circular Queue)는 일반 큐의 한계를 극복하기 위해 설계된 자료구조로, 배열의 시작과 끝을 연결하여 메모리를 효율적으로 사용할 수 있습니다.  

<h3>순환 큐의 특징</h3>  
1. **연결된 구조**: 배열의 끝에 도달하면 다시 시작 부분으로 돌아갑니다.  
2. **효율적인 공간 사용**: 삭제된 요소로 인해 생긴 빈 공간을 재사용할 수 있습니다.  
3. **두 개의 포인터 사용**: `front`는 첫 번째 요소를, `rear`는 마지막 요소를 가리킵니다.  

<h3>순환 큐 구현</h3>  
다음은 C언어를 사용하여 순환 큐를 구현한 예제입니다.  

c

include

include

define MAX 5 // 순환 큐의 최대 크기

typedef struct {
int data[MAX];
int front; // 큐의 앞을 가리킴
int rear; // 큐의 뒤를 가리킴
} CircularQueue;

// 순환 큐 초기화
void initQueue(CircularQueue* q) {
q->front = -1;
q->rear = -1;
}

// 순환 큐가 비었는지 확인
bool isEmpty(CircularQueue* q) {
return q->front == -1;
}

// 순환 큐가 가득 찼는지 확인
bool isFull(CircularQueue* q) {
return (q->rear + 1) % MAX == q->front;
}

// 순환 큐에 요소 추가
void enqueue(CircularQueue* q, int value) {
if (isFull(q)) {
printf(“Queue Overflow\n”);
return;
}
if (isEmpty(q)) {
q->front = 0;
}
q->rear = (q->rear + 1) % MAX;
q->data[q->rear] = value;
}

// 순환 큐에서 요소 제거 및 반환
int dequeue(CircularQueue* q) {
if (isEmpty(q)) {
printf(“Queue Underflow\n”);
return -1;
}
int value = q->data[q->front];
if (q->front == q->rear) { // 큐가 비게 되는 경우
q->front = -1;
q->rear = -1;
} else {
q->front = (q->front + 1) % MAX;
}
return value;
}

// 순환 큐 상태 출력
void printQueue(CircularQueue* q) {
if (isEmpty(q)) {
printf(“Queue is Empty\n”);
return;
}
printf(“Queue elements: “);
int i = q->front;
while (1) {
printf(“%d “, q->data[i]);
if (i == q->rear) {
break;
}
i = (i + 1) % MAX;
}
printf(“\n”);
}

int main() {
CircularQueue q;
initQueue(&q);

enqueue(&q, 10);
enqueue(&q, 20);
enqueue(&q, 30);
enqueue(&q, 40);
printQueue(&q);

printf("Dequeued: %d\n", dequeue(&q));
printQueue(&q);

enqueue(&q, 50);
enqueue(&q, 60);  // 큐가 가득 찼을 때
printQueue(&q);

return 0;

}

<h3>주요 코드 설명</h3>  
1. **순환 동작 구현**: `(rear + 1) % MAX`와 `(front + 1) % MAX`를 통해 포인터를 순환시킵니다.  
2. **초기화 상태**: `front`와 `rear`가 `-1`인 경우 큐는 비어 있습니다.  
3. **오버플로 및 언더플로 처리**: 큐가 가득 찼거나 비었을 때 적절한 오류 메시지를 출력합니다.  

<h3>활용 예시</h3>  
순환 큐는 다음과 같은 상황에서 유용합니다:  
- 네트워크 버퍼 관리  
- 생산자-소비자 문제 해결  
- 데이터 스트림 처리  

순환 큐는 일반 큐보다 메모리 효율이 높아 제한된 메모리에서 데이터를 효과적으로 관리할 수 있습니다.  
<h2>배열과 정렬 알고리즘</h2>  

정렬(Sorting)은 데이터를 특정 순서대로 배열하는 과정으로, 배열은 정렬 알고리즘 구현에 적합한 자료구조입니다. 이 섹션에서는 배열을 활용한 대표적인 정렬 알고리즘 두 가지(버블 정렬과 삽입 정렬)를 설명합니다.  

<h3>정렬 알고리즘의 필요성</h3>  
정렬은 데이터 검색 및 분석의 성능을 향상시키고, 알고리즘의 효율성을 높이는 데 중요한 역할을 합니다. 예를 들어, 이진 검색(Binary Search)은 데이터가 정렬되어 있어야만 작동합니다.  

<h3>버블 정렬</h3>  
버블 정렬은 인접한 두 요소를 비교하여 정렬하는 가장 간단한 정렬 알고리즘입니다.  

<h4>버블 정렬의 동작 원리</h4>  
1. 배열을 순회하며 인접한 요소를 비교합니다.  
2. 두 요소가 잘못된 순서에 있으면 위치를 교환합니다.  
3. 이 과정을 배열의 모든 요소에 대해 반복하며, 한 번의 순회에서 가장 큰 요소가 끝으로 이동합니다.  

<h4>버블 정렬 코드 구현</h4>  

c

include

void bubbleSort(int arr[], int n) {
for (int i = 0; i < n – 1; i++) { for (int j = 0; j < n – i – 1; j++) { if (arr[j] > arr[j + 1]) {
// 요소 교환
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}

void printArray(int arr[], int n) {
for (int i = 0; i < n; i++) {
printf(“%d “, arr[i]);
}
printf(“\n”);
}

int main() {
int arr[] = {64, 34, 25, 12, 22, 11, 90};
int n = sizeof(arr) / sizeof(arr[0]);

printf("Original array: ");
printArray(arr, n);

bubbleSort(arr, n);

printf("Sorted array: ");
printArray(arr, n);

return 0;

}

<h4>버블 정렬의 시간 복잡도</h4>  
- 최선의 경우(O(n)): 이미 정렬된 배열  
- 최악 및 평균의 경우(O()): 역순 배열  

<h3>삽입 정렬</h3>  
삽입 정렬은 정렬되지 않은 데이터를 하나씩 선택하여 적절한 위치에 삽입하는 방식입니다.  

<h4>삽입 정렬의 동작 원리</h4>  
1. 배열의 두 번째 요소부터 시작합니다.  
2. 현재 요소를 정렬된 부분 배열의 올바른 위치에 삽입합니다.  
3. 모든 요소가 정렬될 때까지 반복합니다.  

<h4>삽입 정렬 코드 구현</h4>  

c

include

void insertionSort(int arr[], int n) {
for (int i = 1; i < n; i++) {
int key = arr[i];
int j = i – 1;

    // 정렬된 부분 배열에서 현재 요소의 위치 찾기
    while (j >= 0 && arr[j] > key) {
        arr[j + 1] = arr[j];
        j--;
    }
    arr[j + 1] = key;
}

}

int main() {
int arr[] = {12, 11, 13, 5, 6};
int n = sizeof(arr) / sizeof(arr[0]);

printf("Original array: ");
printArray(arr, n);

insertionSort(arr, n);

printf("Sorted array: ");
printArray(arr, n);

return 0;

}

<h4>삽입 정렬의 시간 복잡도</h4>  
- 최선의 경우(O(n)): 거의 정렬된 배열  
- 최악 및 평균의 경우(O()): 역순 배열  

<h3>정렬 알고리즘 비교</h3>  
| 알고리즘      | 최선의 시간 복잡도 | 평균 시간 복잡도 | 최악의 시간 복잡도 | 안정성   |  
|---------------|--------------------|------------------|--------------------|----------|  
| 버블 정렬     | O(n)              | O()            | O()              | 안정적   |  
| 삽입 정렬     | O(n)              | O()            | O()              | 안정적   |  

배열은 정렬 알고리즘을 실습하기에 적합한 자료구조입니다. 다양한 정렬 알고리즘을 이해하고, 데이터를 효율적으로 관리할 수 있도록 연습해 보세요.  
<h2>배열과 검색 알고리즘</h2>  

검색(Search)은 데이터에서 특정 요소를 찾는 과정으로, 배열은 간단하고 효율적인 검색 알고리즘을 구현하기 위한 이상적인 자료구조입니다. 이 섹션에서는 선형 검색(Linear Search)과 이진 검색(Binary Search)을 설명하고, 그 차이점을 분석합니다.  

<h3>선형 검색</h3>  
선형 검색은 배열의 처음부터 끝까지 요소를 하나씩 검사하여 원하는 값을 찾는 방법입니다.  

<h4>선형 검색의 동작 원리</h4>  
1. 배열의 첫 번째 요소부터 원하는 값을 순차적으로 비교합니다.  
2. 일치하는 값이 발견되면 해당 인덱스를 반환합니다.  
3. 배열 끝까지 값을 찾지 못하면 실패(-1)를 반환합니다.  

<h4>선형 검색 코드 구현</h4>  

c

include

int linearSearch(int arr[], int n, int key) {
for (int i = 0; i < n; i++) {
if (arr[i] == key) {
return i; // 일치하는 인덱스 반환
}
}
return -1; // 실패
}

int main() {
int arr[] = {10, 20, 30, 40, 50};
int n = sizeof(arr) / sizeof(arr[0]);
int key = 30;

int result = linearSearch(arr, n, key);
if (result != -1) {
    printf("Element found at index %d\n", result);
} else {
    printf("Element not found\n");
}

return 0;

}

<h4>선형 검색의 시간 복잡도</h4>  
- 최선의 경우(O(1)): 첫 번째 요소가 검색 대상인 경우  
- 최악 및 평균의 경우(O(n)): 검색 대상이 배열 끝에 있거나 없는 경우  

<h3>이진 검색</h3>  
이진 검색은 정렬된 배열에서 중간 요소를 기준으로 검색 범위를 절반으로 줄여가는 방식입니다.  

<h4>이진 검색의 동작 원리</h4>  
1. 배열의 중간 요소를 확인합니다.  
2. 원하는 값이 중간 값보다 작으면 왼쪽 절반, 크면 오른쪽 절반에서 검색을 반복합니다.  
3. 값이 발견되거나 검색 범위가 없어질 때까지 반복합니다.  

<h4>이진 검색 코드 구현</h4>  

c

include

int binarySearch(int arr[], int left, int right, int key) {
while (left <= right) {
int mid = left + (right – left) / 2;

    if (arr[mid] == key) {
        return mid;  // 검색 성공
    }
    if (arr[mid] < key) {
        left = mid + 1;  // 오른쪽 반으로 이동
    } else {
        right = mid - 1;  // 왼쪽 반으로 이동
    }
}
return -1;  // 실패

}

int main() {
int arr[] = {10, 20, 30, 40, 50};
int n = sizeof(arr) / sizeof(arr[0]);
int key = 40;

int result = binarySearch(arr, 0, n - 1, key);
if (result != -1) {
    printf("Element found at index %d\n", result);
} else {
    printf("Element not found\n");
}

return 0;

}

<h4>이진 검색의 시간 복잡도</h4>  
- 최선의 경우(O(1)): 중간 요소가 검색 대상인 경우  
- 최악 및 평균의 경우(O(log n)): 검색 범위를 절반으로 줄이기 때문에 빠름  

<h3>선형 검색과 이진 검색 비교</h3>  
| 특성             | 선형 검색              | 이진 검색              |  
|-------------------|-----------------------|------------------------|  
| **배열 상태**     | 정렬 필요 없음        | 정렬된 배열이어야 함   |  
| **시간 복잡도**   | O(n)                 | O(log n)              |  
| **구현 난이도**   | 쉬움                 | 약간 복잡             |  
| **사용 사례**     | 작은 배열, 정렬 안 된 배열 | 큰 배열, 정렬된 배열 |  

<h3>활용 예시</h3>  
1. **선형 검색**: 데이터가 적고 정렬되지 않은 경우 사용.  
2. **이진 검색**: 데이터가 크고 정렬된 경우 사용.  

배열과 두 검색 알고리즘의 원리를 이해하고, 상황에 맞는 알고리즘을 선택하여 효율적으로 데이터를 관리하세요.  
<h2>응용 예시와 연습 문제</h2>  

지금까지 설명한 배열과 자료구조, 알고리즘의 개념과 구현을 바탕으로 실제 활용 가능한 예시와 연습 문제를 제공합니다. 이를 통해 배열 기반 자료구조와 알고리즘의 실전 능력을 강화할 수 있습니다.  

<h3>응용 예시</h3>  

<h4>1. 주식 가격 변화 계산</h4>  
배열을 사용하여 특정 기간 동안의 주식 가격 변화를 계산합니다.  

**설명**: 배열의 각 요소는 하루의 주식 가격을 나타냅니다. 이 데이터를 기반으로 각 날의 가격 변동(전일 대비 차이)을 계산합니다.  

**코드 예시**:  

c

include

void calculatePriceChanges(int prices[], int n) {
for (int i = 1; i < n; i++) {
printf(“Day %d to Day %d: %d\n”, i, i + 1, prices[i] – prices[i – 1]);
}
}

int main() {
int prices[] = {100, 105, 102, 110, 115};
int n = sizeof(prices) / sizeof(prices[0]);

calculatePriceChanges(prices, n);

return 0;

}

<h4>2. 병원 대기 관리 시스템</h4>  
환자의 대기 순서를 관리하기 위해 큐를 활용합니다.  

**설명**: 환자 이름을 배열 기반 순환 큐로 관리하며, 접수된 순서대로 진료를 진행합니다.  

**코드 예시**:  

c

include

include

define MAX 5

typedef struct {
char names[MAX][50];
int front;
int rear;
} PatientQueue;

void initQueue(PatientQueue* q) {
q->front = -1;
q->rear = -1;
}

void enqueue(PatientQueue* q, char name[]) {
if ((q->rear + 1) % MAX == q->front) {
printf(“Queue is full!\n”);
return;
}
if (q->front == -1) q->front = 0;
q->rear = (q->rear + 1) % MAX;
strcpy(q->names[q->rear], name);
}

void dequeue(PatientQueue* q) {
if (q->front == -1) {
printf(“Queue is empty!\n”);
return;
}
printf(“Patient %s is being treated.\n”, q->names[q->front]);
if (q->front == q->rear) {
q->front = q->rear = -1;
} else {
q->front = (q->front + 1) % MAX;
}
}

void displayQueue(PatientQueue* q) {
if (q->front == -1) {
printf(“Queue is empty!\n”);
return;
}
printf(“Patients in the queue: “);
int i = q->front;
do {
printf(“%s “, q->names[i]);
i = (i + 1) % MAX;
} while (i != (q->rear + 1) % MAX);
printf(“\n”);
}

int main() {
PatientQueue queue;
initQueue(&queue);

enqueue(&queue, "Alice");
enqueue(&queue, "Bob");
enqueue(&queue, "Charlie");
displayQueue(&queue);

dequeue(&queue);
displayQueue(&queue);

return 0;

}
“`

연습 문제

1. 배열의 중복 요소 제거


문제: 배열에서 중복된 요소를 제거하고, 중복이 없는 배열을 반환하는 프로그램을 작성하세요.
힌트: 배열을 정렬한 후 중복 요소를 건너뛰며 새로운 배열을 생성합니다.

2. 스택을 이용한 괄호 유효성 검사


문제: 주어진 문자열에 포함된 괄호가 올바르게 열리고 닫혔는지 확인하는 프로그램을 작성하세요.
힌트: 여는 괄호는 스택에 넣고, 닫는 괄호가 나오면 스택에서 비교합니다.

3. 배열 기반 우선순위 큐 구현


문제: 각 요소에 우선순위가 포함된 배열 기반 큐를 구현하고, 높은 우선순위를 가진 요소를 먼저 처리하세요.
힌트: 삽입 시 우선순위에 따라 배열을 정렬합니다.

문제를 해결하며 얻을 수 있는 능력


이 연습 문제는 배열과 자료구조를 활용하여 효율적인 데이터 처리 방법을 배우고, 실제 응용 사례에서 문제를 해결하는 데 필요한 사고력을 키우는 데 도움을 줍니다.