관리 메뉴

공부 기록장 💻

[C언어] 배열 - 배열의 선언, 배열 요소 복사와 비교, 선택 정렬, 순차 탐색, 이진 탐색, 2차원/다차원 배열 본문

# Language & Tools/C

[C언어] 배열 - 배열의 선언, 배열 요소 복사와 비교, 선택 정렬, 순차 탐색, 이진 탐색, 2차원/다차원 배열

dream_for 2021. 2. 7. 04:04

 

배열의 개념

 

- 동일한 자료형 & 같은 이름의 데이터 여러 개가 연속적으로 저장되어 있는 저장 장소

- 정수로 되어 있는 번호(=인덱스)로 각 배열의 요소를 구분하고, 인덱스를 이용하여 각 데이터에 접근 가능

 

 

배열의 선언

 

- 배열을 사용하기 위해 먼저 선언이 필요하다. 컴파일러에게 배열의 자료형, 배열 이름, 요소의 개수가 무엇인지 알리는 과정이다.

 

int scores[10]; // int 형 scores 변수 10개 선언 (scores[0]~scores[10])

- 배열의 이름은 scores이고, int형 변수를 의미하는 배열의 요소들은 0번 인덱스부터 시작하여 9번까지 총 10개를 포함한다.

- 인덱스의 범위는 언제나 0~(배열크기 -1) 이다.

 

#define SIZE 10
#define SIZE2 20
int scores[SIZE], avg[SIZE2];

- 배열의 크기는 상수만을 값으로 가질 수 있다. 보통 #define 지시자로 만들어진 기호 상수로 지정한다.

(변수 x, 실수 x, 음수 x)

 

 

 

배열 요소 접근, 배열의 복사와 비교

 

- 한 개의 배열 요소에 접근하여, 값을 대입하기 위한 방법은 다음과 같다.

 

scores[0] = 100; // 직접 상수 값 대입
scores[1] = scores[0]; // 요소의 값 복사 가능
scores[i] = 10; // 정수 변수 i 대입 가능. 다만 i가 초기화 되어 있는 경우만
scores[i+2] = 20; // 수식 가능
scores[index[3]] = 30;  정수 배열의 요소가 인덱스 값이 될 수 있다.

 

 

- 한꺼번에 배열 요소에 값을 대입하기 위해 for 반복문을 사용할 수 있다.

 

#define SIZE 10

	int scores[SIZE];
	
	for (int i = 0;i < SIZE;i++) {
		scores[i] = i; // 배열 요소에 i의 값을 대입
		printf("scores[%d] = %d\n", i, scores[i]); 
	}

 

 

- 생성된 난수를 각 배열 요소에 대입하고, 전체 값에 대한 평균을 구하는 프로그램은 다음과 같다.

 

#include <stdio.h>
#include <stdlib.h> // rand() 라이브러리 함수
#include <time.h> // time() 라이브러리 함수
#define SIZE 10

int main(void){
	srand((unsigned)time(NULL)); // srand() 함수를 이용해 시간과 관련된 난수 발생하도록 함

	int scores[SIZE], sum=0;

	for (int i = 0;i < SIZE;i++) {
		scores[i] = rand() % 100; // 0부터 99까지의 난수를 배열 요소에 대입
		sum += scores[i]; // sum에 각 배열 요소의 값을 더함
		printf("scores[%d] = %d\n", i, scores[i]);
	}
	
	printf("평균은 %f\n", (double)sum / SIZE);

	return 0;
}

 

 

 

 

- 하나의 배열을 다른 배열로 복사하려면, 반복문을 이용해 각각의 요소의 값을 대입해주어야 한다.

- 두 배열을 비교할 때도 마찬가지로 각각의 요소를 서로 비교해주어야 한다. 

 

 

배열 초기화

 

