C++放課

怎麼寫C++?

IDE

Dev-C++

codeblocks

Hello, world!

#include <iostream>
using namespace std;
int main(){
	cout << "Hello, world!\n";
}

建議全部自己打一次

#include<iostream>

引入已經寫好的程式 像cout

using namespace std;

把std::cout變成cout

int main()

程式一開始進入的地方

變數

儲存資料的東西

型別

int

整數

範圍:

-2^{31}
2^{31}-1

~

佔用空間: 4 Bytes

float

浮點數

精準至小數點後七位

佔用空間: 4 Bytes

char

字元

佔用空間: 1 Bytes

用ASCII碼儲存

'   '表示一個字元

型別

int

整數

範圍:

-2^{31}
2^{31}-1

~

佔用空間: 4 Bytes

float

浮點數

精準至小數點後七位

佔用空間: 4 Bytes

char

字元

佔用空間: 1 Bytes

用ASCII碼儲存

'   '表示一個字元

string

字串

可以儲存多個字元

"   "表示一個字串

bool

布林(真假)值

儲存true(1)和false(0)

佔用空間: 1 Bytes

宣告

//變數型別 變數名稱;
int x = 0;
float y = 1.5;
char c = 'c';
bool b = 1;
string s = "Hello, world!";

輸入輸出

#inculde<iostream>
using namespace std;
int main(){
    int a, b;
    cin>>a>>b;
    cout<<"a:"<<a<<" b:"<<b<<'\n';
}

運算子

int x;
x = 3;
x = 2 + 5;//x = 7
x = x - 1;//x = 6
x = 3 * x;//x = 18
x = x / 5;//x = 3
x = x % 3;//x = 0

+

- 減

*

% 取餘

/ 除

整數/整數是取商

= 指定

()括號

運算子

int x;
x = 3;
cout<<x++<<'\n';//cout 3, x = 4
cout<<++x<<'\n';//cout 5, x = 5
x -= 1;
x *= 3;
x %= 2;

++ 遞增

-- 遞

運算後指定

BMI計算機

輸入身高(公分)、體重(公斤),計算出BMI

BMI=體重(kg) \div 身高^2(m)

Sample Input

180 70

Sample output

21.6049

型別轉換

邏輯運算子

< 小於

> 大於

<= 小於等於

>= 大於等於

==相等

(注意不是=)

!=不相等

IF

if(條件){
    你要執行的東西
}
例如:
int a = 5, b = 4;
if(a > b){
   cout<<"a > b\n";
}

邏輯運算子

&& 邏輯AND

|| 邏輯OR

! 邏輯NOT

AND和OR可以用來連接兩個條件

   A&&B B = TRUE B = FALSE
A = TRUE     TRUE    FALSE
A = FALSE    FALSE    FALSE
    A||B B = TRUE B = FALSE
A = TRUE     TRUE     TRUE
A = FALSE     TRUE    FALSE
         -  A = TRUE  A = FALSE
       !A false    true

邏輯運算子

int a;
cin>>a;
if(4 <= a && a < 7){
    cout<<"4 <= a < 7\n";
}
if(4 <= a < 7){
	cout<<"4 <= a < 7\n";
}

else and else if

/*當上方的if(else if)的條件判斷是false時
才會判斷(執行)下面的else if(else)*/
int a, b;
cin>>a>>b;
if(a>b){
    cout<<"a>b\n";
}
else if(a<b){
    cout<<"a<b\n";
}
else{
    cout<<"a==b\n";
}

成績等第

輸入一個正整數\ x \ 代表此學生的成績\\然後有四個正整數a, b,c,d代表判斷標準\\運用下表將成績轉換成等第
x\ge a\ 優\\ a>x\ge b\ 甲\\ b>x\ge c\ 乙\\ c>x\ge d\ 丙\\ x< d \ 丁\\ a>b>c>d

INPUT

OUTPUT

75 90 80 70 60

66 80 68 67 66

99 100 98 50 40

迴圈

讓程式重複執行

while

int i,n;
cin>>n;
i=0;
while(i<n){
    cout<<i<<' ';
    i++;
}
while(條件){
    要執行的東西;
}

