阿格里森姆 - 4

資料結構的第二堂

Lecturer:我真的好想看 𝄇 喔

                   人稱滿等塑膠怪

             aka Repkironca、阿蘇

怎麼可能

又是 STL

陳亮延你可以不要什麼都砸 map 嗎

就 unordered_set 而已

完蛋了 pD 寫不出來...

undset <bool> exist

西曆2022年

不想

讀書

StackOverflow

學長我不想再手刻資結了

linked_list

「AtCoder 上分率0.0000000001%」

段考倒數

白癡才

 接執秘

Do you hear the people sing?

真的啦,今天絕對教得完

I 演算法資料庫 <algorithm>

當然你也可以全部都用手刻

What is <algorithm>

  • ㄛ,但他實際上不能幫你解算法題 ~(>_<。)\
  • 包含一大堆功能,比起手刻簡潔很多
  • C++ 中的 library 之一,記得要先 include
#include <algorithm> //引入 algorithm
// or
#include <bits/stdc++.h> //萬能標頭檔,裡面包含 algorithm 了
  • 功能很雜,所以我簡報很難做,so sad

max()、min()

warning : 如果直接 max/min (a, b, c) 會 Complie Error

int maximum = max({123, 456, 789});
int maximum = max(123, max(456, 789));

在 max 裡面再塞 max

ㄘㄇㄏ:

在 max 中塞大括號

min (a, b):       回傳 a、b 間 較小 的那隻

max (a, b):       回傳 a、b 間 較大 的那隻

int a = max(5, 10); // 10
int b = min(a, 3); // 3

e.g.

swap()

swap (a, b):

交換 a 與 b,a、b 可以是 任何 STL 或變數

不過 a、b 必須 同型態

e.g.

int a = 1, b = 5;
swap(a, b);
cout << a << ' ' << b << '\n';
//OUTPUT : 5 1
vector <bool> vec_1, vec_2;
// 各種對 vector 的操作...
swap(vec_1, vec_2);
  • swap 並不會改變本身記憶體位置

int 的交換

vector 的交換

swap()

int a = 2, b = 3;
int *a_ptr = &a, *b_ptr = &b;
cout << a_ptr << ' ' << b_ptr << '\n';
cout << *a_ptr << ' ' << *b_ptr << '\n';
cout << &a_ptr << ' ' << &b_ptr << '\n';

pointer 的交換

名稱 a b a_ptr b_ptr
位置 0x01 0x02 0x03 0x04
2 3 0x01 0x02
swap(a_ptr, b_ptr);
cout << a_ptr << ' ' << b_ptr << '\n'; //0x02 0x01
cout << *a_ptr << ' ' << *b_ptr << '\n'; //3 2
cout << &a_ptr << ' ' << &b_ptr << '\n'; //0x03 0x04

兩邊的值交換!

reverse()

reverse (first, last):

first、last 是 iterator

反轉 [from, to) 間的所有元素

reverse(vec.begin(), vec.begin()+5); //反轉前半部分
// 4 3 2 1 0 5 6 7 8 9
reverse(vec.begin(), vec.begin()+1); //什麼都不會做
// 0 1 2 3 4 5 6 7 8 9

[0, 5)

[0, 1)

O(N)

vector <int> vec;
for (int i = 0; i < 10; i++) vec.push_back(i);
// 0 1 2 3 4 5 6 7 8 9
reverse(vec.begin(), vec.end()); //反轉整個 vector
// 9 8 7 6 5 4 3 2 1

[0, 10)

e.g.

總共會跑 \(\frac{N}{2}\) 次,O(\(\frac{N}{2})\in O({N})\)

next_permutation()、prev_permutation()

  • 字典序 排列:從 最左邊 開始比較,數字越小則字典序越小
I 123
II 132
III 213
IV 231
V 312
VI 321

e.g.

123 < 213

312 < 321

213 < 312

next_permutation(first, last):

