编程学习

编程学习

博客里已经有Rust学习了,但那个准备更一些从零开始学习的东西

这篇博客就记录一下我在刷牛客和leetcode时,发现的一些小知识吧,因为不限语言,所以叫做编程学习

1.牛客 CPP11判断季节

题目:CPP11判断季节

这里我首先想到的解法是使用 if - else 判断结构:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;
 
int main() {
     
    int month;
    cin >> month;
 
    if (month == 3 || month == 4 || month == 5) {
        cout << "春季";
    } else if (month == 6 || month == 7 || month == 8) {
        cout << "夏季";
    } else if (month == 9 || month == 10 || month == 11) {
        cout << "秋季";
    } else if (month == 1 || month == 2 || month == 12) {
        cout << "冬季";
    } else {
        cout << "不合法";
    }
 
    return 0;
}

但总感觉这样实现不太优雅,看题解发现有人使用switch 判断结构:

 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
#include <iostream>
#include <ostream>
using namespace std;

int main() {
    
    int month;
    cin >> month;

    switch (month) {
        case 1:
        case 2:
        case 12:
            cout << "冬季" << endl;
            break;
        case 3:
        case 4:
        case 5:
            cout << "春季" << endl;
            break;
        case 6:
        case 7:
        case 8:
            cout << "夏季" << endl;
            break;
        case 9:
        case 10:
        case 11:
            cout << "秋季" << endl;
            break;
        default:
            cout << "不合法" << endl;
    }

    return 0;
}

但是这样写得还是有点不太优雅

最后看最高赞题解是这样,同样是switch 判断

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;
 
int main() {
     
    int month;
    cin >> month;
    if(month < 1 || month > 12) //优先判断是否合法月份
        cout << "不合法" << endl;
    else{
        switch(month){ //根据月份判断
            case 3 ... 5: //连续的值
                cout << "春季" << endl;  break;
            case 6 ... 8:
                cout << "夏季" << endl;  break;
            case 9 ... 11:
                cout << "秋季" << endl;  break;
            default:
                cout << "冬季" << endl;
        }
    }
    return 0;
}

这样写的原因是,首先为了避免使用麻烦的 if - else ,选择使用switch , 而且因为是判断季节,月份的值大多是连续的,所以可以使用case的连续写法:在case中,可以用x…y表示范围在 [x,y] 的值,两边都是闭区间。

所以在代码中 case3: case4: case5: 就变成了 case 3...5: 最后冬季不连续,就使用 default 代替,由于已经使用 if 将月份值限制在 1 - 12范围内,除去以上三种情况就剩冬季了

2.牛客 CPP12 求 1~n之间偶数的和

题目: CPP12 求 1~n之间偶数的和

好久没写了连while的用法都忘了,按理说以前这个同样在牛客上用rust写过,应该是不能忘的QAQ

while 循环和 for 循环本质是同一种循环的两种不同形式

while 循环事先只设定好了循环的终点,在此范围内不断执行语句,具体其他的限制条件在循环内部定义

for 循环则事先设定好了循环的终点,以及循环的规则(如 i++ 设定在循环外)

因此while循环要比 for 循环稍微灵活一些

在Rust 中,在程序编译时,编译器会把 while 去糖化变成 “一个无限循环 + 一个条件判断和 break” ,类似于这样:

1
2
3
4
5
let mut i = 0;
while i < 10 {
    println!("{}", i);
    i += 1;
}

在 MIR 层面,它会被转换成类似下面这样的逻辑:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let mut i = 0;
loop {
    if i < 10 {
        // 循环体
        println!("{}", i);
        i += 1;
    } else {
        break; // 条件不满足,退出循环
    }
}

同样的,编译器也会对 for 循环做相似的去糖化处理:

去糖化过程:for -> while let -> loop

Rust 中的 for 循环之所以强大和灵活,是因为它本质上是在遍历一个迭代器(Iterator),因此,任何一个 for 循环都会被编译器转换成一个使用 while let 和迭代器的等价结构

  1. 牛客 CPP13 计算一个数的阶乘

CPP13 计算一个数的阶乘

最适合区分递归和迭代的题目,但我只想到了迭代的实现,忘记递归是怎么实现的了

迭代:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;

int main() {
    
    int n;
    cin >> n;
    long long factorial = 1;
    
    for (int i = 2; i <= n; i++) {
        factorial *= i;
    }
    

    cout << factorial << endl;
    
    return 0;
}

递归:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;

long long fac(int n) {
    if (n == 1) {
        return 1;
    }

    return fac(n-1) * n;
}

int main() {
    
    int n;
    cin >> n;
    long long factorial = fac(n);

    cout << factorial << endl;
    
    return 0;
}

这里遇见了一个小小的问题,C++中函数声明必须在调用前,但Rust就不需要

这里写一下相同的问题使用Rust是如何解决的:

迭代:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
use std::io;

fn main() {
    let mut stdin = String::new();

    io::stdin().read_line(&mut stdin).expect("");

    let n = stdin
        .trim()
        .parse()
        .expect("");

    let mut x = 1;
    for i in 2..=n {
        x *= i;
    }

    println!("{}", x);
}

