(a) 目前使用的格里高利历闰年的规则如下:

1
2
3
4
1. 公元年分非4的倍数,为平年。
2. 公元年分为4的倍数但非100的倍数,为闰年。
3. 公元年分为100的倍数但非400的倍数,为平年。
4. 公元年分为400的倍数为闰年。

请用一个表达式判断某一年是否为闰年。

1
if(year % 4 == 0 && year % 100 != 0 || year % 400 ==0 )

(b) 输入某一天的年月日,输出下一天的年月日。

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
#define _CRT_SECURE_NO_WARNINGS

#include<stdio.h>
#include<stdbool.h>
int daysOfMonth[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

bool isLeapYear(int year);
void setFebDay();
int main(void) {
int year, month, day;
scanf("%d %d %d",&year,&month,&day);
//int daysOfMonth[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
setFebDay(year);
day++;

if (day > daysOfMonth[month]) {
day = 1;
month++;

}
if (month == 13) {
month = 1;
year ++;
}
printf("year = %d, month = %d, day = %d\n", year, month, day);

return 0;
}
bool isLeapYear(int year) {
return year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
}
void setFebDay(int year ) {
if (isLeapYear(year))
daysOfMonth[2] = 29;
else
daysOfMonth[2] = 28;
}

(c) 输入某两天的年月日,输出这两天的相距多少天。

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
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdbool.h>
int daysOfMonth[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
bool isLeapYear(int year);
void setFebDay();
int distance(int year1, int month1, int day1, int year2, int month2, int day2);
int main(void) {
int year1, month1, day1;
scanf("%d %d %d",&year1,&month1,&day1);
int year2, month2, day2;
scanf("%d %d %d", &year2, &month2, &day2);
printf("差距%d天", distance(year1, month1, day1, year2, month2, day2));
return 0;
}
bool isLeapYear(int year) {
return year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
}
void setFebDay(int year ) {
if (isLeapYear(year))
daysOfMonth[2] = 29;
else
daysOfMonth[2] = 28;
}
int distance(int year1, int month1, int day1, int year2, int month2, int day2) {
int days = 0;
//默认前面的日期小于后面的日期
//year1的一月一日到 month1 day1的天数只差(包括year1 的一月一日)
setFebDay(year1);
int distance_1 = 0;
for (int m = 1; m < month1; m++) {
distance_1 += daysOfMonth[m];
}
distance_1 += day1;
//计算year2的一月一日到 year2 month2 day2的天数之差(包括year2 的一月一日)
setFebDay(year2);
int distance_2 = 0;
for (int m = 1; m < month2; m++) {
distance_2 += daysOfMonth[m];
}
distance_2 += day2;
//计算year1的一月一日到year2的一月一日的天数之差(包括year1 的一月一日和year2 的一月一日)
int distance_3 = 0;
for (int y = year1; y < year2; y++) {
distance_3 += 365;
if (isLeapYear(y))
distance_3 += 1;
}
days = distance_2 + distance_3 - distance_1;
return days;
}

这个的验证比较麻烦,毕竟自己不可能计算很多天数来跟程序的结果比对。我使用的excel自带的公式。方法在这里

如何计算两日期相差的天数、月数或年数?—DATEDIF函数 - 小崔学数据分析的文章 - 知乎
https://zhuanlan.zhihu.com/p/441248425

将excel公式计算出的结果和程序的结果比对就知道程序是对的。

(d) 已知1970年1月1日是星期四,输入之后的某一天的年月日,判断它是星期几?

1
2
3
4
5
int weekday(int year, int month, int day) {
return (4 + distance(1970, 1, 1, year, month, day)) % 7;
//如果是星期天,对应的索引值是0。事实上,很做电子日历起初都是把星期日放在最前面
//然后才是星期一星期二等等
}

(e) 输入1970年之后任意一年的年份,输出该年的年历。对话如下:(拓展题,不要求每个同学作答)

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
64
65
66
67
68
69
70
71
72
73
74
75
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdbool.h>

int daysOfMonth[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
const char* DayOfWeek[] = { "SUN", "MON", "TUE", "WED", "THU", "FRI","SAT" };

bool isLeapYear(int year);
int weekday(int year, int month, int day);
void setFebDay(int year);
int distance(int year1, int month1, int day1, int year2, int month2, int day2);
void printCalendar(int year);

int main(void) {
//输入年份
int year;
printf("Please input the year whose calendear you want to know?\n");
scanf("%d", &year);

//打印日历
printCalendar(year);

return 0;
}
bool isLeapYear(int year) {
return year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
}
void setFebDay(int year ) {
if (isLeapYear(year))
daysOfMonth[2] = 29;
else
daysOfMonth[2] = 28;
}
int distance(int year1, int month1, int day1, int year2, int month2, int day2) {
int days = 0;
//默认前面的日期小于后面的日期
//year1的一月一日到 month1 day1的天数只差(包括year1 的一月一日)
setFebDay(year1);
int distance_1 = 0;
for (int m = 1; m < month1; m++) {
distance_1 += daysOfMonth[m];
}
distance_1 += day1;
//计算year2的一月一日到 year2 month2 day2的天数之差(包括year2 的一月一日)
setFebDay(year2);
int distance_2 = 0;
for (int m = 1; m < month2; m++) {
distance_2 += daysOfMonth[m];
}
distance_2 += day2;
//计算year1的一月一日到year2的一月一日的天数之差(包括year1 的一月一日和year2 的一月一日)
int distance_3 = 0;
for (int y = year1; y < year2; y++) {
distance_3 += 365;
if (isLeapYear(y))
distance_3 += 1;
}
days = distance_2 + distance_3 - distance_1;
return days;
}

int weekday(int year, int month, int day) {
return (4 + distance(1970, 1, 1, year, month, day)) % 7;
//如果是星期天,对应的索引值是0。事实上,很做电子日历起初都是把星期日放在最前面
//然后才是星期一星期二等等
}
void printCalendar(int year) {
setFebDay(year);

printf("=====================The Calendar of Year %d====================|\n", year);

for (int i = 1; i <= 6; i++) {

}
}

判断素数的函数

C++

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
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
//O(sqrt(N)),N为你输入的质数或者合数大小
int main()
{
int n;
scanf("%d", &n);
int x;

for (int i=1;i<=n;i++){
scanf("%d", &x);
bool is_prime = true;

//数字2是质数
if(x == 2) is_prime = true;
//如果是大于二的偶数或者是1则不是质数
//事实上,1既不是质数也不是合数
else if(x % 2==0||x<2) is_prime = false;
//其余的奇数的情况。

else
{
//j从3开始,每次加2.
//有如下定理,如果N是合数,那么存在T属于2<=T<=根号N,使得T整除N
for(int j = 3; j*j<=x;j+=2)
{
if(x%j == 0 )
{
is_prime = false;
break;
}
}
}
if(is_prime == true) printf("%d is prime\n",x);
else printf("%d is not prime\n",x);
}
return 0;
}

C

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
#define _CRT_SECURE_NO_WARNINGS
#define SIZE(a) (sizeof(a) / sizeof(a[0]))
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<stdbool.h>

#define N 100

bool isPrime(int x);
int main(void) {
int x;
scanf("%d", &x);
if (isPrime(x)) printf("is prime\n");
else printf("is not prime\n");
return 0;
}

bool isPrime(int x) {
bool is_prime = true;

//数字2是质数
if (x == 2) is_prime = true;

//如果是大于二的偶数或者是1则不是质数
//事实上,1既不是质数也不是合数
else if (x % 2 == 0 || x < 2) is_prime = false;
//其余的奇数的情况。
else {
//i从3开始,每次加2.
//有如下定理,如果N是合数,那么存在T属于2<=T<=根号N,使得T整除N
for (int i = 3; i * i <= x; i += 2) {
if (x % i == 0) {
is_prime = false;
break;
}
}
}

return is_prime;
}

递归

从递归的定义去理解递归不是很好,我们可以从名字入手去理解。

  1. 递:把大问题分解成若干个子问题,子问题的求解方式和大问题一致,只是问题规模不一致。
    1. 归:把子问题的解合并成大问题的解。

例子1:电影院的例子。

例子2:Fibonacci 数列。

1
2
3
4
5
// 0, 1, 1, 2
long long fib(int n) {
if (n == 0 || n == 1) return n;
return fib(n-2) + fib(n-1);
}

但是这种求解方式的效率很低,会存在大量重复的计算。如下图所示

so,如何避免重复计算问题呢?答案是动态规划。顺序求解子问题,并将子问题的解保存起来,从而避免重复计算,最终求解到大问题。

0 1 1 2 3 5 8

但是对于求解 Fibnacci 数列来说,我们并不需要保存前面所有项的值,我们只需要保存最近两项即可

1
2
3
4
5
6
7
8
9
10
11
long long fib(int n) {
if (n == 0 || n == 1) return n;
long long a = 0, b = 1;
for (int i = 2; i <= n; i++) {
// 计算fib(i)的值
long long tmp = a + b;
a = b;
b = tmp;
}
return b;
}

例子3:汉诺塔

有三根杆子A,B,C。A杆上有 N 个 (N>1) 穿孔圆盘,盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至 C 杆:

  1. 每次只能移动一个圆盘;

  2. 大盘不能叠在小盘上面。

    提示:可将圆盘临时置于 B 杆,也可将从 A 杆移出的圆盘重新移回 A 杆,但都必须遵循上述两条规则。

    问:最少需要移动多少次?如何移?

    (1) 输入:

    n = 1

    输出:

    total step(s): 1

    A –> C

    (2) 输入:

    n = 2

    输出:

    total step(s): 3

    A –> B

    A –> C

    B –> C

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
#define _CRT_SECURE_NO_WARNINGS
#define SIZE(a) (sizeof(a) / sizeof(a[0]))
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<stdbool.h>
#include<math.h>
#define N 100
void hanio_helper(int n, char c1, char c2, char c3);
void hanio(int n);
long long fib(int n) {
if (n == 1 || n == 0) return n;
long long a = 0;
long long b = 1;
for (int i = 2; i <= n; i++) {
long long tmp = a + b;
a = b;
b = tmp;
}
return b;

}
int main(void) {
int n;
scanf("%d", &n);
hanio(n);
return 0;
}
//规定第一个位置是初始位置,第二个位置是辅助的柱子,第三个是目标的柱子
//命名成name1,name2,name3意思是第一个第二个第三个柱子的名字
void hanio_helper(int n, char name1, char name2, char name3) {
//边界条件
//n == 1
if (n == 1) {
printf("%c --> %c\n", name1, name3);
return;
}
//递归公式
// 1.将上面n-1个盘子移动到中间杆子上
hanio_helper(n - 1, name1, name3 , name2);
// 2.把最大的盘子移动到目标杆子上
printf("%c --> %c\n", name1 , name3);
// 3.把中间杆子上的n-1个盘子移动到目标杆子上
hanio_helper(n - 1, name2, name1, name3);
}

void hanio(int n) {
// S(n) = S(n - 1) + 1 + S(n - 1)
// S(1) = 1
// 等比序列求和
long long steps = (1 << n) - 1;
printf("total steps is: %lld\n", steps);
//委托,外包
hanio_helper(n, 'A', 'B', 'C');
}


例子4:约瑟夫环问题(每2人干掉一个)

约瑟夫环是一个数学的应用问题:已知 n 个人 (以编号1,2,3, …, n 分别表示) 围坐在一张圆桌周围。从编号为 1 的人开始,每两个人出列一个人,直至只剩一个人。问:最终剩下的这个人的编号是多少?

思路:
循环链表, 循环数组:

空间复杂度:O(n)

时间复杂度:O(n)

但如果用递归

空间复杂度:O(logn),因为栈的深度

时间复杂度:O(logn)

递推公式

joseph(n) = 2 * joseph(n / 2) - 1 n为偶数

joseph(n) = 2 * joseph((n - 1) / 2) + 1 n为奇数

边界条件

n == 1 return 1;joseph(1) = 1

n == 2 return 1;joseph(2) = 1

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<stdbool.h>
int joseph(int n);

int main(void)
{
int n;
scanf("%d", &n);
printf("%d", joseph(n));
return 0;
}
int joseph(int n) {
if (n == 1 || n == 2) return 1;
else if (n > 2 && n % 2 == 0) return 2 * joseph(n / 2) - 1;
else return 2 * joseph((n - 1) / 2) + 1;
}

例子5:约瑟夫环问题(每m人干掉一个)

循环链表

空间复杂度:O(n)

时间复杂度:O(m * n)

递推公式joseph(n, m) = (joseph(n - 1, m) + m) % n

边界条件joseph(1, m) = 0 // 这里的编号是从0到n - 1

空间复杂度:O(n)

时间复杂度:O(n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<stdbool.h>
int joseph(int n, int m);

int main(void)
{
int n;
int m;
scanf("%d%d", &n, &m);
printf("%d", 1 + joseph(n, m));//因为这里的joseph函数编号是0到n - 1
return 0;
}
int joseph(int n , int m) {
if (n == 1) return 0;
return (joseph(n - 1, m) + m) % n;

}

总结:递归三问

Q1:什么情况下可以考虑使用递归?

答:问题具有递归结构。(1)也就是说,大问题可以分解成若干个子问题,子问题的求解方式和大问题一致,只是问题规模不一致。(2)子问题的解可以合并成大问题的解。

Q2: 到底要不要使用递归?

答:如果不存在重复计算问题,且递归的层次不是很深时,就可以使用递归。

Q3: 如何写递归?

答:两步走。(1) 边界条件 (2) 递归公式

  1. (拓展题,不要求每个同学都作答) 德州扑克:写一个程序循环读取 5 张手牌 (输入 0 结束程序),然后把手中的牌分为下面某一类:1.同花顺 2.四张 3.葫芦 (3 + 2) 4. 同花 5. 顺子 6.三张 7.两对 8. 一对 9.高牌。程序对话如下:

    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
    Enter a card: 2s
    Enter a card: 5s
    Enter a card: 4s
    Enter a card: 3s
    Enter a card: 6s
    Straight flush

    Enter a card: 8c
    Enter a card: as
    Enter a card: 8c
    Duplicate card; ignored.
    Enter a card: 7c
    Enter a card: ad
    Enter a card: 3h
    Pair

    Enter a card: 6s
    Enter a card: d2
    Bad card; ignored.
    Enter a card: 2d
    Enter a card: 9c
    Enter a card: 4h
    Enter a card: ts
    High card

    Enter a card: 0
    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
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    #include <stdio.h>
    #include <stdbool.h>

    void read_cards(void);
    void analyze_hand(void);
    void print_result(void);

    int nums_in_suit[4], nums_in_rank[13];

    bool flush, straight, four, three;
    int pairs; /* can be 0, 1 or 2*/

    int main(void) {
    for (;; ) {
    read_cards();
    analyze_hand();
    print_result();
    }
    }

    void print_result(void) {
    if (flush && straight)
    printf("Flush straight\n");
    else if (four)
    printf("Four\n");
    else if (three && pairs)
    printf("Full house\n");
    else if (flush)
    printf("Flush\n");
    else if (straight)
    printf("Straight\n");
    else if (three)
    printf("Three\n");
    else if (pairs == 2)
    printf("Two pairs\n");
    else if (pairs)
    printf("One pair\n");
    else
    printf("High card\n");

    printf("\n");
    }

    void analyze_hand(void) {
    /* 初始化 */
    flush = false;
    straight = false;
    four = false;
    three = false;
    pairs = 0;

    /*判断是否是同花*/
    for (int i = 0; i < 4; i++) {
    if (nums_in_suit[i] == 5) {
    flush = true;
    break;
    }
    }

    /* 判断是否是顺子 */
    int idx = 0;
    while (nums_in_rank[idx] == 0)
    idx++;
    // nums_in_rank[i] != 0;
    int n_consective = 1;

    while (idx < 12 && nums_in_rank[++idx]) {
    n_consective++;
    }

    if (n_consective == 5) {
    straight = true;
    return;
    }

    /* check four, three, two */
    for (int i = 0; i < 13; i++) {
    if (nums_in_rank[i] == 4) {
    four = true;
    } else if (nums_in_rank[i] == 3) {
    three = true;
    } else if (nums_in_rank[i] == 2) {
    pairs++;
    }
    }

    }

void read_cards() {
// 初始化操作
bool in_hand[4][13] = { false };

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
64
65
	for (int i = 0; i < 13; i++) {
nums_in_rank[i] = 0;
}
for (int i = 0; i < 4; i++) {
nums_in_suit[i] = 0;
}

// 读取5张卡牌
int cards_read = 0;
while (cards_read < 5) {
bool bad_card = false;

printf("Enter a card: ");

char c = getchar();
int rank;
switch (c) {
case '0': exit(0);
case '2': rank = 0; break;
case '3': rank = 1; break;
case '4': rank = 2; break;
case '5': rank = 3; break;
case '6': rank = 4; break;
case '7': rank = 5; break;
case '8': rank = 6; break;
case '9': rank = 7; break;
case 't':case 'T': rank = 8; break;
case 'j':case 'J': rank = 9; break;
case 'q':case 'Q': rank = 10; break;
case 'k':case 'K': rank = 11; break;
case 'a':case 'A': rank = 12; break;
default: bad_card = true;
}

c = getchar();
int suit;
switch (c) {
case 'd':case 'D': suit = 0; break;
case 'c':case 'C': suit = 1; break;
case 'h':case 'H': suit = 2; break;
case 's':case 'S': suit = 3; break;
default: bad_card = true;
}

// 处理这一行的剩余字符
while ((c = getchar()) != '\n') {
if (c != ' ' && c != '\t')
bad_card = true;
}

if (bad_card) {
printf("Bad card; ignored.\n");
}
else if (in_hand[suit][rank]) {
printf("Duplicate card; ignored.\n");
}
else {
in_hand[suit][rank] = true;
cards_read++;
nums_in_rank[rank]++;
nums_in_suit[suit]++;
}
}
}

  1. 编写程序模拟掷骰子的游戏(两个骰子)。第一次掷的时候,如 果点数之和为 7 或 11 则获胜;如果点数之和为2、3或12则落败;其他情况下的点数之和称为“目标”,游戏继续。在后续的投掷中,如果玩家再次掷出“目标”点数则获胜,掷出7则落败,其他情况都忽略,游戏继续进行。每局游戏结束时,程序询问用户是否再玩一次,如果用 户输入的回答不是 y 或 Y ,程序会显示胜败的次数然后终止。(拓展题,不要求每个同学作答)

    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
    You rolled: 8
    Your point is 8
    You rolled: 3
    You rolled: 10
    You rolled: 8
    You win!

    Play again? y

    You rolled: 6
    Your point is 6
    You rolled: 5
    You rolled: 12
    You rolled: 3
    You rolled: 7
    You lose!

    Play again? y

    You rolled: 11
    You win!

    Play again? n

    Wins: 2 Losses: 1
    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
    64
    65
    66
    67
    68
    69
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    #include <stdbool.h>

    bool play_game();
    int roll_dice();

    int main(void) {
    int wins = 0, losses = 0;
    char again;
    do {
    bool result = play_game();
    if (result) {
    wins++;
    } else {
    losses++;
    }

    printf("\nPlay again?");

    again = getchar();
    while (getchar() != '\n')
    ;
    // scanf(" %c", &again);

    } while (again == 'y' || again == 'Y');

    printf("\nWins: %d, losses: %d\n", wins, losses);
    }

    int roll_dice() {
    int r1 = rand() % 6 + 1;
    int r2 = rand() % 6 + 1;
    printf("You rolled: %d\n", r1 + r2);

    return r1 + r2;
    }

    bool play_game() {
    srand(time(NULL));

    int tolly = roll_dice();

    if (tolly == 7 || tolly == 11) {
    printf("You win!\n");
    return true;
    }
    if (tolly == 2 || tolly == 3 || tolly == 12) {
    printf("You lose!\n");
    return false;
    }

    int point = tolly;
    printf("Your point is %d", point);

    for (;;) {
    tolly = roll_dice();

    if (tolly == point) {
    printf("You win!\n");
    return true;
    }
    if (tolly == 7) {
    printf("You lose!\n");
    return false;
    }
    }
    }