first, last 是 iterator

直接把 iterator 指向物件間的數字

變成 下個字典序 之排列

若不存在,會變回 最小字典序排列

且本函式 回傳 false

prev_permutation(first, last):

同 next_permutation

但改成 上個字典序 之排列

若不存在,則變成  最大字典序排列

e.g.

vector <int> vec;
for (int i = 1; i <= 4; i++) vec.push_back(i);
// {1, 2, 3, 4}
next_permutation(vec.begin(), vec.end());
// {1, 2, 4, 3}
prev_permutation(vec.begin(), vec.end());
/* 因為 {1, 2, 3, 4} 沒有上一個字典序了
所以變成最大字典序 {4, 3, 2, 1} */

想一想

要怎麼由小到大

依序印出 {1, 2, 3, 4, 5}

的字典序排列?

這是去年你們學長遇到的困擾

這是去年你們學長的學長的簡報

這是今年你們學長的簡報

明年的學術,你知道要怎麼做了吧

有一說一,陳亮延到底做了什麼?

用 while

翻譯年糕:拿 for 跑過整個陣列,從左掃到右

比大小

翻譯年糕:每次挑兩個數字 a, b出來,看誰比較大

後 swap

翻譯年糕:如果 a > b,那 a 應該在右邊,所以 swap(a, b)

陳亮延忘記說的那些事

  • 實際上要 兩層迴圈
9 2 5 4 7 1 6 8 3 0

9 要跑到最右邊,需要 9 次 swap()

但第一次迴圈就會做完

若 0 要跑到最左邊,也需要 9 次 swap()

且需要跑到 9 輪迴圈

  • 他根本沒掃整個陣列,到 n-1 項就可以停了

在第 N-1 項會比較 N-1 與 N 的大小

若跑到 N,會超出陣列範圍 ㄘ RE

扣掉他噁心的 coding-style,大概是這個意思

vector <int> bubble_sort(vector <int> vec){
  for (int i = 0; i < vec.size()-1; i++){
    for (int j = 0; j < vec.size()-1; j++){
      if (vec[j] > vec[j+1]) swap(vec[j], vec[j+1]);
    }
  }

  return vec;
}
我覺得數字太大會 TLE

這個實際上叫 泡沫排序法,Bubble Sort

APCS 觀念很愛考,而且 10 次中有 9 次會被它寫爛

O(\(N^2\))

O(\(N^2\)) 好慢ㄛ,有沒有什麼快一點的方法

有。而且還不用自己刻,超級讚

std::sort!!!

sort(from, to, function)

  • from、to 都是 iterator,範圍是 [from, to)
sort(vec.begin(), vec.end()); //預設升序
// {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
sort(vec.begin(), vec.end(), greater <int>())
// {10, 9, 8, 7, 6, 5, 4, 3, 2, 1}
vector <int> vec;
for (int i = 2; i <= 10; i += 2) vec.push_back(i);
for (int i = 1; i <= 10; i += 2) vec.push_back(i);

e.g.

  • function 為比較的方式,分為下面三種,預設為升序

-less <int>() 升序排列

-greater <int>() 降序排列

-自己刻出的函式名 自訂排序方式

sort(from, to, function)

  • 複雜度?

O(N log N)

而且本身經過好幾重優化

就算你刻出 O(N log N) 的 sort function

要比 std::sort 快也有難度

  • 那如果我是 array 教的,可以用這方法嗎

但在此大力宣傳 vector,用習慣之後很舒服的

-把 from, to 改成 指向開頭、結尾 + 1的 pointer

e.g.

int arr[10] = {2, 4, 6, 8, 10, 1, 3, 5, 7, 9};
sort(arr, arr+10, greater <int>());

自訂排序方式

若我想排序的東西不是 int

      而是 pair、struct、queue 之類的呢

  • 超級重要:若 a、b 完全等價,要確保會 return false
  • 自己刻一個 function,吃進兩個參數,回傳一個 bool
