상세 컨텐츠

본문 제목

[프로그래밍랩] 5주차 - 난수, 확률, 통계

C/문제 풀이

by uzichoi 2025. 4. 13. 22:24

본문

[프로그래밍랩] 5주차 C언어 복습 - 난수 확률, 통계

 

1. 난수

난수 n개를 발생시키고 합과 평균을 출력하라. 사용자에게 원하는 범위의 시작과 끝을 입력받아 조건에 맞는 수가 출력되도록 작성하라.

<실행화면>

<소스코드>

// 프로그래밍랩 5주
// Lab5-1 난수 n개 (start ~ end) 발생시키고 합과 평균 출력
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void main()
{
	int i, n, r, sum = 0;
	int start, end;
	srand(time(NULL)); // 난수값 초기화
	//srand(0);
	printf("난수의 개수: ");
	scanf("%d", &n);
	printf("시작과 끝 : ");
	scanf("%d %d", &start, &end);
	int range = end - start + 1; //range = 101

	for (i = 0; i < n; i++) {
		r = rand() % range + start;
		printf("%d ", r);
		sum += r;
	}
	printf("\nsum=%d avg=%d\n", sum, sum / n);
}
  1. seed값 설정
    • srand(time(NULL)) 으로 현재 시간 이용해 난수처럼 이용 
  2. 범위 설정 
    • 구간의 개수(범위): end - start + 1 
    • rand()%range → 0~100 사이의 랜덤한 수 → +100  100~200 사이 범위 지정 가능
    • 100~200 사이의 난수일 때, range = 200 - 100 + 1 = 101

 

 

2. 주사위 확률 계산

(1) 주사위를 n번 굴려서 나오는 수 1~6의 빈도수와 확률을 계산하여 출력하라. 

(2) 주사위를 굴렸을 때 나오는 번호들은 각 1/6(~17%)의 확률을 갖는다. 이때, 번호가 나오는 확률이 조작되어 있는 이상한 주사위가 있다고 가정하고, 1~5는 각 10%, 6은 50%가 나오는 주사위를 만들어라. 확률을 조작하기 위해서는 rand() 함수를 이용해 범위를 지정한 후,  1, 2, 3, 4, 5, 6을 각각 구간으로 변경하면 된다.

<실행화면>

<소스코드>

// 프로그래밍랩 5주
// Lab5-2 주사위 
// (1) n번 던져서 각각의 수의 발생 확률 계산해 보기
// (2) 확률이 조작된 주사위 
//		1~5 = 10%, 6 = 50% 즉, 6이 자주 나오는 주사위
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void main()
{
	int i, r, ntest, dice;
	int count[6] = {0}; // 1 ~ 6 count 를 보관
	srand(time(NULL)); // 난수 초기화
	printf("주사위 횟수 : ");
	scanf("%d", &ntest);
	printf("정상적인 주사위\n");
	for (i=0;i<ntest;i++) {
		dice = rand() % 6;	// dice = ?? 0~5 -> 주사위 1~6
		count[dice]++;	// count[?] = 발생 회수 증가, 완성할 것
	}
	for (i=0;i<6;i++) {
		// 화면과 같이 출력
		printf("%d : %d (%4.2lf%%)\n", i + 1, count[i], (double)count[i] / 100);
	}
	printf("\n");

	// 이상한 주사위
	printf("\n이상한 주사위 6이 50%% 확률\n");
	for (i = 0;i < 6;i++)
		count[i] = 0;
	for (i=0;i<ntest;i++) {
		r = rand() % 100;	// 분포를 0~99로 구하고 0~9, 10~19, 20~29, 30~39, 40~49, 50~99 를
		// 1, 2, 3, 4, 5, 6 으로 맵핑
		if (r < 10)
			dice = 0;
		else if (r < 20)
			dice = 1;
		else if (r < 30)
			dice = 2;
		else if (r < 40)
			dice = 3;
		else if (r < 50)
			dice = 4;
		else if (r >= 50 && r < 100)
			dice = 5;
		// 발생 회수 증가
		count[dice]++;
	}
	for (i=0;i<6;i++) {
		// 화면과 같이 출력
		printf("%d : %d (%4.2lf%%)\n", i + 1, count[i], (double)count[i] / 100);
	}
}
  • rand()%100 의 값을 가지는 r은 0~99 사이의 값을 가지는 난수이다. r의 발생 확률을 1~5에 각각 10%, 6에 50%로 고정하기 위하여 위 코드와 같이 if 조건식 안 r의 범위를 지정해야 한다. 범위에 따라 dice 값을 다르게 부여하여 확률을 조작한다.
  • count[6]에 주사위의 각 번호가 나온 횟수를 저장하기 위하여 count[dice]++ 하면 번호별 발생 횟수를 증가시킬 수 있다.
  • 확률을 계산할 때에는 100.0 * count[i] / ntest 와 같이 작성하여 부동소수점으로 자동 형변환해야 한다.

 

 