for迴圈

int n;
cin>>n;
for(int i=0;i<n;i++){
    cout<<i<<' ';
}
for(第一次進入;條件;每次結束){

}

1

3

2

4

5

break

int n;
cin>>n;
while(1){
    cout<<i++<<' ';
    if(i>=n) break;
}

跳出迴圈

3n+1猜想

輸入

n

如果   是奇數

n
n=3n+1

如果   是偶數

n
n=n\div2
n

輸出過程中   的數值直到

n=1

INPUT

3

4

OUTPUT

3 10 5 16 8 4 2 1

4 2 1

陣列

一連串的變數

宣告方式:

陣列變數型別 陣列名稱[陣列大小];

範例:

int array[100];

陣列

陣列是從零開始

使用方式:

陣列名稱[編號]

範例:

int array[100];
array[0] = 5;

                 0                  1                   2

迴圈與陣列

利用迴圈的計數器當作索引

範例:

輸入n個數,並存在陣列中

#include<iostream>
using namespace std;
int main() {
	int n, a[100];
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> a[i];
	}
}

交換

INPUT

第一行有兩個正整數n q

第二行有n個整數代表數列A的值

接下來有q行,每一行有兩個正整數i j

代表要交換的數的編號

編號從1開始

OUTPUT

輸出操作後的數列

給一個數列A,接著有q個操作每次操作會交換數列裡的兩個編號的數,在最後輸出操作後的數列

n < 10^6 ,\ q< 10^6

5 4

5 4 2 3 1

1 3

1 2

3 4

1 5

1 2 3 5 4

眾數

眾數的定義是在一個數列A出現次數最多的數

給定一個數列並找出它的眾數

INPUT

第一行一個正整數n代表數列A長度

第二行有n個數,代表數列A的值

OUTPUT

輸出眾數

5

3 2 2 2 1

10

9 9 8 3 4 7 1 3 4 9

2

 

9

n < 10^6,\ 0<=數列的值< n

函數

就像數學函數一樣

f(x)=ax+b

你可以自己定義函數

函數裡就可以放程式碼

語法

回傳資料型別 函數名稱(參數資料型別 參數名稱, ...){
    執行的程式碼
    可用return回傳並結束函數
}

寫一個相加的函數試試看

#include<iostream>
using namespace std;
int f(int a, int b){
	return a + b;
}
int main(){
	int x, y;
    cin >> x >> y;
    cout << f(x, y) << '\n';
}

函數要幹嘛

如果你的程式有幾百行

透過函數可以把整個程式分成多個函數

讓程式比較好閱讀

也比較好debug

class類別

設計自己的物件

可以把多個變數和函式包在裡面

設計一個變數代表學生成績

  • 學生姓名
  • 國文成績
  • 英文成績
  • 數學成績

使用方式

class 物件名稱{
public:
    包含變數
    ...
};

範例

#include<iostream>
using namespace std;
class Student{
public:
    string name;
    int mandarin_score, math_score, english_score;
};

可以把他當一種變數

#include<iostream>
using namespace std;
class Student{
public:
    string name;
    int mandarin_score, math_score, english_score;
};
int main(){
	Student student1;
}

加上.來使用裡面的變數

#include<iostream>
using namespace std;
class Student{
public:
    string name;
    int mandarin_score, math_score, english_score;
};
int main(){
	Student student1;
    student1.name = "Scott";
}

也可以在物件裡面放函數

#include<iostream>
using namespace std;
class Student {
public:
    string name;
    int mandarin_score, math_score, english_score;
    int sum() {
        return mandarin_score + math_score + english_score;
    }
    double avg() {
        return (double)sum() / 3;
    }
};
int main() {
    Student student1;
    student1.name = "Scott";
    student1.mandarin_score = 80;
    student1.english_score = 75;
    student1.math_score = 90;
    cout << student1.sum() << ' ' << student1.avg() << '\n';
}

遞迴

遞迴是什麼?

  • 一種函式
  • 自己呼叫自己

聽起來很抽象?

舉例

階乘

\(n! = 1 * 2 * 3 ... * (n - 1) * n\)

定義函式\(f(n)\)為n!的值