#define pii pair <int, int>
#define F first
#define S second

bool cmp(pii a, pii b){

}
  • 若最後回傳 true,代表 a 要排在前面     若回傳 false,代表 b 要排在前面

{3, 4}、{6, 8}

-> return false

{5, 7}、{2, 15}

-> return true

{13, 8}、{13, 4}

-> return true

{9, 5}、{9, 5}

-> return false

為什麼不能這樣寫?

bool cmp (pii a, pii b){
  if (a.F < b.F) return false;
  if (a.S < b.S) return false;
  return true;
}

e.g.

bool cmp (pii a, pii b){
  if (a.F > b.F) return true;
  if (a.S > b.S) return true;
  return false;
}

你會跟講師一樣在資芽上機考噴掉 100 分

(a.F == b.F &&  a.S == b.S)

return true

還有什麼比 O(N log N) 更快的方法ㄇ

-有啊,best case O(1)

-為什麼說 best case 呢,因為它 worst case

O(\infty)

-這只是我講爽的,超級不建議使用

  • 每次都把整個數列套一次隨機,然後檢查我排好了沒

Bogo Sort 猴子排序

  • 聽過 無限猴子定理
  • 複雜度從 O(1) 到 O(\(\infty\)),無敵的吧

我看過很多 sort 的問題,然後都忘光ㄌ

現在你可以直接砸 <algorithm> 了

白癡才用遞迴刻 DFS

去年的資芽語法二階上機考

切身之痛,我剛剛的 噴掉 100 分 就是這一題

很裸很裸的裸題

大概 1 分鐘就 AC 那種(註:它會卡 long long)

裸ㄉ,很舒服

這樣就有 100 分了耶,不香嗎

II 優先隊列 priority_queue

除非你想一直 sort(),但很小丑

這是高二的英雜單字

Ivan Lo:可是我高一就會了耶

Wt is priority_queue

  • 對,就是一個 queue,所以要 #include <queue>
  • 裡面的元素 永遠 按照 升序 / 降序 排列
  • 複雜度?
push():O(log N)
top():O(1)
pop():O(log N)
size():O(1)
empty():O(1)

宣告 priority_queue

*可以善用 define

priority_queue <型別, 容器, 排序方式> 命名

-int、long long、pii、pipii 等都是常見的型別

-容器部分,一般會拿 vector,我也沒看過其他ㄉ

-排序方式,less <型別> 使 top() 為最大,

                        greater <型別> 使 top() 為最小

priority_queue <int, vector <int>, less<int> > pq; //升序
priority_queue <int> pri_que; //預設就是用 vector、less
priority_queue <int, vector <int>, greater<int> > pq; //降序

e.g.

*如果出現 >>,建議在中間加空格 避免誤判

.push(x)

e.g.

  • 將 x 推進 priority_queue 中,且擺在正確的位置
priority_queue <int> pq;
pq.push(5); //{5}
pq.push(3); //{3, 5}
pq.push(13); //{3, 5, 13}

O(log N)

.top()

e.g.

  • 回傳 priority_queue 最上面的那個值
priority_queue <int> pq;
pq.push(5);
pq.push(3);
cout << pq.top() << '\n'; // 5
pq.push(13);
cout << pq.top() << '\n'; // 13

O(1)

.pop()

e.g.

  • 把 priority_queue 的 top() 踢出去
priority_queue <int> pq;
pq.push(5); //{5}
pq.push(3); //{3, 5}
cout << pq.top();
pq.pop(); // {3}
cout << pq.top();

O(log N)

.size()

e.g.

  • 回傳一個 unsigned int                                                  代表 priority_queue 裡還有多少元素
priority_queue <int> pq;
pq.push(5);
pq.push(3);
pq.push(13);
cout << pq.size() << '\n'; // 3

O(1)

while(pq.size()){

}
只要 pq 中還有元素就繼續跑