3.Lotto 번호 발생기

Lotto 번호는 1~45 사이의 6개 수로 구성된다. rand() 함수를 이용하여 난수를 생성하고, 각 줄에서 중복되지 않도록 조건을 설정하라.  출력 형식에 맞게 오름차순으로 정렬하고, 6개를 출력할 때마다 개행되도록 해야 한다.  

<실행화면>

<소스코드>

// 프로그래밍랩 5주
// Lab5-3 Lotto 출력
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void bubbleSort(int lotto[], int n) {
	int i, j, tmp;
	int changed;	//불필요한 swap 확인할 변수
	for (i = 0; i < n-1; i++) {
		changed = 0;
		for (j = 0; j < n - 1 - i; j++) {
			if (lotto[j] > lotto[j + 1]) {
				tmp = lotto[j + 1];
				lotto[j + 1] = lotto[j];
				lotto[j] = tmp;
				changed = 1;	//swap 발생했다는 의미
			}
		}
		if (changed == 0)		//swap 발생 X = 이미 오름차순 정렬된 상태이므로
			break;				//바로 탈출
	}
}

void main()
{
	int i, j, n, l;
	int lotto[6] = { 0 };

	srand(time(NULL));
	printf("복권 매 수는 : ");
	scanf("%d", &l);

	while (l--) {
		for (i = 0; i < 6; i++) {			
			n = rand() % 45 + 1;		//난수 발생
			for (j = 0; j < i; j++) {	//중복 검사
				if (n == lotto[j]) {
					n = rand() % 45 + 1;	//중복이면 난수 다시 생성
					j = -1;		//j 초기화해서 다음 loop(j++->j=0)에서 다시 검증하도록
				}
			}
			lotto[i] = n;		//중복 아니면 lotto 배열에 저장
		}
	
		bubbleSort(lotto, 6);

		for (i = 0; i < 6; i++) {	//순서대로 출력
			printf("%02d ", lotto[i]);
		}
		printf("\n");
	}	
}
  1. 버블 정렬
    • void bubbleSort(int lotto[], int n) 함수는 가장 큰 수를 오른쪽으로 밀어내면서 오름차순으로 정렬하는 함수이다. 
    • 이 함수에는 이중 반복문 구조가 필요하다. 외부 for문은 0부터 n-1(이 문제에서는 마지막 원소 인덱스인  5번)까지 반복한다. 이때,  i번째 루프가 끝날 때마다 배열의 뒤쪽 i개 원소는 정렬이 완료된 상태가 된다.
    • 따라서 내부 for문에서는 아직 정렬되지 않은 앞부분만 검사하면 되므로, 반복 범위는 0부터 n-1-i까지로 설정한다.  
    • 또한, 불필요한 비교(swap)을 줄이기 위해 changed라는 정수형 변수를 활용한다. 이 변수는 i번째 외부 for문이 시작될 때 0으로 초기화되며, 내부 for문에서 swap이 발생하면 1로 바뀐다.
    • 내부 루프가 종료될 때까지 changed 값이 여전히 0이라면, 해당 루프에서는 교환이 한번도 발생하지 않았다는 뜻이므로 배열은 이미 오름차순으로 정렬되어 있다는 뜻이다. 이 경우 정렬을 더 이상 수행할 필요가 없으므로 외부 루프를 조기 종료하여 함수의 실행 효율을 높인다.    
  2. 중복 검사
    • 외부 for문에서 0부터 6까지 반복하며 난수 n을 생성한다.
    • 내부 for문에서 lotto[0]부터 lotto[i-1]까지의 값들과 n을 비교한다.
    • lotto[] 배열의 기존 원소와 n이 중복되는경우, 난수 n을 새롭게 생성하고, j를 -1로 초기화한다. 
    • 이후 다음 루프에서 j++이 실행되면 j==0이 되므로 처음부터 다시 비교하게 된다. 즉, 모든 이전 값들과 다시 한번 중복 검사를 진행할 수 있게 된다. 
    • 중복 없이 통과한 난수는 lotto[i]에 저장한다.     

 

 