舉例

\(n! = n * (n - 1)!\)

 

用遞迴表示\(f(n)\)

\(f(n) = f(n - 1) * n\)

\(f(3) = f(2) * 3\)

\(f(2) = f(1) * 2\)

\(f(1) = f(0) * 1\)

\(f(0) = f(-1) * 0\)

......\(\infty\)

寫出來看看

什麼時候結束?

遞迴終止條件

設結束條件

定f(1) = 1

\(f(3) = f(2) * 3\) = 6

\(f(2) = f(1) * 2\) = 2

\(f(1) = 1\)

試試看吧

  • 判斷n是否等於\(1\)
  • \(n = 1\), 回傳\(1\)
  • \(n \not = 1\), 回傳\(n * f(n - 1)\)

範例測資

輸入:5  輸出:120

輸入:8  輸出:40320

#include<iostream>
using namespace std;
int f(int n) {
	if (n == 1)
		return 1;
	return n * f(n - 1);
}
int main() {
	int x;
	cin >> x;
	cout << f(x) << '\n';
}

費氏數列

  • 1, 1, 2, 3, 5, 8, 13, 21, 34...
  • 第n項是前兩項和
  • \(f(1) = 1, f(2) = 1\)
  • \(f(n) = f(n - 1) + f(n - 2)\)

範例測資

輸入:5  輸出:5

輸入:9  輸出:34

#include<iostream>
using namespace std;
int f(int n) {
	if (n == 1)
		return 1;
	if (n == 2)
		return 1;
	return f(n - 1) + f(n - 2);
}
int main() {
	int x;
	cin >> x;
	cout << f(x) << '\n';
}

河內塔

有a, b, c三根桿子,一開始有N個盤子由大到小往上疊,問最少要花多少步才能把所有盤子從a桿子移到c桿子。

#include<iostream>
using namespace std;
int f(int n) {
	if (n == 1)
		return 1;
	return f(n - 1) * 2 + 1;
}
int main() {
	int x;
	cin >> x;
	cout << f(x) << '\n';
}

實際應用

遞迴枚舉

可以把每一個數字分為拿和不拿,透過遞迴跑過每個可能的狀況。

遞迴枚舉

給一個有長度為n的整數陣列a,計算其中有多少種組合的和等於k。

#include<iostream>
using namespace std;
int n, k, ans = 0, a[30];
void f(int i, int sum = 0) {
	if (i == n) {
		if (sum == k)
			ans++;
		return;
	}
	f(i + 1, sum + a[i]);
	f(i + 1, sum);
}
int main() {
	cin >> n >> k;
	for (int i = 0; i < n; i++) {
		cin >> a[i];
	}
	f(0);
	cout << ans << '\n';
}

a524

排序

照順序排

6 3 8 0 9 => 0 3 6 8 9

照順序排

6 3 8 0 9 => 0 3 6 8 9

  • 選擇排序
  • 泡沫排序
  • 插入排序

選擇排序

找到最大的數,放到最後面

選擇排序

找到最大的數,放到最後面

泡沫排序

看相鄰兩個,如果順序相反就交換

選擇排序

找到最大的數,放到最後面

泡沫排序

看相鄰兩個,如果順序相反就交換

插入排序

一個一個把數字插入到正確的位置

sort函式

#include<algorithm>

預設從小到大

用法

sort(起始指標, 結尾指標)

不包含

起始指標

結尾指標

排序範圍

實際用法

有一個陣列a, 要排序a[0]~a[n - 1]

  • sort(*a[0], *a[n]);
  • sort(a, a + n);

如果要由大到小呢?

  • 寫一個回傳bool的比較函式
  • sort(起始指標, 結尾指標, 比較函式名稱)
#include<iostream>
#include<algorithm>
using namespace std;
int a[200005];
bool cmp(int a, int b) {
	return a > b;
}
int main() {
	int n;
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> a[i];
	}
	sort(a, a + n, cmp);
	for (int i = 0; i < n; i++) {
		cout << a[i] << ' ';
	}
	cout << '\n';
}

二分搜尋法