.empty()

  • 回傳一個 bool       代表 priority_queue 是否為空

O(1)

  • 只要有用到 pop() 建議都搭配 empty() 使用      避免對空的 pq 取 top(),會 Runtime Error

e.g.

priority_queue <int> pq;

while (!pq.empty()) pq.pop();
強制清空 priority_queue

單考 priority_queue 的題目好少ㄛ

雖然他叫 heap 練習

但不建議真的刻一棵 heap 出來

寫不出來滿正常的,因為這是 Greedy

但去年 yeedrag 放在 pq,我被搞得很慘

所以我要你們跟我一樣:)

III 集合 set

我永遠會在該砸 set 時忘記它的存在

What is set?

  • 字面上的意思,同數學上的集合
  • 具有 唯一性,每個元素只有一個
  • 裡面的元素是 有序
  • 不支援 []、top() 等功能,只能拿 iterator 訪問
  • 本身是一顆 紅黑樹,所以很多複雜度帶 log
insert():O(log N)
erase():O(log N)
count():O(log N)
size():O(1)
empty():O(1)
  • 顯然我們需要 #include <set>
clear():O(N)

宣告 set

set <型態> 命名

#define pii pair <int, int>

set <int> exist;
set <pii> history;

e.g.

.insert(x)

  • 把 x 加進 set 中
  • 如果 x 早就存在,那 什麼也不會發生
set <int> exist;
exist.insert(2); // {2}
exist.insert(5); // {2, 5}
exist.insert(1); // {1, 2, 5}
exist.insert(5); // 什麼也不會發生,{1, 2, 5}

e.g.

O(log N)

.count(x)

O(log N)

  • 回傳一個 int,代表 x 在 set 中的數量
set <int> exist;
exist.insert(2); // {2}

if (exist.count(2)){
  //...
}else{
  //...
}

e.g.

在一般 set 中非 0 即 1,所以可以 直接當 bool 用!

.erase(x)

O(log N)

  • 把 x 從 set 中踢出,並 回傳 true
set <int> exist;

int tmp; cin >> tmp;
if (exist.erase(tmp)){
  cout << tmp << " deleted\n";
}else{
  cout << "It has never existed\n";
}
  • 如果 x 本來就不存在,那什麼都不會發生,並 回傳 false

e.g.

.size()

  • 回傳一個 unsigned int                                                  代表 set 裡還有多少元素
set <int> exist;
exist.insert(1);
cout << exist.size() << '\n'; // 1
exist.insert(2);
cout << exist.size() << '\n'; // 2
exist.insert(1);
cout << exist.size() << '\n'; // 2

O(1)

e.g.

.empty()

  • 回傳一個 bool       代表 set 是否為空
set <int> exist;

if (exist.empty()) cout << "The set is empty\n";
else cout << "There are something in the set\n";

O(1)

e.g.

.clear()

  • 強制把整個 set 清空
set <int> exist;
exist.insert(1);
exist.clear();
if (exist.empty()) cout << "The set is empty\n";
else cout << "There are something in the set\n";
// The set is empty

e.g.

O(N)

unordered_set

  • 語法基本上與 set 相同,宣告換成 unordered_set 就好

e.g.

#define unordered_set und_set
und_set <int> exist;
  • 本身是 hash table,保有 set 的 唯一性,但 不再自動排序
set unordered_set
.insert() O(log N) O(1)
.count() O(log N) O(1)
.erase() O(log N) O(N)
常數 偏大 比偏大還要更大

multiset

  • 那我為何不能用 map?

我哪有差,你開心就好

  • 語法基本上與 set 相同,宣告換成 multiset 就好

e.g.

multiset <int> exist;
  • 保留相同元素,在 count() 時回傳 set 中該元素的數量

e.g.

multiset <int> exist;
exist.insert(5);
exist.insert(5);
exist.insert(5);
cout << exist.count(5) << '\n'; // 3