4. 모의 성적 데이터 발생-1

성적 처리 프로그램을 위한 테스트 데이터를 생성하고자 한다. n명의 성적을 발생시키고 각 성적별 빈도수와 %를 출력하라.   

<실행화면>

<소스코드>

// 프로그래밍랩 5주
// Lab5-4 성적 발생하기
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void main()
{
	int i, j, n, r;		//n: 난수의 개수, r: 난수
	int sum = 0;		// 총점	

	// 학점별 인원수 0:A+, 1:A, ... D, 8:F
	char* grade_str[] = { "A+", "A ", "B+", "B ", "C+", "C ", "D+", "D ", "F " };		

	int grade_index, grade_count[9] = { 0 };
	int* scores[9];
	//int score1;

	scanf("%d", &n);

	/*for (i = 0; i < 9;i++)
		scores[i] = (int*)malloc(sizeof(int) * n);	
	if (scores == NULL) {
		printf("동적할당 실패\n");
		return -1;
	}*/

	srand(time(NULL));				// 난수 초기화

	for (i = 0; i < n; i++) {
		r = rand() % 101;		// score1 = 0~100
		printf("%d ", r);
		sum += r;				// sum 을 구한다.
		// grade_index 를 구한다. 0:A+, 1:A ...
		if (r >= 95) grade_index = 0;        // A+
		else if (r >= 90) grade_index = 1;   // A
		else if (r >= 85) grade_index = 2;   // B+
		else if (r >= 80) grade_index = 3;   // B
		else if (r >= 75) grade_index = 4;   // C+
		else if (r >= 70) grade_index = 5;   // C
		else if (r >= 65) grade_index = 6;   // D+
		else if (r >= 60) grade_index = 7;   // D
		else grade_index = 8;                // F

		// grade_count[]를 증가시킨다.
		grade_count[grade_index]++;		
	}

	printf("\n\nn=%d 평균 = %.2f\n", n, ((double)sum) / n);
	for (i = 0; i < 9; i++) {
		// 화면과 같이 출력
		printf("%s : %5d (%6.2lf%%)", grade_str[i], grade_count[i], 100.0 * grade_count[i] / n);
		printf("\n");
	}

	//free(scores);
}
  • char* grade_str[]은 포인터 배열. 각 포인터가 다른 주소에 있는 문자열 리터럴을 가리킴. 값 수정 불가.
  • scores[i]는 학생 점수를 담는 별도의 배열 하나를 가리킴. 학생들의 성적별 점수를 따로따로 저장하기 위함.

 

 

5. 모의 성적 데이터 발생-2

성적 분포가 주어졌을 때 모의 데이터를 발생시킨다.

A+: 10%, A: 10%, B+: 20%, B: 25%, C+: 15%, C: 10%, D+: 5%, D: 3%, F: 2%

주사위 확률 조작 방법과 같은 방법으로 A+~F에  해당하는 점수를 발생시키고, 각 학점별 % 분포가 학생 수(정수)로 환산할 때 정확하게 일치하지 않을 수 있으므로 소수점으로 나오는 경우 보정해야 한다.

<실행화면>

<소스코드>

// 프로그래밍랩 5주
// Lab5-5 150명 성적 분포에 맞게 점수 발생시키기
// A+(95~100): 10%, A(90~94):10%, B+(85~89):20%, B(80~84):25%, 
// C+(75~79):15%, C(70~74):10%, D+(65~69):5%, D(60~64):3%, F:2%
// Lab5-4와 강의노트 참조하여 작성
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>