- 배열을 선언함과 동시에 요소의 값들을 미리 초기화할 수 있다. 중괄호 {} 로 감싸주고, 각 값들은 콤마 , 로 분리하면 된다. 초기화하지 않은 배열 요소엔 쓰레기값이 저장되므로, 요소 접근 전 초기화 과정이 꼭 필요하다.

 

int scores[] = {100, 90, 80}; // 배열의 크기는 자동적으로 3이 됨
int grades[10] = {0}; // 전체 초기화 (쓰레기값이 저장되는 것을 방지)
int avg[5] = {80]; // avg[0] 을 제외한 나머지 값들은 0으로 초기화됨

 

 

배열과 함수

 

- 배열이 함수로 전달될 때, 값이 복사가 되어 함수의 매개변수로 전달되는 일반적인 값에 의한 호출(call by value)로 이루어지지 않고, 원본이 그대로 전달되는 방식을 이용한다. 배열의 크기가 크기 때문에 메모리 공간을 효율적으로 사용하기 위해서이다.

 

 

위 예시에서 평균을 구하는 과정을 함수로 구현해 보면 다음과 같다.

 

#include <stdio.h>
#include <stdlib.h> // rand() 라이브러리 함수
#include <time.h> // time() 라이브러리 함수
#define SIZE 10

double GetAverage(int s[], int size);
// 배열과 배열의 크기를 인수로 전달받아 평균을 구하는 함수 원형

int main(void){
	srand((unsigned)time(NULL)); // srand() 함수를 이용해 시간과 관련된 난수 발생하도록 함

	int scores[SIZE], sum=0;

	for (int i = 0;i < SIZE;i++) {
		scores[i] = rand() % 100; // 0부터 99까지의 난수를 배열 요소에 대입
		// sum += scores[i]
		printf("scores[%d] = %d\n", i, scores[i]);
	}
	
	printf("평균은 %f\n", GetAverage(scores, SIZE));

	return 0;
}
double GetAverage(int s[], int size) {
	int sum = 0;

	for (int i = 0;i < size;i++)
		sum += s[i];

	return (double)sum / size;
}

 

미리 함수의 원형을 선언해준다. 함수를 정의할 때, 배열을 받는 매개 변수는 크기를 적어주지 않아도 된다.

배열의 크기 정보를 두 번째 매개 변수로 받아 데이터를 처리하도록 한다.

 

 

- 매개 변수를 통해 배열의 원본을 참조하게 되기 때문에, 배열을 언제나 변경/훼손할 수 있음을 유의하자!

 

 

 

정렬과 탐색

 

- 정렬: 오름차순 또는 내림차순으로 나열하는 것. (알파벳순, 크기순)

 

  • 선택정렬 알고리즘 (오름차순)
#include <stdio.h>
#define SIZE 5

int main(void) {

	int list[SIZE] = { 5,3,9,1,4 }; // 5 정렬되지 않은 원소를 가지고 있는 배열
	int i, j, least, tmp;
	// least: 최솟값을 찾아 저장하는 인덱스 번호, tmp: 두 원소의 위치 변경하기 위한 임시 변수

	for (i = 0;i < SIZE - 1;i++)  
	{
		least = i; // i번째 인덱스의 원소를 최솟값으로 가정
		
		for (j = i + 1;j < SIZE;j++)
			if (list[j] < list[least])
				least = j; // list[j]와 비교하며 더 작은 값이 나올 때마다 해당 요소의 인덱스를 least에 저장
		
		// 가장 작은 값 list[least]를 list[i]와 위치 변경
		tmp = list[i];
		list[i] = list[least];
		list[least] = tmp;
	}

	for (int i = 0;i < SIZE;i++)
		printf("%d ", list[i]);
	printf("\n");

	return 0;
}

 

선택 정렬은 왼쪽 배열, 오른쪽 배열이 있다고 가정한 상태에서

왼쪽 배열에는 정렬이 완료된 숫자들이, 오른쪽 배열에는 정렬되지 않은 숫자들이 있다고 생각하여