我只記得最近打比賽遇到一堆 set

如果真的解不出來,Youtube 上會有講解

然後你就會發現這題爆簡單

你要用圖論的做法我也沒意見啦

就會跟姜睿喆一樣

IV 關聯性容器 map

拜託不要對它成癮,否則有點毒瘤

  • 記得先 #include <map>

你知道嗎

  • 建電幹部有一坨暈船仔,超級多,多到很恐怖
  • 其中,副社長陳亮延,aka 先帝的暈船對象超級酷
  • 每天廚、一直廚,滿腦子都只有它 ==

What is map?

  • 聽過 python 的 dictionary 嗎,幾乎一模一樣
  • 可以想像成 array 的語法,但 index 不再侷限於數字
  • 一個 key 對應一個 value,key 具有 唯一性
  • 本身是一棵 紅黑樹,故儲存其中的資料會 自動排序
insert():O(log N)
get value:O(log N)
clear():O(N)

宣告 map

map <key 型態, value 型態> 命名

map <int, int> dic; // int 對到 int
map <char, int> m; // char 對到 int
map <string, int> animal; // string 對到 int

e.g.

加入一個新的 key

I.

insert() 一個 pair 進去

first 是 key,而 value 是 value

e.g.

map <string, int> dic;
dic.insert({"test", 73});

II.

直接像 array 一樣賦值

e.g.

map <string, int> dic;
dic["test"] = 73;

warning:

該加的""、' '

還是要加

O(log N)

取值

  • 痾就,取值,你在 array 中怎麼做,在 map 中就怎麼做

e.g.

map <string, string> autumn_trip;
autumn_trip["Aaw"] = "執秘準備退休囉 :partying_face:";
autumn_trip["立葉"] = "你到底在興奮什麼啦\n";
autumn_trip["田鼠"] = "有人今天特別躁誒,她又沒有來\n";
autumn_trip["Yen"] = "他的娛樂就是轉圈圈,一直轉圈圈不願離開\n";
autumn_trip["姜姜"] = "整天沒有手機,超級可撥\n";
autumn_trip["先帝"] = "我認識的陳亮延不可能那麼帥,見鬼了\n";
autumn_trip["Brine"] = "怎麼有人在打北市賽啊\n";
autumn_trip["乘一"] = "我記得我暑訓就跟你說過不要妄想這招,這我可以臭一年\n";
autumn_trip["阿蘇"] = "怎麼道具都是這個人在搬啊,雖然搬得很甘願,汪汪\n";
autumn_trip["溫室蔡"] = "割,你的肉要焦掉了\n";
autumn_trip["大毛"] = "這是不是我這輩子首次聽到你唱歌\n";
autumn_trip["Greg"] = "這個學長沒有交錢,那個人也還沒付錢\n";
autumn_trip["鬆餅"] = "有人 RPG 玩到開始自彈自唱是正常的嗎\n";
cout << autumn_trip["姜姜"];
// 整天沒有手機,超級可撥

O(log N)

.clear()

  • 我連說明文字都懶得打了
map <int, int> dic;
dic[71] = 1e9 + 17;
cout << dic[71] << '\n'; //1000000017
dic.clear();
cout << dic[71] << '\n'; //沒看過的 key 預設是 0

e.g.

O(N)

vector v.s. map

  • 聽起來很蠢,但如果今天 MAXN = 1e15?

vector 根本不能開這麼大,沒 TLE 也 MLE

  • 總共只有 10 筆資料,但 MAXN = 1e9?

vector:要開 1e9 格,但找資料只需要常數時間
map:只需開 10 * log 10 格,worst case 要 log 10 = 3.32 * 常數

vector v.s. map

結論:

map 因為本身是紅黑樹,需要 O(log N),與 O(1) vector 相比較劣勢

但在 資料離散嚴重(極值超級大) 的情況下,map 會成為解方

unordered_map

  • 語法基本上與 map 相同,宣告換成 unordered_map 就好

