線性數據結構
線性結構是一個有序數據元素的集合。
常用的線性結構
線性表,棧,隊列,雙隊列,串(一維數組)。
非線性數據結構
關於廣義表、數組(高維),是一種非線性的數據結構。
常見的非線性結構有:二維數組,多維數組,廣義表,樹(二叉樹等),圖
線性表(線性存儲結構)
將具有“一對一”關系的數據“線性”地存儲到物理空間中,這種存儲結構就稱為線性存儲結構(簡稱線性表)。
使用線性表存儲的數據,如同向數組中存儲數據那樣,要求數據類型必須一致,也就是說,線性表存儲的數據,要么全部都是整形,要么全部都是字符串。一半是整形,另一半是字符串的一組數據無法使用線性表存儲。
將數據依次存儲在連續的整塊物理空間中,這種存儲結構稱為順序存儲結構(簡稱順序表);數據分散的存儲在物理空間中,通過一根線保存着它們之間的邏輯關系,這種存儲結構稱為鏈式存儲結構(簡稱鏈表)
某一元素的左側相鄰元素稱為“直接前驅”,位於此元素左側的所有元素都統稱為“前驅元素”;某一元素的右側相鄰元素稱為“直接后繼”,位於此元素右側的所有元素都統稱為“后繼元素”
棧
棧又名堆棧,它是一種運算受限的線性表。限定僅在表尾進行插入和刪除操作的線性表。這一端被稱為棧頂,相對地,把另一端稱為棧底。向一個棧插入新元素又稱作進棧、入棧或壓棧,它是把新元素放到棧頂元素的上面,使之成為新的棧頂元素;從一個棧刪除元素又稱作出棧或退棧,它是把棧頂元素刪除掉,使其相鄰的元素成為新的棧頂元素。
單調棧
單調棧中存放的數據應該是有序的,所以單調棧也分為單調遞增棧和單調遞減棧。
單調遞增棧:單調遞增棧就是從棧底到棧頂數據是從大到小。
單調遞減棧:單調遞減棧就是從棧底到棧頂數據是從小到大。
括號序列
括號序列是指由 ‘(’和‘)’ 組成的序列,假如一個括號序列中,包含相同數量的左括號和右括號,並且對於每一個右括號,在它的左側都有左括號和他匹配,則這個括號序列就是一個合法括號序列,如(())( )就是一個合法括號序列,但(())(( )不是合法括號序列.
空串是合法的括號序列。
若S是合法的括號序列,則(S)是合法的括號序列。
若S和T分別是合法的括號序列,則ST也是合法的括號序列。
隊列
隊列是一種特殊的線性表,特殊之處在於它只允許在表的前端進行刪除操作,而在表的后端進行插入操作,和棧一樣,隊列是一種操作受限制的線性表。進行插入操作的端稱為隊尾,進行刪除操作的端稱為隊頭。
單調隊列
單調隊列顧名思義就是一個有規律的隊列,這個隊列的規律是:所有在隊列里的數都必須按遞增(或遞減)的順序列隊。
單調隊列只能解決一個叫滑動窗口的問題。
雙端隊列
雙端隊列是一種具有隊列和棧性質的數據結構,即可(也只能)在線性表的兩端進行插入和刪除。
折半搜索(二分)
前綴和
前綴和是一個數組的某項下標之前(包括此項元素)的所有數組元素的和。
差分
差分,一般在大數據里用在以時間為統計維度的分析中,其實就是下一個數值 ,減去上一個數值 。
二維前綴和:b[x,y]=b[x-1,y]+b[x,y-1]-b[x-1,y-1]+a[x,y]
矩陣求和:S(x1,y1,x2,y2)=b[x2,y2]-b[x1-1,y2]-b[x2,y1-1]+b[x1-1,x2-1]
二維差分:b[x,y]=a[x,y]+a[x-1,y-1]-a[x-1,y]-a[x,y-1]
修改矩形[x1,y1,x2,y2]等價於b[x1,y1]+=v,b[x2+1,y2+1]+=v,b[x1,y2+1]-=v,b[x2+1,y1]-=v。
基數排序(松氏基排)
基本解法
第一步
以LSD為例,假設原來有一串數值如下所示:
73, 22, 93, 43, 55, 14, 28, 65, 39, 81
首先根據個位數的數值,在走訪數值時將它們分配至編號0到9的桶子中:
0
1 81
2 22
3 73 93 43
4 14
5 55 65
6
7
8 28
9 39
第二步
接下來將這些桶子中的數值重新串接起來,成為以下的數列:
81, 22, 73, 93, 43, 14, 55, 65, 28, 39
接着再進行一次分配,這次是根據十位數來分配:
0
1 14
2 22 28
3 39
4 43
5 55
6 65
7 73
8 81
9 93
第三步
接下來將這些桶子中的數值重新串接起來,成為以下的數列:
14, 22, 28, 39, 43, 55, 65, 73, 81, 93
這時候整個數列已經排序完畢;如果排序的對象有三位數以上,則持續進行以上的動作直至最高位數為止。
LSD的基數排序適用於位數小的數列,如果位數多的話,使用MSD的效率會比較好。MSD的方式與LSD相反,是由高位數為基底開始進行分配,但在分配之后並不馬上合並回一個數組中,而是在每個“桶子”中建立“子桶”,將每個桶子中的數值按照下一數位的值分配到“子桶”中。在進行完最低位數的分配后再合並回單一的數組中。
區間算法
區間計算與傳統的以數為對象的運算(即點計算)不同,它的運算對象是區間。
由於數字計算機只能使用有限位數表示實數,不能精確表達數學意義上的數值,所以數值的每一步計算都會產生誤差。億萬次計算之后,計算機的“舍入規則”效應可能累積相當大的計算誤差,導致數值計算結果精度嚴重損失。而區間計算的整個過程以“區間”為運算對象,提供區間形式的計算結果。這些運算區間在構造上保證包含數據的真實值,使得結果區間也能夠保證包含數據運算的真實結果。
O(n)-O(1)
四毛子算法
一種(非常規)分塊后暴力預處理以此來優化復雜度的思想。
RMQ
RMQ,即區間最值查詢,這是一種在線算法,所謂在線算法,是指用戶每次輸入一個查詢,便馬上處理一個查詢。RMQ算法一般用較長時間做預處理,時間復雜度為O(nlogn),然后可以在O(1)的時間內處理每次查詢。
RMQ標准算法:先規約成LCA(最近公共祖先),再規約成約束RMQ,O(n)-O(q) online。
首先根據原數列,建立笛卡爾樹,從而將問題在線性時間內規約為LCA問題。LCA問題可以在線性時間內規約為約束RMQ,也就是數列中任意兩個相鄰的數的差都是+1或-1的RMQ問題。約束RMQ有O(n)-O(1)的在線解法,故整個算法的時間復雜度為O(n)-O(1)。
哈希表
unordered-map(基於哈希實現的映射)
除留余數法
取關鍵字被某個不大於散列表表長m的數p除后所得的余數為散列地址。即 H(key) = key MOD p,p<=m。不僅可以對關鍵字直接取模,也可在折疊、平方取中等運算之后取模。對p的選擇很重要,一般取素數或m,若p選的不好,容易產生同義詞。
雙向平方試判(雙平方探測法)
為了解決二次聚集現象發明了雙平方探測法 當沖突產生時 向該沖突點的雙向以步長i^2(1 4 9 16 25…) 探測若保證散列表的長度是素數且滿足4K+3則可以遍歷整個散列表從而不存在二次聚集現象。
STL
STL 是“Standard Template Library”的縮寫,中文譯為“標准模板庫”。
#include<algorithm>
#include<bits/stdc++.h>(推薦)
sort()
sort函數用於給一個數組進行排序,在algorithm庫里。
使用方法為sort(v.begin(),v.end(),cmp)),這里的v.begin()是開始的指針位置,v.end()是結束的指針位置(這里的表示是左閉右開,也就是說v.end()並不在排序內容里),cmp可要可不要,如果使用的是自定義類型且沒有重載運算符就一定需要。
這個數組的類型可以是自定義類型或者STL中的vector,需要注意的是如果采用的是自定義類型則需要重載運算符(也可以如上面說的一樣用cmp)。
stack
模擬棧,在<stack>里。
stack <Type> A;
常用函數
A.push(a);
入棧
A.pop();
出棧
A.top();
返回棧頂元素(但不出棧)
queue
模擬隊列,在<queue>里。
queue <Type> A;
常用函數
A.push(a);
入隊
A.pop();
出隊
A.front();
返回隊首元素(但不出隊)
deque
雙端隊列
priority queue
優先隊列,一個類似於堆的數據結構,在<queue>里。
默認是大根堆,如果想讓他是小根堆的話有兩種辦法,其中一種是重載小於號。
能以O(logN)復雜度完成插入元素,刪除最值,尋找最值。
Priority_queue <Type> A;
常用函數
A.push(a);
插入
A.pop();
刪除最值(默認為最大值)
A.top();
返回最值(但不刪除)
pair
一個包含兩個可以不同的數據值的類型。
pair <Type1,Type2> A;
往往Type1,Type2都是標准類型,但如果是自定義類型,需要重定義運算符;
常用函數
賦值:make_pair(t1,t2);
返回對應的值:A.first,A.second;
vector
向量(Vector)是一個封裝了動態大小數組的順序容器。跟任意其它類型容器一樣,它能夠存放各種類型的對象。可以簡單的認為,向量是一個能夠存放任意類型的不定長的動態數組,在vector庫里。
定義方式:vector <Type> A;
容器特性
順序序列
順序容器中的元素按照嚴格的線性順序排序。可以通過元素在序列中的位置訪問對應的元素。
動態數組
支持對序列中的任意元素進行快速直接訪問,甚至可以通過指針算述進行該操作。提供了在序列末尾相對快速地添加/刪除元素的操作。
能夠感知內存分配器的(Allocator-aware)
容器使用一個內存分配器對象來動態地處理它的存儲需求。
相當於是個動態數組,每次可以往末端插入一個元素,下標從0開始。
實現方式是每次不夠大的時候暴力倍長,可以發現均攤是線性的。
常用操作
A.clear();
清空
A.push_back(a);
尾部添加元素
A.pop_back();
尾部刪除元素
A.empty();
檢查是否為空,空返回true
v.size()
這個一個unsigned int類型。也就是說對空的vector的size()-1會得到2^32-1。因此寫代碼的時候應帶盡量避免這種寫法。(或者強制類型轉化成int)
v.resize()
其復雜度是O(max(1, resize()中的參數-原來的size()))的。
如果是大小變大的resize(),且可以指定新擴展的位置的值。若未指定則調用其默認構造函數,例如int之類的會默認是0。
v.clear()和vector<int>().swap(v)的區別。
前者是假裝清空了,實際內存沒有被回收。
后者是真的回收了,不過需要和v.size()的大小成正比的時間。
后者的意思是使用vector<>()這句話調用無參的構造函數生成一個vector<>類型的對象,然后和v交換,之后其生存期結束被銷毀會自動調用其~vector<>()析構函數。注意<>里面要寫v一樣的類型(例如int)
set
一個存儲集合的容器,在set庫里。
map相當於是一個下標可以是任何數值的數組,如果訪問時沒有賦值就會返回零。
內部使用紅黑樹(一種平衡樹)實現。
當set<>中的第一個參數是自定義類型的時候需要重新定義小於號。
復雜度基本上是O(log(當前大小))。
定義方式:set <Type> A;
常用函數
A.insert(a);
插入a
A.erase(a);
刪除a:
A.find(a);
查找a,如果查找成功,返回對應指針,查找失敗返回尾指針
A.begin(),A.end();
返回頭指針與尾指針,尾指針不存儲具體內容
map
存儲一個從key到value的映射。某種意義上就是“廣義”數組,在map庫里。
map相當於是一個下標可以是任何數值的數組,如果訪問時沒有賦值就會返回零。
內部使用紅黑樹(一種平衡樹)實現。
當map<,>中的第一個參數是自定義類型的時候需要重新定義小於號。
復雜度基本上是O(log(當前大小))
map <Type1,Type2> A;Type1是key類型,Type2是value類型。
可以通過A[B]=C這種形式賦值,B為Type1,C為Type2。
常用函數
A.clear();
清空
A.empty();
判斷是否為空
A.insert(pair<Type1,Type2> (C,D));
插入(不建議這么寫)
A.erase(B);
刪除,B可以是key值也可以是指針
A.begin(),A.end();
頭指針,尾指針
m[x]
哪怕你什么也不干只寫一個m[x];也會新建一個點。
因此當你想知道map中是否存在這個映射的時候最好使用m.count(x)。
很多時候可以有效卡常。
multiset和multimap
是可重集合和可重映射。
有兩個注意的:第一個是count函數復雜度變成了O(lg(集合大小)+答案)的,也就是如果有很多相同元素,那么count函數代價很大。
第二個是刪除x的話,使用s.erase(x)會把所有權值為x的刪除。
如果只想刪掉一個需要s.erase(s.find(x))。
unordered_set和unordered_map
基於哈希實現的集合和映射。
基本上里面的類型只能是int,long long,double這種非自定義類型。 因為其基於哈希)
在c++11及以后存在,之前沒有,亂用可能會CE。
空間常數比較大,時間常數不小,比數組訪問慢很多,慎用。
不能順序遍歷,不支持lower_bound。
迭代器
只介紹set/map的迭代器。
bitset
高精度壓位二進制。
一個用於處理二進制串的“數組”,在<bitset>里。
所有時間復雜度是線性的操作,常數都是1/32大概。
下標從0開始。
bitset <n> A;//n為長度;
支持所有位運算: << , >> , & , | , ^ ;
常用函數
A.count();
統計1的個數
A.reset();
清0
A.set();
全賦為1
A.size();
返回位數
lower bound()/upper bound()
lower_bound(v.begin(),v.end(),c)可以在一個有序數組當中找出剛好大於或等於c的數,在algorithm庫里,可以使用自定義類型,用法與sort相類似。
upper_bound函數類似,這個函數則是在有序數組中找出剛好大於c的數。
next permutation/prev permutation(全排列算法)
algorithm頭文件中包含了next_permutation(v.begin(),v.end())和prev_permutation(v.begin(),v.end())兩個全排列函數,分別給出一個序列在全排列中的下一個和上一個序列(按字典序),如果存在這樣的序列則返回true,不存在則返回false,但仍會得到一個序列。
對於一個任意元素不相同的序列來說,正序排列是最小的排列方式,相應的逆序排列是最大的排列方式,以整數序列{1,2,3}為例,{1,2,3}是第一個排列,{3,2,1}則是最后一個排列,因此序列{1,2,3}沒有上一個序列,{3,2,1}沒有下一個序列。
pq.swap(pq2)
補充
namespace
命名空間,之后寫代碼長的時候用來避免變量名或者函數名重名的。主要是同一個namespace里面默認使用自己名字空間的東西。
std一般是教師專用賬號。
對於107~108的數據,一般運行1s左右。
並非原創,僅是整理,請見諒