玩過猜數字嗎?

  • 每次猜一個數字
  • 如果猜到就結束遊戲
  • 如果沒猜到,會知道答案比你猜的數字大或小

有沒有一個策略可以讓你有在最差情況下,猜的次數最少?

每次猜數字範圍的中間

  • 猜到就結束
  • 如果答案比猜的小,可以刪掉所有比它大的
  • 如果答案比猜的大,可以刪掉所有比它小的

最慘會猜幾次?

  • 假設數字範圍有\(n\)個數字
  • 每次沒猜到都會刪掉一半
  • 設最壞情況下會猜k次
  • 在猜到第k-1次的時候,可能的範圍要多於一個
{\frac n {2^{k - 1}}} > 1
n > 2^{k - 1}
\log_{2}n > k - 1

假設要猜的範圍是\(0\)~\(2^{31} - 1\)

最多只需要猜31次!

用數學算算看

二分搜尋法

在一個有序陣列中快速找到一個值的演算法

每次找搜尋陣列範圍的中間位置

如果他比你的目標大,就往左找

如果他比你的目標小,就往右找

二分搜尋法

遞迴想法

設函式\(f(l, r, val)\)為整數\(val\)在陣列a[\(l\)~ \(r\)]區間中的位置

  • 如果\(l\) == \(r\),回傳\(l\)
  • 找陣列a[\(l\) ~ \(r\)]中間位置\(mid = (l + r) / 2\)
  • 如果a[\(mid\)]=val,回傳\(mid\)
  • 如果a[\(mid\)]>val,回傳\(f(l, mid - 1, val)\)
  • 如果a[\(mid\)]<val,回傳\(f(mid + 1, r, val)\)
#include<iostream>
#include<algorithm>
using namespace std;
int a[200005];
int f(int l, int r, int val) {
	if (l == r) {
		if (a[l] == val)
			return l;
		else
			return -1;
	}
	int mid = (l + r) / 2;
	if (a[mid] == val)
		return mid;
	else if (a[mid] > val)
		return f(l, mid - 1, val);
	else
		return f(mid + 1, r, val);
}
int main() {
	int n;
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> a[i];
	}
	sort(a, a + n);
	int q;
	cin >> q;
	cout << f(0, n - 1, q) << '\n';
}
#include<iostream>
#include<algorithm>
using namespace std;
int a[200005];
int f(int l, int r, int val) {
	while (l < r) {
		int mid = (l + r) >> 1;
		if (a[mid] == val)
			return mid;
		else if (a[mid] < val)
			l = mid + 1;
		else
			r = mid - 1;
	}
	if (a[l] == val)
		return l;
	return -1;
}
int main() {
	int n;
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> a[i];
	}
	sort(a, a + n);
	int q;
	cin >> q;
	cout << f(0, n - 1, q) << '\n';
}

內建二分搜

#include<algorithm>

lower_bound(開始位置, 結束位置, 要找的值):

找第一個大於等於的

upper_bound(開始位置, 結束位置, 要找的值):

找第一個大於的

指標

儲存變數資料的記憶體位置

在變數前加上&符號就是他的指標

int a = 5;
cout << &a << '\n';
008FFCFC

指標變數

用於儲存變數的記憶體位置的變數

宣告方式

變數型別 *變數名稱;

int a = 5;
int *p = &a;

取得儲存的記憶體位置上的變數

int a = 5;
int *p = &a;
cout << *p << '\n';
*p = 4;
cout << *p << '\n';

參考

幫變數取一個新名字

int a = 5;
int& b = a;
cout << b << '\n';
b = 3;
cout << a << '\n';

自己實作交換函式

動態分配記憶體

new 變數型別

int *p;
p = new int;

設初始值

int *p;
p = new int(100);

delete 指標

int *p = new int(100);
delete p;

釋放記憶體

資料結構

list

可以快速插入或刪除一個數

list實作

#include<iostream>
using namespace std;
class list{
public:
    list* next;
    int val;
};
list* root=new list;
int main(){
    int n,i;
    list* now=root;
    cin>>n;
    for(i=0;i<n;i++){
        cin>>now->val;
        now->next=new list;
        now=now->next;
    }
    now=root;
    for(i=0;i<n;i++){
    	cout<<now->val<<' ';
        now=now->next;
    }
}