e.g.

#define unordered_set und_map
und_map <int, int> dic;
  • 本身是 hash table,仍然具有 唯一性,但 不再自動排序
map unordered_map
加入新 key O(log N) O(1)
取值 O(log N) O(1)
常數 偏大 比偏大還要更大

陳亮延歷史上最興奮的一刻

MAXN 開到 1e18

你確定要用不優化的遞迴嗎

一三的共同回憶

如果 OJ 有救回來開張,記得去 AC 一下啦 QAQ

偷自 yeedrag 的簡報

V 鍊結串列 linked_list

你們遇到的第一個手刻 STL

What is linked_list?

  • 用 struct 跟 pointer 實作出的一種資料結構
  • 利用指標一個接著一個,所以每次操作都要從 頭或尾 開始
  • 只能用手刻,C++ 內建的難用到有跟沒有一樣
  • 節點間的自由度很高,可以從任一點插入或斷開

root

[1]

[2]

[3]

end

插播:struct 與 pointer 的結合

struct animal{
  string name;
};

struct zoo{
  animal *area_1 = nullptr;
  animal *area_2 = nullptr;
  animal *area_3 = nullptr;
};
  • 事實上,指標可以指向任何東西,包含 其他struct

e.g.

-對,所以你要拿指標來接

再插播:new 與 delete

new (變數類型):

向某特殊的記憶體區塊申請一個空間來存此目標,

回傳一個位址

delete (指標名稱):

把這塊記憶體還回去,delete 後再 訪問它會爛掉

struct animal{
  string name;
};
struct zoo{
  animal *area_1 = nullptr;
  animal *area_2 = nullptr;
  animal *area_3 = nullptr;
};

int main (){
  zoo Z;
  animal *cat = new animal; // 從 Heap 拿記憶體給 cat
  Z.area_1 = cat;
  delete cat; //把記憶體還給 Heap
}

又插播:->

  • 喔,剛剛那坨如果聽不懂就算了,不是今天重點
  • 之後專案建置會用更清楚完整的方式重講

一般 struct 中汲取成員:.成員變數名

struct animal{
  string name;
};
struct zoo{
  animal *area_1 = nullptr;
  animal *area_2 = nullptr;
  animal *area_3 = nullptr;
};

int main (){
  zoo Z;
  animal *cat = new animal; // 從 Heap 拿記憶體給 cat
  Z.area_1->name = "cats";
  cout << Z.area_1->name << '\n'; // cats
}

指標 struct 中汲取成員:->成員變數名

下面的做法都只是給你們個概念
基本上每題的 linked_list 都長得不一樣
你們自己視題目應變
  • val:這個節點自己的值
struct node{
  long long value;
  node *next = nullptr;
};

製作節點

常見元素:

  • next:一個指標,指向下個節點

製作 linked_list 本身

  • isempty:一個 bool,紀錄它是不是空的
  • length:紀錄 linked_list 的存在節點數量
  • end:紀錄 list 最後面那點,以增加 push_back 的速度
struct linked_list{
  unsigned int length = 0;
  node *root = nullptr;
  node *end = nullptr;
  bool isempty = true;
 };

常見元素:

  • root:整個 list 最前面那一點

print_list()

預計功能:從 root 開始到 end,依序印出每個值

把整個 list 印出來

root = 9

2

5

7

3

end = 4

  • 只要 next != nullptr , 就輸出自己,並不斷往後

9

  • 創造一個 node *tmp,從 root 開始往後推
node *tmp = root;

把整個 list 印出來

root = 9

2

5

7

3

end = 4

9

2

  • 只要 next != nullptr , 就輸出自己,並不斷往後
  • 創造一個 node *tmp,從 root 開始往後推
node *tmp = root;

把整個 list 印出來

root = 9

2

5

7

3

end = 4

9

2

5

  • 只要 next != nullptr , 就輸出自己,並不斷往後
  • 創造一個 node *tmp,從 root 開始往後推