void main()
{
	int i, nstudents, prob, score;	//prob: 각 등급에 배정할 학생 수(정수)
	int j, sum = 0;		//추가한 변수
	char* grade_str[] = { "A+", "A ", "B+", "B ", "C+", "C ", "D+", "D ", "F " };
	//int grade;		//0~8, A+~F
	int count[9] = { 0 };	//0~8, A~F+ 인원수
	int maxcount[9] = { 10, 10, 20, 25, 15, 10, 5, 3, 2 };	//미리 계산된 최대 인원
	int start[9] = { 95, 90, 85, 80, 75, 70, 65, 60, 0 };	//점수 시작
	int end[9] = { 100, 94, 89, 84, 79, 74, 69, 64, 59 };	//점수 끝
	
	scanf("%d", &nstudents);

	srand((unsigned)time(NULL));	

	for (i = 0; i < 9; i++) {
		prob = (int)round(nstudents * (maxcount[i] / 100.0));	//학점별 인원수 사전 설정. i 증가할 때마다 갱신
		for (j = 0; j < prob; j++) {				//제한된 인원수만큼 난수 발생
			score = start[i] + rand() % (end[i] - start[i] + 1);
			count[i]++;
			sum += score;
			printf("%d ", score);
		}
	}
	printf("\n\nn=%d 평균 = %.2lf\n", nstudents, (double)sum / nstudents);
	for (i = 0; i < 9; i++) {
		printf("%s : %5d (%6.2lf%%)\n", grade_str[i], count[i], 100.0 * count[i] / nstudents);
	}
}

 

 

6. 문장의 알파벳 분포 조사

input.txt를 읽어서 알파벳들의 문자수를 count하여 분표를 표시하라.

<실행화면>

<소스코드>

// 프로그래밍랩 5주
// Lab5-6 영문 TEXT 의 알파벳 빈도수 check
// input.txt 를 입력으로 받고, 아파벳들을 count하여 등장하는 확률을 표시
// 입력의 끝(keyboard 에서는 CTRL-Z) EOF로 검사를 한다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <ctype.h>
//#include <windows.h>

void main()
{
	char c;
	int total = 0; // 전체 입력 수(영문자, 숫자, 공백, 특수문자 전부 포함됨)
	int alpha = 0; // 알파벳 문자수
	int blank = 0; // blank(space, tab, newline) 문자수
	int digit = 0; // 0~9 문자수
	int special = 0; // 특수문자 수
	int count[26] = { 0 };//알파벳별로 count

	while ((c = getchar()) != EOF) {	// 입력의 끝이 아니면 알파벳 카운트
		total++;
		if (isalpha(c)) {		// 알파벳만 검사
			c = toupper(c);
			alpha++;
			count[c - 'A']++;
		}
		else if (isspace(c)) blank++;	// whitespace 이면
		else if (isdigit(c)) digit++;	// 숫자이면	
		else special++;
	}

	printf("\n\n전체문자수=%d 알파벳수=%d\n", total, alpha);
	for (c = 'A'; c <= 'Z'; c++) {
		// 화면과 같이 출력
		printf("%c:%-3d (%5.2lf%%) ", c, count[c - 'A'], count[c - 'A'] * 100.0 / total);
		if ((c - 'A') % 5 == 4) printf("\n");	// 인덱스 번호가 4의 배수이면 개행
	}
	printf("\n");

	//Sleep(50000);
}

 

 

7. 3개의 주사위 통계

난수를 이용하여 주사위 3개의 합(3~18)을구하고 그 분포를 화면과 같이 Histogram으로 표시하는 프로그램을 작성하시오. 주사위 합은 30,000번 구한다. 화면과 같이 분포는 소수점 2자리, Histogram은 1%당 1개씩 *를 표시하며 소수점이 존재하면 한 개씩 더 출력한다. (eg. 1.36%의 경우 2개의 *가 표시되고 10.00%의 경우 10개만 표시된다.) 

<실행화면>

<소스코드>

// Lb5-7 주사위 3개 통계
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
void main()
{
	// 필요하면 변수 추가
	//int r1, r2;
	int dice[19] = { 0 };	//dice[0]~dice[2]는 버려지는 공간
	int n, i, j, hist;
	int sum;
	double perc = 0;

	// 난수 초기화 
	srand((unsigned)time(NULL));
	
	// 30000번 동안 주사위 3개 합을 구해 dice[] 값을 증가시킨다.
	for (i = 0; i < 10000; i++) {
		sum = 0;
		for (j = 0; j < 3; j++) {
			n = rand() % 6 + 1;
			sum += n;
		}
		dice[sum]++;
	}
	
	// Histogram을 출력한다.
	// *의 수는 소숫점이 있으면 +1개 출력한다.
	for (i = 3; i <= 18; i++) {
		perc = 100.0 * dice[i] / 10000;
		hist = ceil(perc);
		printf("%2d: %5.2lf%% ", i, perc);
		for (j = 0; j < hist; j++)
			printf("*");
		printf("\n");
	}
}
  • <math.h>의 ceil() 함수를 이용하여 가우스 처리하였다.
  • 3개의 주사위를 10,000번 굴려 합을 구했으므로 확률 계산 시 10,000으로 나누어야 한다.   

 

 

