演算法基礎班 [3]

基礎 STL/複雜度

INFOR38 學術 葉倚誠

課程大綱

# 1 標準模板庫 (STL)
# 2複雜度
# 2 容器 Container/適配器 Adapter
# 3Pair
# 4 迭代器 iterator
# 5 演算法 Algorithms
# 7

概述

標準模板庫 (STL)

  • C++ 標準函式庫的一部分
  • 內建各式資料結構/演算法
  • 支援多數 OJ 及競賽
  • 容器/迭代器/演算法
  • cppreference.com

複雜度

什麼是複雜度?

時間複雜度和空間複雜度是衡量一個演算法效率的重要標準

OI Wiki

透過複雜度可以判定一個演算法的執行效率

基本操作數

  • 在不同計算機上的運行速度不同
  • 考慮基本操作數量可以評斷演算法的效率

E.g. 加減乘除、變數存取等皆為基本操作

時間複雜度

  • 考慮數據規模的大小,以計算時間複雜度
  • 數據規模與演算法用時有正相關

算法用時隨數據規模而增長的趨勢,即為時間複雜度

漸進符號

  • 忽略了函數中增長較慢的部分以及各項的係數
  • 在時間複雜度計算中,忽略常數
  • 大 O 代表漸進上界,時間複雜度通常使用大 O

判斷常數

#include <bits/stdc++.h>
using namespace std;

int main() {
  int n = 100000;
  for (int i = 0; i < n; ++i) {
    cout << "hello world\n";
  }
  
  return 0;
}

如果 n 不被看作輸入規模,那麼這段程式的時間複雜度就是 𝑂(1)

時間複雜度計算

#include <bits/stdc++.h>
using namespace std;

int main() {
  int n, m;
  cin >> n >> m;
  for (int i = 0; i < n; ++i) {
    for (int j = 0; j < n; ++j) {
      for (int k = 0; k < m; ++k) {
        cout << "hello world\n";
      }
    }
  }
}

容器 Container / 適配器 Adapter

容器/適配器

容器:用來儲存資料的結構

順序容器:array、vector、list、deque

關聯容器:set、map

無序容器:unorderd_set、unorderd_map

適配器:類似容器,但沒有迭代器

queue、priority_queue、stack

順序容器

Sequence Container

Vector

Deque

List

Array

Array 陣列

#include <array>
array<int, 5> a = {1, 2, 3, 4, 5};
operator[] / at() 存取指定索引值元素
front() / back() 存取第一個/最後一個元素
fill() 填充指定元素在容器中
empty() 回傳容器是否為空
size() 回傳容器長度

Vector 動態陣列

#include <vector>
vector<int> v = {1, 2, 3, 4, 5}
operator[] / at() 存取指定索引值元素
front() / back() 存取第一個/最後一個元素
push_back() / pop_back() 在容器末端插入/刪除元素
clear() 清空容器
empty() 回傳容器是否為空
size() 回傳容器長度
resize() 改變容器長度

Deque 雙向隊列

#include <deque>
deque<int> d = {1, 2, 3, 4, 5}
operator[] / at() 存取指定索引值元素
front() / back() 存取第一個/最後一個元素
push_back() / pop_back() 在容器末端插入/刪除元素
push_front() / pop_front() 在容器頭端插入/刪除元素
clear() 清空容器
empty() 回傳容器是否為空
size() 回傳容器長度
resize() 改變容器長度

List 雙向鏈表

#include <list>
list<int> l = {1, 2, 3, 4, 5}
empty() 回傳容器是否為空
size() 回傳容器長度
resize() 改變容器長度
  • 絕大多數功能與 deque 一樣,支援雙向插入
  • 資料不存在連續記憶體中,無法使用下標

關聯容器

Associative Container

Map

Set

multiset

multimap

Set

  • #include <set>
  • set<T> s;

  • insert(it, x) / erase(x / it)

  • find(x) / count(x)

  • size()

  • empty() / clear()

#include <bits/stdc++.h>
#define endl '\n'

using namespace std;

int main() {
    set<int> s;

    for (int i = 0; i < 10; ++i) s.insert(i);
    for (int i = 0; i < 10; ++i) s.insert(i);
    for (auto it = s.begin(); it != s.end(); it++) {
        cout << *it << " ";
    } // 0 1 ... 9
    cout << endl;
    cout << s.size() << endl; // 10
    cout << s.count(5) << endl; // 1

    return 0;
}