node *tmp = root;

把整個 list 印出來

root = 9

2

5

7

3

end = 4

9

2

5

7

node *tmp = root
  • 只要 next != nullptr , 就輸出自己,並不斷往後
  • 創造一個 node *tmp,從 root 開始往後推
node *tmp = root;

把整個 list 印出來

root = 9

2

5

7

3

end = 4

9

2

5

7

3

  • 只要 next != nullptr , 就輸出自己,並不斷往後
  • 創造一個 node *tmp,從 root 開始往後推
node *tmp = root;

把整個 list 印出來

root = 9

2

5

7

3

end = 4

9

2

5

7

3

4

  • 只要 next != nullptr , 就輸出自己,並不斷往後
  • 創造一個 node *tmp,從 root 開始往後推
node *tmp = root;

push_back(int tar)

預計功能:把 tar 加到 linked_list 的尾端

push_back 的實作

  • new 出一個 node*,把 end->next 設為它
node *tmp = new node;
end->next = tmp;

root = 9

2

5

7

end = 4

17

push_back 的實作

  • new 出一個 node*,把 end->next 設為它
node *tmp = new node;
end->next = tmp;

root = 9

2

5

7

end = 4

17

push_back 的實作

  • new 出一個 node*,把 end->next 設為它
node *tmp = new node;
end->next = tmp;

root = 9

2

5

7

4

end = 17

  • 現在這個點變成 end 了
node *tmp = new node;
end->next = tmp;
end = tmp;

push_back 的實作

  • 特例:如果今天 linked_list 中什麼都沒有呢?

-root 跟 end 都是 nullptr,對它取 ->next 會爛掉

  void push_back(int tar){
    node *tmp = new node;
    tmp->value = tar;
    length++;
    if (isempty){
      root = tmp;
      end = tmp;
      isempty = false;
      return;
    }
    end->next = tmp;
    end = tmp;
  }

*find_node(int tar)

預計功能:找到第一個值 = tar 的點

       並且return 它 (node*)

find_node 的實作

  • 吃進一個數字,回傳一個 node*
node *find_node (int tar){

}

root = 7

12

9

5

9

  • 跟 print_list() 有點像,從 root 開始一路尋找目標
node *tmp = root;
while (tmp->next != nullptr){
  if (tmp->value == tar) return tmp;
  tmp = tmp->next;
}
return tmp;

tar = 7

find_node 的實作

  • 吃進一個數字,回傳一個 node*
node *find_node (int tar){

}

root = 7

12

9

5

9

  • 跟 print_list() 有點像,從 root 開始一路尋找目標
node *tmp = root;
while (tmp->next != nullptr){
  if (tmp->value == tar) return tmp;
  tmp = tmp->next;
}
return tmp;

tar = 7

find_node 的實作

  • 吃進一個數字,回傳一個 node*
node *find_node (int tar){

}

root = 7

12

9

5

9

  • 跟 print_list() 有點像,從 root 開始一路尋找目標
node *tmp = root;
while (tmp->next != nullptr){
  if (tmp->value == tar) return tmp;
  tmp = tmp->next;
}
return tmp;

tar = 7

return!

insert (int loc, int tar)

預計功能:把 tar 插到數字 loc 後

  • 創造一個 node *last,接著用 find_node 找出插入點
node *last = find_node(loc);

insert 的實作

  • new 出一個 node *tmp,設定其值
node *tmp = new node;
tmp->value = tar;

last

tmp

last->next

  • 把 tmp 連結到 last->next 上
tmp->next = last->next;

insert 的實作

last

tmp

last->next

  • 把 tmp 連結到 last->next 上
tmp->next = last->next;

insert 的實作

last

tmp

last->next

  • 把 last 連結到 tmp 上
last->next = tmp;
  • 把 tmp 連結到 last->next 上
tmp->next = last->next;

insert 的實作

last

