深圳大学算法实验总结2020(实验1~6+大作业)
【本篇博客旨在幫助更多人少走彎路,歡迎轉載。若轉載,請轉載全面,圖片文字格式要到位,只復制文字的那種可讀性很差,就不要轉了。】
目錄
- 前言
- 容器介紹
- vector
- 簡介
- 特性
- 增刪復雜度
- 應用場合
- stack / queue / deque
- 簡介
- 特性
- 增刪復雜度
- 應用場合
- map/set
- 簡介
- 特性
- 查找/增刪復雜度
- 應用場合
- unordered_map/set
- 簡介
- 特性
- 查找/增刪復雜度
- 應用場合
- priority_queue
- 簡介
- 特性
- 增刪復雜度
- 應用場合
- 編程技巧
- 多用容器
- 容器引用
- 多自定義類型
- 萬能頭
- bfs訪問控制
- 注釋
- 多用庫函數
- 實驗介紹
- 實驗1 排序
- 實驗2 最近點對
- 實驗3 消消樂
- 實驗4 動態規劃代碼查重
- 實驗5 找橋
- 實驗6 圖最大流
- 大作業 基于RDF圖的語義地點skyline查詢
- RDF圖:
- **語義地點:**
- 語義地點支配:
- skyline查詢算法設計:
- 節點詞匯查詢:
- 語義地點查詢算法設計:
- 蠻力法
- 蠻力+法步數剪枝
- 蠻力法+篩根
- 離線查詢(蠻力法2)
- 反向bfs
- 總覽
前言
這學期快結束了,算法課程也進入尾聲,這學期一共有6個實驗+大作業,一共7個實驗,當時做的時候苦于沒有思路,雖然網上可以搜到往屆前輩的報告,因為沒有代碼可以參考,導致每次實驗都比較艱難。因為偽代碼往往不會將算法的全部細節描述出來,而且一些實際編程中的細節,偽代碼無法描述,所以有了這個總結。
這篇博客分為幾個部分:
首先是容器介紹,因為算法都是基于容器的,我們不需要關系容器怎么實現,我們專注于算法的實現,所以不應該在容器上花費時間。搞定算法實驗,先搞定容器。
然后是編程技巧,編程技巧中主要以c++為基礎,介紹一些算法實驗中有用的技巧,這些技巧能夠大大優化代碼復雜度或時間復雜度,使編程變得輕松。
然后是實驗介紹,這部分介紹每個實驗的要求,有那些算法能夠實現,然后會有傳送門,跳轉到每一個實驗的講解報告。
最后是總結以及一些感悟。這部分就是談談算法實驗是如何折磨我的 心得
容器介紹
既然是算法實驗,我們精力自然是放在算法上,不應該花費精力在準備算法的容器上,比如鄰接表,我見過有老哥自己寫一個鏈表來存數據的,其實完全沒有必要自己寫,C++ STL提供了相當方便且高效的容器。寫下來介紹一些C++ STL的容器,及其常用場合
vector
簡介
vector也就是向量,數組,線性表容器,底層實現就是一維數組。最最最常用的容器,沒有之一。
特性
增刪復雜度
一般使用push_back來增加一個尾部元素,復雜度為O(1)
一般使用pop_back來刪除一個尾部元素,復雜度為O(1)
其他位置的增刪,不推薦,因為會有元素的移動,復雜度為O(n)
應用場合
任何需要線性存儲元素的場合,最常用的容器沒有之一。
stack / queue / deque
簡介
棧/隊列/雙端隊列容器 底層為線性表
特性
棧:后進先出,push加入元素,pop彈出元素,top返回棧頂元素引用但是不彈出。
隊列:先進先出,front返回隊頭元素引用,push添加元素,pop彈出元素
雙端隊列:pop_back , pop_front 頭尾都可以增刪
增刪復雜度
O(1)
應用場合
stack的應用相對vector來說較少,因為stack是特化了的vector
queue主要應用于bfs廣度優先搜索
deque則更加少,在某些需要快速在首尾增刪的線性表,比如滑動窗口最大值問題
map/set
簡介
關聯式容器,底層是紅黑樹
特性
通過insert方法插入元素,erase方法刪除元素。find方法查找元素。
find方法返回元素迭代器,如果 .find() != .end() 則說明找到
set的迭代器存儲元素對象,假設有迭代器對象it,那么通過 *it 即可訪問元素
map的迭代去存儲元素<鍵,值>對,假設有迭代器對象it,那么通過 it->first 即可訪問鍵, it->second即可訪問值
map可以通過[]下標運算符直接找到鍵對應的值,用法:值 = map[鍵]
查找/增刪復雜度
O(log(2, n))
應用場合
set比較少,主要用于快速查找,map也是,主要也是用于快速查找,比如記憶化遞歸,鍵表示狀態,值表示答案。因為查找有更優秀的哈希容器,接下來會介紹
unordered_map/set
簡介
關聯式容器,底層是哈希表,通過對元素的哈希,來快速存取元素,前提是元素擁有高效的哈希函數
特性
插入,訪問,刪除元素方法同 map/set
注:如果使用自定義類型,需要在構造函數傳入自定義的哈希函數與比較函數
參考:【C++ 自定義哈希函數使得自制數據類型也可使用STL的哈希set,map】
查找/增刪復雜度
O(1)
應用場合
unordered_map用于記憶化遞歸比較多,尤其是那些【鍵】不連續的問題,比如消消樂,就不能開mem數組來表示,而是用哈希把他們映射到連續的內存
unordered_set用于存儲不重復元素,比如要篩重復元素,或者要快速查找一些元素是否在某個集合中(主要是這個 快速查找),舒適好用~~(就是insert插入元素效率略低 常數代價。。。)~~
priority_queue
簡介
優先隊列容器,底層是堆,隊頭總是保存最大(小)值
詳情見【C++ STL 優先隊列(priority_queue)容器的簡單使用】
特性
push插入元素,pop彈出隊頭元素,top返回隊頭元素引用
增刪復雜度
O(log(2, n))
應用場合
廣泛,只要用到堆的地方,都可以用。主要用于在動態更新的序列中快速獲取最值的場合。你可以用來堆排,topk,最高標號預流推進等等
編程技巧
多用容器
容器提供的方便是不言而喻的,使用容器能夠大大減少編程的代碼復雜度,而且出錯的機會也會隨之降低。
容器引用
在函數尤其是遞歸函數,如果希望動態改變容器參數的內容,那么需要使用引用,引用不僅能夠避免元素的多次拷貝(尤其是容器元素較多的時候),而且可以使函數思路更加清晰,因為高級語言默認所有形參都是引用
多自定義類型
有些問題往往需要我們自定義一些類型,比如圖論中的【邊】,或者是一些其他的類型。
比如實現圖的一條邊,可以用vector,下標0表示起始,1表示終點,2表示權值,但是這一切值得嗎 這樣會使得代碼的可讀性降低,這對后續代碼的維護是不利的。
少量的代碼就可以定義數據類型,卻可以使代碼更加易懂
typedef struct edge {int st, ed; // 起點, 終點edge(){} }edge;萬能頭
#include <bits/stdc++.h>多用萬能頭文件,雖然編譯很慢,但是要用到什么函數,不用再查找他是哪個頭文件的,可以減少編譯錯誤的幾率,比如想用memset,又要用strcpy和clock,就要include很多頭,而#include <bits/stdc++.h>可以解決一切。
缺點是容易造成命名沖突,比如clock就是函數名,不能用了
bfs訪問控制
如果要做很多次bfs,而每次bfs不必搜完所有節點,那么我們沒有必要重置整個visit數組,因為這樣做大量的時間會浪費在重置visit上面,而bfs的時間占少數。
假設bfs起點為src,我們可以令
這樣做n次bfs只用一次初始化vis數組為-1即可,大大減少訪問控制數組初始化的開銷。
注釋
注釋可以增加代碼可讀性,將程序模塊化。因為一個實驗往往需要好多天來寫代碼,隔夜代碼難讀,需要注釋的索引。
下面貼一個我比較常用的函數注釋的格式,表明函數的功能, 參數, 返回值 ,以及函數的解釋
/* function : 對nums的子數組進行快速排序 param nums : 要排序的數組引用 param l : 子數組范圍 左邊界 param r : 子數組范圍 右邊界 return : --- explain : 左右邊界都是閉區間 */ void quick_sort(vector<int>& nums, int l, int r) {if(l<0 || r>=nums.size() || l>=r) return;int key=nums[l], i=l, j=r;while(i<j){while(i<j && nums[j]>=key) j--;swap(nums[i], nums[j]);while(i<j && nums[i]<=key) i++;swap(nums[i], nums[j]);}nums[i] = key;quick_sort(nums, l, i-1);quick_sort(nums, i+1, r); }多用庫函數
庫函數就是為了減少代碼復雜度而生的,雖然難記憶,但是一回生二回熟。下面介紹常用庫函數
memset 批量設置內存
max_element / min_element 數組最大 / 最小值
inplace_merge 原地歸并,歸并排序用
sort 快速排序,很快 比手寫的要快
swap 交換兩個數據,不限類型(本質是函數模板)
next_premutation 下一個全排列
reverse 反轉數組
lower / upper _bound 二分搜索(前提是數組有序)
實驗介紹
所有實驗的資源 鏈接: https://pan.baidu.com/s/1lukZRM3Rsd1la35EyyJcvg 提取碼: iv72實驗1 排序
【實驗傳送門】
第一題是要實現幾種排序算法,然后測時間,這個沒啥,用vector然后排就完事了
第二題要找topk元素,用優先隊列模擬即可
實驗2 最近點對
【實驗傳送門】
需要定義結構體 point 表示點,初始化需要對點排序,用sort,自定義比較函數即可。然后就是分治法有兩種,一種是合并前排序,一種是邊分治邊排序。
注意生成測試數據需要去重,要用哈希set,要自定義比較/哈希函數
實驗3 消消樂
【實驗傳送門】
這個消去方塊比較難搞,我是用長度為 3/4/5 的橫豎條去覆蓋棋盤,看看有沒有相等的三聯,四聯,五聯,統計數目為 cnt3, cnt4, cnt5,然后將他們標記成負數,方便之后消除。
覆蓋結束后,消除所有負數的方塊,然后下落。注意下落后要再次判斷,因為可能可以再消。用遞歸
因為四聯,五聯包含三聯,所以要去重重復的計數。一個五聯包含兩個四聯和三個三聯,一個四聯包含2個三聯。
cnt4-=2*cnt5, cnt3-=(3*cnt5+2*cnt4); // 消去重復的計數記憶化遞歸,先將棋盤轉為字符串,然后因為c++自帶對字符串的哈希,用unordered_map即可存狀態和得分。
實驗4 動態規劃代碼查重
【實驗傳送門】
這個實驗主要用STL的string對象即可完成字符的處理,其他倒沒啥。。
實驗5 找橋
【實驗傳送門】
這里我做完才知道,用哈希set來標記邊太慢了。應該設一個mark數組就行了,然后就是找lca的時候,沿途標記環邊,要壓縮,即所有的點掛到lca下
不用在dfs的時候就找lca,也可以邊標記環邊邊找lca,過程如下:
實驗6 圖最大流
【實驗傳送門】
定義邊結構體,然后邊數組,偶數下標存正向邊,奇數下標存反向邊
typedef struct edge {int st, ed, val, pair; // 起點, 終點, 還能通過多少流量, 反向邊下標 edge(){}edge(int a, int b, int c, int d){st=a;ed=b;val=c;pair=d;} }edge;后來我發現,不用pair這個變量來找反向邊,因為【偶數下標存正向邊,奇數下標存反向邊】,對一條邊的下標異或1,就能找到反向邊
edge e1 = edges[i]; // 正向邊 edge e2 = edges[i^1] // 它的反向邊然后就是正常的求最大流就行了
大作業 基于RDF圖的語義地點skyline查詢
先咕著,等我交完報告就更
基于RDF圖的語義地點skyline查詢問題,問題分為
RDF圖:
有向圖,圖的每個頂點包含一些詞匯信息
語義地點:
假設現在有查詢詞匯集合w,w的語義地點是RDF圖的一個特殊子圖,具有樹的結構。W的語義地點是一顆以地點p為根的樹,樹的所有節點的詞匯的并集,包含查詢詞匯集合w。
語義地點支配:
如果對兩個查詢詞匯集合w的語義地點p1和p2
那么語義地點p1支配p2。P1支配p2說明p1是比p2更好的選擇
skyline查詢算法設計:
假設我們現在已經擁有所有符合條件的語義地點集合p1,我們希望找出p1集合中所有語義地點skyline。
新建集合p2,枚舉p1中的每一個語義地點c,對每個c都遍歷p2中的所有語義地點
其實就是暴力篩,當然也可以根據距離之和排序,距離之和小的不會被距離之和大的地點支配,這樣我們只用比較一次支配關系,這樣做可以減少比較的次數
節點詞匯查詢:
通過建立每一個節點的哈希表來作為節點詞匯集合,利用哈希的特性可以在O(1)的常數時間內查詢該節點是否具有某個詞匯,表的建立在讀取原始數據時就已經完成。
當然順序查找也是可以的,不過時間稍微慢一些,因為最多300個詞匯多點,效率差別不大
語義地點查詢算法設計:
蠻力法
我們根據語義地點的定義,對每個點找離其最近的查詢詞匯。通過bfs廣度優先搜索可以快速確定符合條件的點到源點的最短距離。蠻力法通過枚舉所有頂點作為bfs的源點,做n次bfs。Bfs過程中查看遍歷到的點是否包含查詢詞匯并嘗試更新查詢詞匯到源點的最短距離。
蠻力+法步數剪枝
我們認為用戶查詢的詞匯總是有很強的相關性,而距離根節點遠端的詞匯表示其相關性弱,是答案的可能性小,于是把它剪掉。
我們預設一個步數來限制bfs的搜索。bfs搜索的時候,搜索層次達到k以上就放棄搜索
蠻力法+篩根
我們假設語義地點根節點常常會包含某個查詢詞匯,沒有包含任何查詢詞匯的節點,通常不會作為語義地點的根節點,我們通過對語義地點的根節點做進一步篩選,然后再對選出的根節點施加蠻力法,即只選擇包含關鍵詞的節點做源點進行bfs,從而排除部分無效的bfs。
離線查詢(蠻力法2)
既然每次查詢都要對所有節點做一次bfs,我們為何不在一次bfs中記錄所有詞匯到源點的距離,而非只記錄查詢詞匯。
使用預處理的思想,在一次預處理中進行bfs,記錄所有詞匯到源點的距離并且將結果存儲在n張巨大的哈希表中(每個節點都有一張),這意味著我們可以花費常數時間來查詢任意節點到任意詞匯的最短距離。
注:離線查詢的本質還是蠻力法
反向bfs
反向bfs采用逆向思維。即從擁有查詢詞匯的節點開始,沿著有向圖的反向邊進行bfs,沿路更新父節點們到詞匯的距離。即只做有用的bfs。
除此之外,如果一個節點到所有詞匯的距離都不能被更新,那么不再對其bfs,因為該點及其之后的節點最短距離的計算,都不會依賴這一次的bfs帶來的最短距離信息,對他們來說這是一次無效的bfs,故可以提前結束
和蠻力法搜尋詞匯不同,反向bfs自己主動更新其他節點到查詢詞匯的距離,而不是等待別人來搜索自己。這么做大大減少了無效的bfs。這種更新方式和自底向上的動態規劃異曲同工。
總覽
總結
以上是生活随笔為你收集整理的深圳大学算法实验总结2020(实验1~6+大作业)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 采用结构光,TOF传感器的立体成像系统,
- 下一篇: 服务器CPU经常跑高是什么原因