오른쪽 배열에서 최솟값을 찾아 왼쪽 배열(초기엔 i값이 최솟값)의 수의 자리로 이동시키는 방식으로,

9배열의 요소 개수-1) 만큼 이 과정을 되풀이하며 정렬은 완성해 나가는 기법이다. 

위의 예시를 i값에 따라 반복되는 과정을 표로 나타내면 아래와 같다.

 

i 값 왼쪽 배열(위치 변경 전 -> 후) 오른쪽 배열(위치 변경 전 -> 후) 설명
0 (5) -> (1) (3,9,1,4) -> (3,9,5,4) i=5 -> least=1 (5와 1 자리 변경)
1 (1,3) -> (1,3) (9,5,4) - > (9,5,4) i=3 -> least=3 (최솟값 그대로)
2 (1,3,9) -> (1,3,4) (5,4) -> (5,9) i=9 -> least=4 (9와 4 자리 변경)
3 (1,3,4,5) -> (1,3,4,5) (9) -> (9) i=5 -> least=5 (최솟값 그대로)

 

처음엔 i값을 갖고 있는 인덱스의 요소를 최솟값으로 설정한 후,

하나씩 값을 비교하며 가장 작은 값을 찾아낸 뒤 i가 있던 자리에 최솟값을 이동시키는 과정이다.

 

 

 

- 탐색: 탐색키를 이용하여 배열에 해당 키와 동일한 데이터를 찾아내는 과정

 

  • 순차 탐색(sequential search): 배열의 원소를 순서대로 하나씩 탐색키와 비교하는 방법

 

- 정수 탐색

#include <stdio.h>
#define SIZE 5

int main(void) {

	int list[SIZE] = { 5,3,9,1,4 };
	int key; // 탐색 키

	printf("탐색할 값: ");
	scanf_s("%d", &key);

	for(int i=0;i<SIZE;i++)
		if (list[i] == key) {
			printf("탐색 성공 인덱스 = %d\n", i);
			break; // 탐색 성공할 시 해당 반복문을 빠져나옴
		}

	printf("탐색 종료\n");
	
	return 0;
}

 

- 문자 탐색

#include <stdio.h>

int main(void) {

	char s[] = "Hello"; // 문자열
	char key; // 탐색키(문자)

	printf("탐색할 문자: ");
	scanf_s("%c", &key);

	for (int i = 0;i < sizeof(s)-1;i++) {
		if (s[i] == key) {
			printf("탐색 성공 인덱스 = %d\n", i);
			break; // 탐색 대상 문자가 문자열에 포함되어 있는 경우 탐색 성공
		}
	}

	printf("탐색 종료\n");
	return 0;
}

 

- 문자열 탐색

#include <stdio.h>
#include <string.h>

int main(void) {

	char* s[] = { "Hi", "Programming", "Fun" }; // 문자배열
	char key[20]; // 탐색키(문자열)

	printf("탐색할 문자열: ");
	gets(key);

	
	for (int i = 0;i < sizeof(*s)-1;i++) {
		if (strcmp(s[i],key)==0) {
			printf("탐색 성공 인덱스 = %d\n", i);
			break; // 탐색 대상 문자열이 문자 배열에 포함되어 있는 경우 탐색 성공
		}
	}

	printf("탐색 종료\n");
	return 0;
}

 

아래 실행 결과

더보기

 

정수 배열에서 탐색
문자열에서 문자 탐색 
문자열 배열에서 문자열 탐색

 

 

  • 이진 탐색(binary search): 탐색하려는 배열이 정렬되어 있을 때, 배열의 중앙값을 탐색키와 비교하여 다시 탐색할 범위를 전반부, 후반부인 절반으로 좁혀 나가기를 반복하며 탐색하는 과정

 

#include <stdio.h>
#define SIZE 20

// 배열, 배열 길이, 탐색키를 매개변수로 전달받아 이진 탐색을 하는 함수 원형
int binary_search(int list[], int n, int key);