tmp

last->next

  • 把 last 連結到 tmp 上
last->next = tmp;

insert 的實作

  • 特例:如果插入點剛好是 end?

-end 要 更新 成新增的那點

  • 特例:如果 linked_list 是空的?

不存在 root,所以 find_node 那邊會 RE

void insert (int loc, int tar){
  node *last = find_node(loc);
  node *tmp = new node;
  tmp->value = tar;
  
  if (isempty){
    root = tmp;
    end = tmp;
    isempty = false;
  }
  if (last->value == end->value) end = last;
  
  tmp->next = last->next;
  last->next = tmp;
  length++;  
}

其他超級常見的功能:

  • push_front (int tar):把 tar 放到 link_list 最前端

-注意要 更新 root

  • erase (int tar):把 tar 踢出 linked_list

-更改一下指標,使其 無人指向 即可。

    心有餘力可以順便把它 delete

  • find_node (int loc):回傳 linked_list [loc]

-那就往後推 loc-1 次,不過要注意不能超出 end

  • 具備 *next*last 的 linked_list

-雙向連結,有時候這會讓你實作簡單很多

隨便亂刻的模板,我猜很難用 可能有 bug

要視情況做更改,建議刻出屬於自己的

struct node{
  long long value;
  node *next = nullptr;
};

struct linked_list{
  unsigned int length = 0;
  node *root = nullptr;
  node *end = nullptr;
  bool isempty = true;

  bool empty(){
    return isempty;
  }

  unsigned int size(){
    return length;
  }

  void push_front(long long num){
    node *tmp = new node;
    tmp->value = num;
    if (!isempty) tmp->next = root; //root 被往後推一格
    if (length == 0) end = tmp; //什麼都沒有,這點是 root 加 end
    else if (length == 1) end = root; //root 要變成 end 了
    root = tmp;
    length++;
    isempty = false;
  }

  void push_back(int tar){
    node *tmp = new node;
    tmp->value = tar;
    length++;
    if (isempty){
      root = tmp;
      end = tmp;
      isempty = false;
      return;
    }
    end->next = tmp;
    end = tmp;
  }

  void print_list(){
    if (!isempty){
      node *now = new node;
      now = root;
      cout << now->value << ' ';
      while (now->next != nullptr){
        now = now->next;
        cout << now->value << ' ';
      }
      delete now;
    }
  }

  node *find_node(int loc){
    if (loc == 0) return root;
    if (loc == size()-1) return end;
    node *tmp = root;
    for (int i = 0; i < loc && i < size(); i++) tmp = tmp->next;
    return tmp;
  }

  void insert(int loc, long long tar){
    node *now = new node;
    now->value = tar;
    length++;
    if (empty()){
      root = now;
      end = now;
      isempty = false;
    }else if (loc == 0){
      now->next = root;
      root = now;
    }else if (loc == size()-1){
      end->next = now;
      end = now;
    }else{
      now->next = find_node(loc)->next;
      find_node(loc)->next = now;
    }
  }

  void erase_by_location(int loc){
    if (loc == 0) root = root->next;
    else R_erase_by_location(find_node(loc-1), loc);
  }
  
  void R_erase_by_location(node *last, int loc){
    if (loc == size()-1){
      end = last;
      last->next = nullptr;
    }else{
      last->next = last->next->next;
    }
    length--;
    if (length == 0) isempty = true;
  }
};

通常 linked list 只要根據題目需求寫出必要的函式就好,不用每次刻好刻滿

通常吃 RE 的機率會比吃 WA 高很多

可以搭配前面的 set 與 map 服用

你開心的話,也能用好幾個 queue 寫

@Qt 這題絕對不是 vector

好煩,好毒,非常破題目

建北小社_algorithm 04 STL - linked_list, set, map, pq, sort, algorithm

By repkironca

建北小社_algorithm 04 STL - linked_list, set, map, pq, sort, algorithm

  • 680