list實作

#include<iostream>
using namespace std;
class list{
public:
    list* next;
    int val;
};
list* root=new list;
void list_insert(list* now){
	list* t=now->next;
	now->next=new list;
	now=now->next;
	now->next=t;
}
void list_erase(list* now){
	now->next=now->next->next;
}

時間複雜度

時間複雜度是什麼?

  • 分析演算法執行時間
  • 假設每行程式執行時間相同

看看幾個程式

#include<iostream>
using namespace std;
int main()
{
    int n;
    cin >> n;
    cout << n << '\n';
    return 0;
}

執行次數: 3

#include<iostream>
using namespace std;
int main()
{
    for(int i = 0; i < 10; i++){
        cout << i << '\n';
    }
}

執行次數: 31

#include<iostream>
using namespace std;
int main()
{
    int n;
    cin >> n;
    for(int i = 0; i < n; i++){
        cout << i << '\n';
    }
}

執行次數: \(3n+2\)

#include<iostream>
using namespace std;
int main()
{
    int n;
    cin >> n;
    int a = 0;
    for(int i = 0; i < n; i++){
        for(int j = 0; j < n; j++){
            a++;
        }
    }
    cout << a << '\n';
    return 0;
}

執行次數: \(9n^2 + 3n + 4\)

BigO表示法

  • 忽略常數倍數
  • 取最高位

\(3\)

\(31\)

\(3n + 2\)

\(9n^2 + 3n + 4\)

\(O(1)\)

\(O(1)\)

\(O(n)\)

\(O(n^2)\)

Big-O表示法

執行次數

如果你的程式有機會跑很快呢

看一下這個程式

#include<iostream>
using namespace std;
int a[10005];
int main(){
	int n;
    cin >> n;
    for(int i = 0; i < n; i++){
		cin >> a[i];
	}
    int search;
	while(cin >> search){
    	bool found = 0;
    	for(int i = 0; i < n; i++){
        	if(search == a[i]){
            	cout << i << '\n';
                found = 1;
                break;
            }
        }
        if(!found){
        	cout << -1 << '\n';
        }
    }
}

這是一個循序搜尋的程式

如果他第一個就找到

那他是不是\(O(1)\)?

#include<iostream>
using namespace std;
int a[10005];
int main(){
	int n;
    cin >> n;
    for(int i = 0; i < n; i++){
		cin >> a[i];
	}
    int search;
	while(cin >> search){
    	bool found = 0;
    	for(int i = 0; i < n; i++){
        	if(search == a[i]){
            	cout << i << '\n';
                found = 1;
                break;
            }
        }
        if(!found){
        	cout << -1 << '\n';
        }
    }
}

這是一個循序搜尋的程式

如果他第一個就找到

那他是不是\(O(1)\)?

估算時間複雜度時

要用最差狀況評估

常見的時間複雜度

\(O(1)\)

\(O(logn)\)

\(O(\sqrt{n})\)

\(O(n)\)

\(O(nlogn)\)

\(O(n\sqrt{n})\)

\(O(n^2)\)

\(O(n^3)\)

\(O(2^n)\)

當然它也可以是各種排列組合

\(O(n\log^2n)\)

\(O(n \log n + q \log n)\)

不是所有內建函式都是O(1)

#include <iostream>
using namespace std;

int main()
{
	char a[20];
	cin >> a;
	for (int i = 0; i < strlen(a); i++) {
		cout << (char)(a[i] + 1);
	}
	return 0;
}

STL

STL

  • Standard Template Library
  • 有很多別人幫你寫好的工具
  • 很好用

vector

引入函式庫:vector

宣告方式:vector<陣列變數型別> 陣列名稱

取值方式:跟陣列一樣

可向後延伸的陣列

  • size():回傳大小
  • push_back():在陣列尾端新增一個數
  • pop_back():

常用函式

\(O(1)\)

\(O(1)\)

\(O(1)\)