int main(void) {

	int key;
	int list[SIZE] = { 1,3,5,7,8,10,11,13,16,19,20,21,24,25,28,30,31,35,39,49 };

	printf("탐색할 값: ");
	scanf_s("%d", &key);
	printf("탐색 결과 = %d\n", binary_search(list, SIZE, key));

	printf("탐색 종료\n");
	return 0;
}
int binary_search(int list[], int n, int key) {
	int low, high, middle;

	// 초기 범위를 전체 배열로 설정
	low = 0; 
	high = n - 1; 

	while (low <= high) {
		printf("[%d %d]\n", low, high); // 탐색하는 범위의 처음, 끝값 인덱스 출력
		middle = (low + high) / 2; // 탐색 범위의 중간 값 설정

		if (key == list[middle]) // 중간값과 일치하는 경우 
			return middle;
		else if (key > list[middle]) 
			low = middle + 1; // 중간값보다 탐색키가 큰 경우, 후반부로 범위 좁힘
		else 
			high = middle - 1; // 중간값보다 탐색키가 작은 경우, 전반부로 범위 좁힘
	}
}

 

- 이진 탐색을 하는 함수를 정의하여, 정렬되어 있는 배열에서 찾고자 하는 탐색키를 찾아낸다. 탐색하는 범위의 중간값과 탐색키를 비교하여 중간값을 기준으로 탐색 범위를 전반부 또는 후반부로 좁혀나가길 반복한다. 중간값이 탐색키와 일치할 때까지 이 과정을 되풀이한다.

 

 

 

 

다차원 배열

 

2차원 배열: 행(Row, 가로)과 열(Column, 세로)로 이루어진 2차원 형태의 배열

- 다차원 배열은 디지털 이미지나 보드 게임판 또는 학생들의 성적 기록표 등 같은 형태의 데이터를 여러 개 저장하여야 하는 경우 사용된다.

- 2차원 배열에서 하나의 요소를 참조하기 위해선, 행과 열을 각각 의미하는 두 개의 인덱스가 필요하다. [0][0] 의 요소부터 [행의 수-1][열의 수-1)] 까지 있음을 유의하자.

- 학생 3명(ROW 행)의 세 과목의 성적(COL 열)을 저장해야 하는 경우, 다음과 같이 2차원 배열로 나타낼 수 있다.

 

#include <stdio.h>

// 행과 열의 개수를 상수로 표현
#define ROW 3
#define COL 3

int main(void) {
	
	int scores[ROW][COL] = { {100,90,80},{90,80,85},{75,80,95} }; // 2차원 배열의 초기화
	// int scores[][COL] = {100, 90, 80, 80, 80, 85, 75, 80, 95};
    	// 초기화 하는 경우 행의 크기는 생랼될 수 있다. 열의 크기에 따라 초기값을 자동으로 분류 하기 때문
    
	for (int i = 0;i < ROW;i++) {
		for (int j = 0;j < COL;j++)
			printf("%3d ", scores[i][j]);
		printf("\n");
	}

	return 0;
}

 

각 배열의 원소를 출력하기 위해서는, 행/열의 순서를 정확하게 인지하여 이중 반복문을 작성하여야 한다.

위의 행렬 데이터를 표로 나타내면 다음과 같다. (KIM, PARK, LEE 3 명의 English, Math, Korean 성적 데이터)

성적 English Math Korean
KIM 100 90 80
PARK 90 80 85
LEE 75 80 95

 

- 두 개 이상의 서로 같은 크기의 행렬에 대한 연산도 가능하다. 

- 2차원 배열을 함수로 전달하는 경우, 행의 크기는 생략 가능하나 열의 크기는 꼭 적어주어야 한다. 열의 크기에 따라 자동으로 컴파일러가 행을 분류하기 때문이다.

 

 

728x90
반응형
Comments