递归:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use std::io;

fn main() {
    let mut stdin = String::new();

    io::stdin().read_line(&mut stdin).expect("");

    let n = stdin
        .trim()
        .parse()
        .expect("");

    let x = factorial(n);

    println!("{}", x);
}

fn factorial(n: i64) -> i64 {
    if n == 1 {
        return 1;
    }

    return n * factorial(n - 1);
}
  1. 牛客CPP2 实现四舍五入

这里看到了两种做法,第一种实现是: 由于C++中double类型是自动四舍五入的,所以直接格式化输出0位小数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <iostream>
using namespace std;

int main() {
    double a;

    cin >> a;
    
    printf("%.0f", a);
    
    return 0;

}

在题解中看到更加优雅的实现方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <iostream>
using namespace std;

int main() {
    double d;
    cin >> d;
    
    if (d > 0) {
        cout<<(int)(d+0.5);
    }
    else {
        cout<<(int)(d-0.5);
    }
    return 0;

}

这个是游戏编程里常用的方法,正数浮点数+0.5(负数浮点数-0.5)后强制类型转换相当于四舍五入

这个原理是:由于强制转型的时候程序会直接截断小数(向零取整),所以对任意一个正数加0.5,如果它的小数部分>0.5 则进一,然后直接截断,对于负数同理,减去0.5,然后向零取整.

这样就实现了四舍五入的功能.

endl 和 /n 的区别:

endl: endl 会输出 ‘\n’ ,然后立即刷新缓冲区并输出到屏幕上,由于要刷新缓冲区, endl 会比 \n 慢一点

5.牛客CPP4 获取两数中的较大值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <iostream> 
using namespace std;

int main() {
    int a;
    int b;
    
    scanf("%d%d", &a,&b);
    printf("%d", a>b?a:b);
    
    return 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
#include <iostream>
#include <vector>

void selection_sort(std::vector<int> &nums) {
  int n = nums.size();
  for (int i = 0; i < n - 1; i++) {
    int k = i;
    for (int j = i + 1; j < n; j++) {
      if (nums[j] < nums[k]) {
        k = j;
      }
    }
    std::swap(nums[i], nums[k]);
  }

}

int main() {
  int n;
  std::cin >> n;
  std::vector<int> nums(n);
  for (int i = 0; i < n; i++) {
    std::cin >> nums[i];
  }
  selection_sort(nums);
  for (int i = 0; i < n; i++) {
    std::cout << nums[i] << " ";
  }
  return 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
#include <iostream>
#include <vector>

void bubble_sort(std::vector<int> &nums) {
  int n = nums.size();
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < n - i - 1; j++) {
      if (nums[j] > nums[j + 1]) {
        std::swap(nums[j], nums[j + 1]);
      }
    }
  }
}

int main() {
  int n;
  std::cin >> n;
  std::vector<int> nums(n);
  for (int i = 0; i < n; i++) {
    std::cin >> nums[i];
  }
  bubble_sort(nums);
  for (int i = 0; i < n; i++) {
    std::cout << nums[i] << " ";
  }
  return 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
#include <iostream>
#include <vector>

void bubble_sort(std::vector<int> &nums) {
  int n = nums.size();
  for (int i = n - 1; i > 0; i--) {
    for (int j = 0; j < i; j++) {
      if (nums[j] > nums[j + 1]) {
        std::swap(nums[j], nums[j + 1]);
      }
    }
  }
}

int main() {
  int n;
  std::cin >> n;
  std::vector<int> nums(n);
  for (int i = 0; i < n; i++) {
    std::cin >> nums[i];
  }
  bubble_sort(nums);
  for (int i = 0; i < n; i++) {
    std::cout << nums[i] << " ";
  }
  return 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
#include <iostream>
#include <vector>

void bubble_sort(std::vector<int> &nums) {
  int n = nums.size();
  for (int i = n - 1; i > 0; i--) {
    bool flag = false;  // 在这里每轮重置flag
    for (int j = 0; j < i; j++) {
      if (nums[j] > nums[j+1]) {
        std::swap(nums[j], nums[j+1]);
        flag = true;
      }
    }
    if (!flag) {
      break;
    }
  }
}

int main() {
  int n;
  std::cin >> n;
  std::vector<int> nums(n);
  for (int i = 0; i < n; i++) {
    std::cin >> nums[i];
  }
  bubble_sort(nums);
  for (int i = 0; i < n; i++) {
    std::cout << nums[i] << " ";
  }
  return 0;
}

这里有个每轮重置flag的细节我一开始没有注意到,如果把flag定义在所有循环的外面,那么如果遇到只需要交换一次的情况,由于第一轮已经将flag设置为true,后续即使不再交换,还是会过排序逻辑,优化就失效了;但如果每轮重置flag的话,就可以很好地应对只交换少量次数的情况了,效率也提高了一些.

Licensed under CC BY-NC-SA 4.0
Build by Oight
使用 Hugo 构建
主题 StackJimmy 设计