8. 수학 공부 프로그램

난수를 이용하여 어린 조카의 수학 공부를 위한 덧셈(x + y) 학습 프로그램을 작성한다. 다음 조건을 확인하여 프로그램하시오.

<실행화면>

<소스코드>

// 덧셈 공부
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void main()
{
	int x, y, r, result, ans;	// 필요하면 변수 추가
	int round = 0, ok = 0;		// round: 총 라운드 수, ok: 맞힌 문항 수

	srand((unsigned)time(NULL));	// 난수 초기화

	while (1) {	// 무한 반복
		r = rand() % 100;

		// x+y 를 확률에 따라 1자리, 2자리, 3자리 문제를 출력한다.
		if (r < 30) {	// 30% 확률
			x = rand() % 10 + 1;
			y = rand() % 10 + 1;
		}
		else if (r < 90) {	// 60% 확률
			x = rand() % 90 + 10;
			y = rand() % 100 + 10;
		}
		else {		// 10% 확률
			x = rand() % 900 + 100;
			y = rand() % 900 + 100;
		}
		result = x + y;

		printf("(문제 %d) %d + %d = ", ++round, x, y);
		scanf("%d", &ans);		// ans 입력

		if (ans == 0)	// ans 가 0이면 반복 종료
			break;
		if (ans == result) {	// 정답과 오답 결과 출력
			printf("정답입니다.\n");
			ok++;
		}
		else
			printf("틀렸습니다.\n");
	}	// 반복끝
	
	// 정답률 출력
	printf("정답률: %.0lf%% (%d/%d)\n", 100.0 * ok / round, ok, round);	
}
  1. 자릿수별 확률 제한
    • 난수 r의 범위를 0~99로 설정하고, 조건 분기를 통해 자릿수별 출제 확률을 조절하였다.
      • r이 0~29일 경우 (30%): 1자리 수
      • r이 30~89일 경우 (60%): 2자리 수
      • r이 90~99일 경우 (10%): 3자리 수
  2. x, y의 범위 제한
    • 예를 들어 2자리 덧셈 문제를 출제하려는 경우, 피연산자인 x, y는 10 이상 99 이하의 값을 가져야 한다.
    • 따라서 rand() % 90 + 10처럼 작성하여 범위를 제한하였다.

 

 

ACM. 괄호 검증 - VPS

중첩된 괄호 ( ) 의 유효성을 검사한다. 올바른 괄호 문자열 VPS(Vaild Parenthesis String)인지 확인하여 YES/NO를 출력한다. ')'가 먼저 나오거나 '('로 끝나는 경우도 확인하라.

<실행화면>

input.txt

<소스코드>

//  프로그래밍랩
// 5주 ACM 문제
// VPS (Valid Parantesis String) 검증
// () 의 짝이 맞으면 YES, 틀리면 NO
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
//#include <windows.h>
//#include <time.h>
void main()
{
	int ntest;
	char buf[100];
	int i, len;
	int count1 = 0, count2 = 0;
	scanf("%d", &ntest);
	while (ntest--) {
		scanf("%s", buf); // 1라인 입력
		count1 = count2 = 0;

		len = strlen(buf);	

		// ')'가 먼저 나오거나 '('로 끝나는 경우 확인
		if (buf[0] == ')'|| buf[len-1] == '(')
			printf("NO\n");

		// 문자열을 모두 검사해서 '(' 갯수와 ')' 갯수를 count하고
		for (i = 0; i < len; i++) {
			if (buf[i] == '(')
				count1++;
			else if (buf[i] = ')')
				count2++;
		}

		// count1 count2 를 비교하여 판단
		(count1 == count2) ? printf("YES\n") : printf("NO\n");
	}

	//Sleep(10000);
}

관련글 더보기