元素自動排序 / 不允許重複元素

#include <bits/std.c++.h>
using namespace std;

int main() {
    set<int> s;
    int color;
    for (int i = 0; i < 4; i++) {
        cin >> color;
        s.insert(color);
    }
    cout << 4 - s.size() << endl;

    return 0;
}

Map

  • #include <map>
  • map<T, T> m;

  • insert({key, val}) / erase(key / it)

  • find(key) / count(key)

  • size()

  • empty() / clear()

#include <bits/stdc++.h>
#define endl '\n'

using namespace std;

int main() {
    map<string, int> m;
    
    m["a"] = 1;
    m["b"] = 2;
    m["c"] = 3;
    m.insert({"d", 4});

    cout << m.count("a") << endl; // 1
    cout << (m.find("a") != m.end()) << endl; // 1

    return 0;
}

按鍵值自動排序 / 不允許重複鍵值

#include <bits/std.c++.h>
using namespace std;

int main() {
    int n; cin >> n;
    map<int, int> counts;
    int max_freq = 0;
    for (int i = 0; i < n; i++) {
        int a;
        cin >> a;
        counts[a]++;
        max_freq = max(max_freq, counts[a]);
    }
    cout << max_freq << endl;
    
    return 0;
}

適配器

Adapter

Stack

priority_queue

Queue

Queue

#include <bits/stdc++.h>
#define endl '\n'

using namespace std;

int main() {
    queue<int> q;

    for (int i = 1; i <= 5; ++i) q.push(i);
    while (!q.empty()) { // 1 2 3 4 5
        cout << q.front() << " ";
        q.pop();
    }
    cout << endl;

    return 0;
}
  • #include <queue>
  • queue<T> q;

  • push(x) / pop()

  • front() / back()

  • size() / empty()

  1. Deque 為底層容器

  2. 先進先出

Priority Queue

  • #include <priority_queue>
  • priority_queue<T, Container, Compare> pq

  • push(x) / pop()

  • top()

  • size() / empty()

  1. 自訂底層容器

  2. 自訂比較函數

  3. 按照優先級排序

Priority Queue

#include <bits/stdc++h>
using namespace std;

int main() {
    vector<int> data = {10, 2, 45, 18, 7};
    priority_queue<int, vector<int>, less<int>> pq;

    for (int num : data) {
        pq.push(num);
    }

    while (!pq.empty()) {
        cout << pq.top() << " ";
        pq.pop();
    }

    return 0;
}

Stack

#include <bits/stdc++.h>
#define endl '\n'

using namespace std;

int main() {
    stack<int> s;

    for (int i = 0; i < 10; i++) s.push(i);
    while (!s.empty()) { // 9 8 7 6 5 4 3 2 1 0
        cout << s.top() << " ";
        s.pop();
    }
    cout << endl;

    return 0;
}
  • #include <stack>
  • stack<T> s;

  • push(x) / pop()

  • top()

  • size() / empty()

  1. Deque 為底層容器

  2. 後進先出

Pair

Pair

pait<int, double> = make_pair(1, 2.0)
#include <bits/stdc++.h>
using namespace std;

int main() {
    pair<int, double> p1;
    p1.first = 1;
    p1.second = 2.0;

    pair<int, double> p2 = make_pair(1, 2.0);

    auto p3 = make_pair(1, 2.0);

	return 0;
}

迭代器 iterator

單向迭代器 ForwardIterator 僅能向前遍歷容器
雙向迭代器 BidirectionalIterator 可以向前 / 後遍歷容器
隨機存取迭代器 RandomAccessIterator 可以任意訪問整個容器

迭代器是什麼?

  • 用來遍歷容器中的元素
  • 類似一個 STL 專用的指標
  • 代表指定的記憶體位址
  • 使用 *it 存取元素

迭代器分類

● 單向迭代器:

unorded_set, unorded_multiset, unorded_map, unorded_multiset

雙向迭代器:

list, set, multiset, map, multimap

隨機存取迭代器

array、vector、deque

無迭代器 (非容器)

queue、priority_queue、stack、pair

迭代器語法

iterator data.begin()回傳開頭 iterator 
iterator data.end()回傳結尾 iterator
iterator next(iterator it)回傳下一個 iterator
terator prev(iterator it)回傳上一個 iterator
*it存取 iterator
it -> func();(*it).func();
it + 3回傳後 3 個 iterator