#include <iostream>
#include <vector>
using namespace std;
int main()
{
	vector<int> a;
	a.push_back(5);
	a.push_back(3);
	a.push_back(4);
	cout << a.size() << '\n'; //輸出3
	cout << a[1] << '\n'; //輸出3
	a.pop_back();
	cout << a.size() << '\n'; //輸出2
}

stack

從上面放,從上面拿

引入函式庫:stack

宣告方式:stack<變數型別> 名稱

常用函式

  • size():回傳大小
  • top():回傳頂端的值
  • push():在最頂端放入值
  • pop():刪除最頂端的值

\(O(1)\)

\(O(1)\)

\(O(1)\)

\(O(1)\)

#include <iostream>
#include <stack>
using namespace std;
int main()
{
	stack<int> s;
	s.push(0);
	s.push(1);
	cout << s.size() << '\n'; //輸出2
	cout << s.top() << '\n'; //輸出1
	s.pop();
	cout << s.top() << '\n'; //輸出0
	
}

queue

像排隊一樣

引入函式庫:queue

宣告方式:queue<變數型別> 名稱

常用函式

  • size():回傳大小
  • front():回傳前面的值
  • back():回傳後面的值
  • push():從後面加一個值
  • pop():從前面刪掉一個值

\(O(1)\)

\(O(1)\)

\(O(1)\)

\(O(1)\)

\(O(1)\)

deque

結合stack和queue

引入函式庫:deque, stack, queue都可以

宣告方式:deque<變數型別> 名稱

常用函式

  • size():回傳大小
  • front():回傳前面的值
  • back():回傳後面的值
  • push_front():從前面新增一個值
  • push_back():從後面新增一個值
  • pop_front():從前面刪掉一個值
  • pop_back():從後面刪掉一個值

\(O(1)\)

\(O(1)\)

\(O(1)\)

\(O(1)\)

\(O(1)\)

\(O(1)\)

\(O(1)\)

練習題

stack:

zj b923

zj c123

zj a565

zj e924

queue:

zj e447

進階題:

tcirc judge d028

tcirc judge d029

iterator

反覆運算器

用法跟指標差不多

宣告方式

STL的模板名稱::iterator 變數名稱;

vector<int>::iterator p;

可使用++移動到下一位,--移動到上一位

也可以使用prev()和next()

因為變數名稱太長,可以在有初始值時使用auto

vector<int> v;
auto p = v.begin();

set

集合

引入函式庫:set

宣告方式:set<int>  名稱

常用函式

  • size():回傳大小
  • insert():插入一個值
  • find():找一個值的位置
  • lower_bound():找第一個大於等於的元素的iterator
  • upper_bound():找第一個大於的元素的iterator

\(O(1)\)

\(O(logn)\)

\(O(logn)\)

\(O(logn)\)

\(O(logn)\)

練習題:

zerojudge f607

map

字典

引入函式庫:map

宣告方式:map<鍵的型別, 值的型別>  名稱

可以把它當陣列用

map<string, int> m;
m["ten"] = 10;
m["five"] = 5;
cout << m["ten"] + m["five"] << '\n';

常用函式

  • size():回傳大小
  • insert():插入一個值
  • find():找一個值的位置
  • lower_bound():找第一個大於等於的元素的iterator
  • upper_bound():找第一個大於的元素的iterator

\(O(1)\)

\(O(logn)\)

\(O(logn)\)

\(O(logn)\)

\(O(logn)\)

priority_queue

優先度最高的在最上面

引入函式庫:queue

宣告方式:priority_queue<int>  名稱(最大)

priority_queue<int, vector<int>, greater<int> > 名稱(最小)

常用函式

  • size():回傳大小
  • top():回傳最上面(最大)的值
  • pop():把最上面(最大)的值刪掉
  • push():加入一個值

\(O(1)\)

\(O(1)\)

\(O(logn)\)

\(O(logn)\)

練習題:

zerojudge b606

自定義比較

operator

回傳型別 operator要重載的運算子(運算子需要的參數){

 

}

struct A{
	int x, y;
};
bool operator<(A a, A b) {
	if (a.x != b.x)
		return a.x < b.x;
	return a.y < b.y;
}

Copy of 2021C++放課簡報

By Richard Lai

Copy of 2021C++放課簡報

  • 45