演算法 Algorithm

演算法是什麼?

  • STL 內建的一些函數

  • 方便執行常用的操作

Sort

#include <bits/stdc++.h>
using namespace std;

int main(){
    int n;
    cin >> n;
	vector<int> v(n);
    
    for (int i=0;i<n;i++) {
    	cin >> v[i];
    }
    
    sort(v.begin(),v.end());
    
    for (int i=0;i<n;i++) {
    	cout << v[i] << ' ';
    }
}

Sort

#include <bits/stdc++.h>
using namespace std;

bool cmp(int a ,int b) {
	return a < b; // 原本從小到大,變成從大到小
}


int main() {
    int n;
    cin >> n;
	vector<int> v(n);
    
    for (int i=0;i<n;i++) {
    	cin >> v[i];
    }
    
    sort(v.begin(), v.end(), cmp);
    
    for (int i=0;i<n;i++) {
    	cout << v[i] << ' ';
    }
}

Reverse

#include <bits/stdc++.h>
#define endl '\n'

using namespace std;

int main() {
    vector<int> v = {5, 1, 3, 4, 2};

    for (int i : v) cout << i << " ";
    cout << endl;
    // 5 1 3 4 2
    
    reverse(v.begin(), v.end());
    for (int i : v) cout << i << " ";
    cout << endl;
    // 2 4 3 1 5

    return 0;
}

Count

#include <bits/stdc++.h>
#define endl '\n'

using namespace std;

int main() {
    vector<int> v = {1, 1, 2, 3, 3, 4, 6, 7, 7, 7};

    cout << count(v.begin(), v.end(), 7) << endl;  // 3
    cout << count(v.begin(), v.end(), 1) << endl;  // 2
    cout << count(v.begin(), v.end(), 8) << endl;  // 0

    return 0;
}

Min / Max

#include <bits/stdc++.h>
#define endl '\n'

using namespace std;

int main() {
    cout << min(10, 5) << endl;        // 5
    cout << max(10, 5) << endl;        // 10
    cout << min({10, 5, 20}) << endl;  // 5
    cout << max({10, 5, 20}) << endl;  // 20

    return 0;
}

Find

#include <bits/stdc++.h>
#define endl '\n'

using namespace std;

int main() {
    vector<int> v = {1, 2, 3, 4, 5};

    if (find(v.begin(), v.end(), 3) != v.end()) cout << "Yes" << endl;
    else cout << "No" << endl; // Yes
    
    if (find(v.begin(), v.end(), 6) != v.end()) cout << "Yes" << endl;
    else cout << "No" << endl; // No

    return 0;
}

Remove

#include <bits/stdc++.h>
#define endl '\n'

using namespace std;

int main() {
    vector<int> v = {1, 2, 3, 4, 5};

    v.erase(remove(v.begin(), v.end(), 3), v.end());
    for (const auto &i : v) cout << i << " ";
    cout << endl;
    // 1 2 4 5
    

    return 0;
}

Transform

#include <bits/stdc++.h>
#define endl '\n'

using namespace std;

int f(const int &x) { return x * 2; }

int main() {
    vector<int> v = {1, 2, 3, 4, 5};
    string s = "HeLlo woLRD";

    transform(v.begin(), v.end(), v.begin(), f);
    for (const auto &i : v) cout << i << " "; 
    cout << endl;
    // 2 4 6 8 10
    
    transform(s.begin(), s.end(), s.begin(), ::tolower);
    cout << s << endl;
    // hello world

    return 0;
}
#include <iostream>
#include <vector>
#include <stack>

using namespace std;

void solve() {
    int n;
    if (!(cin >> n)) return;
    vector<int> target(n);
    for (int i = 0; i < n; i++) {
        cin >> target[i];
    }

    stack<int> s;
    int next_push = 1;
    bool possible = true;

    for (int i = 0; i < n; i++) {
        while ((s.empty() || s.top() != target[i]) && next_push <= n) {
            s.push(next_push);
            next_push++;
        }

        if (!s.empty() && s.top() == target[i]) {
            s.pop();
        } else {
            possible = false;
            break;
        }
    }

    if (possible) cout << "Yes" << endl;
    else cout << "No" << endl;
}

int main() {
    int t;
    if (cin >> t) {
        while (t--) {
            solve();
        }
    }
    return 0;
}

The End

Made with Slides.com