算法
插入排序
- 每次將一個待排序的元素與已排序的元素進行逐一比較,直到找到合適的位置按大小插入。
插入排序代碼
public static void insertsort(int arr[]){
for(int i = 1;i < arr.length; i ++){
if(arr[i] < arr[i-1]){
int temp = arr[i];
int j;
for(j = i-1; j >= 0 && arr[j] > temp; j --){
arr[j+1] = arr[j]; }
arr[j+1] = temp; }
}
}
注意[0,i-1]都是有序的。如果待插入元素比arr[i-1]還大則無需再與[i-1]前面的元素進行比較了,反之則進入if語句
快速排序
- 快速排序是找出一個元素(一般找最后一個)作為基準(pivot),然后對數組進行分區操作,使基準左邊元素的值都不大于基準值,基準右邊的元素值 都不小于基準值,如此作為基準的元素調整到排序后的正確位置。遞歸快速排序,將其他n-1個元素也調整到排序后的正確位置。最后每個元素都是在排序后的正確位置,排序完成。快速排序算法的核心算法是分區操作,即如何調整基準的位置以及調整返回基準的最終位置以便分治遞歸。
快速排序JavaScript代碼
var quickSort = function(arr) {
if (arr.length <= 1) { return arr; }
var pivotIndex = Math.floor(arr.length / 2);
var pivot = arr.splice(pivotIndex, 1)[0];
var left = [];
var right = [];
for (var i = 0; i < arr.length; i++){
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat([pivot], quickSort(right));
};
動態規劃:
- 實質上還是嘗試了所有可能的方案,只是從小到大按照需求的增加去得到每一個子問題的最優解并記錄,后面的階段大問題對所有可能做嘗試并劃分成小的問題,直接利用小問題已知的最優解帶入去和其他嘗試的結果去比較得到大問題的最優解。在這個過程中不需要再去重復求解小問題的最優解。
- 鋼條的切割問題:不斷累加的是鋼條的長度,在每一個長度內嘗試所有的切割方式(即不斷累加切割的長度),嘗試比較后得到這個鋼條長度的最優切割方式并保存。從小到大最后得到最終的最優解。
- 01背包??問題:不斷增加的是背包的容量,在每一個長度內嘗試放入一種物品(如果空間足夠),放入后剩余空間則放入對應容量的最優值,比較放入物品后的總價值并記錄。最終得到大容量背包的最優解。
01??代碼
#include<stdlib.h>
#include<stdio.h>
int V[200][200];//前i個物品裝入容量為j的背包中獲得的最大價值
int max(int a,int b) //一個大小比較函數,用于當總重大于第I行時
{
if(a>=b)
return a;
else return b;
}
int Knap(int n,int w[],int v[],int x[],int C)
{
int i,j;
for(i=0;i<=n;i++)
V[i][0]=0;
for(j=0;j<=C;j++)
V[0][j]=0;
for(i=0;i<=n-1;i++)
for(j=0;j<=C;j++)
if(j<w[i])
V[i][j]=V[i-1][j];
else
V[i][j]=max(V[i-1][j],V[i-1][j-w[i]]+v[i]);
j=C;
for(i=n-1;i>=0;i--)
{
if(V[i][j]>V[i-1][j])
{
x[i]=1;
j=j-w[i];
}
else
x[i]=0;
}
printf("選中的物品是: \n");
for(i=0;i<n;i++)
printf("%d ",x[i]);
printf("\n");
return V[n-1][C];
}
貪婪算法
- 分式背包??問題: 首先應計算物品在單位體積下的價值,先獲取單位價值最大的物品,未滿的情況下繼續添加單位價值第二大的物品,直至背包填滿::
最大流
- 從源點到經過的所有路徑的最終到達匯點的所有流量和。
流網絡G=(V,E)是一個有向圖,其中每條邊(u,v)∈E均有一個非負容量c(u,v)>=0。如果(u,v)不屬于E,則假定c(u,v)=0。流網絡中有兩個特別的頂點:源點s和匯點t。下圖展示了一個流網絡的實例(其中斜線左邊的數字表示實際邊上的流,右邊的數字表示邊的最大容量): - Alt text
- 對一個流網絡G=(V,E),其容量函數為c,源點和匯點分別為s和t。G的流f滿足下列三個性質:
1. 容量限制:對所有的u,v∈V,要求f(u,v)<=c(u,v)。
2. 反對稱性:對所有的u,v∈V,要求f(u,v)=-f(v,u)。
3. 流守恒性:對所有u∈V-{s,t},要求∑f(u,v)=0 (v∈V)。
*容量限制說明了從一個頂點到另一個頂點的網絡流不能超過設定的容量
在給定的流網絡G=(V,E)中,設f為G中的一個流,并考察一對頂點u,v∈V,在不超過容量c(u,v)的條件下,從u到v之間可以壓入的額外網絡流量,就是(u,v)的殘留容量,就好像某一個管道的水還沒有超過管道的上限,那么就這條管道而言,就一定還可以注入更多的水。殘留容量的定義為:cf(u,v)=c(u,v)-f(u,v)。而由所有屬于G的邊的殘留容量所構成的帶權有向圖就是G的殘留網絡。下圖就是上面的流網絡所對應的殘留網絡:
- Alt text
殘留網絡中的邊既可以是E中邊,也可以是它們的反向邊。只有當兩條邊(u,v)和(v,u)中,至少有一條邊出現在初始網絡中時,邊(u,v)才會出現在殘留網絡中。下面是一個有關殘留網絡的定理,若f是G中的一個流,Gf是由G導出的殘留網絡,f'是Gf中的一個流,則f+f'是G中一個流,且其值|f+f'|=|f|+|f'|。證明時只要證明f+f'這個流在G中滿足之前所講述的三個原則即可。在這里只給出理解性的證明,可以想象如果在一個管道中流動的水的總流量為f,而在該管道剩余的流量中存在一個流f'可以滿足不會超過管道剩余流量的最大限,那么將f和f'合并后,也必定不會超過管道的總流量,而合并后的總流量值也一定是|f|+|f'|。
增廣路徑p為殘留網絡Gf中從s到t的一條簡單路徑。根據殘留網絡的定義,在不違反容量限制的條件下,G中所對應的增廣路徑上的每條邊(u,v)可以容納從u到v的某額外正網絡流。而能夠在這條路徑上的網絡流的最大值一定是p中邊的殘留容量的最小值。這還是比較好理解的,因為如果p上的流大于某條邊上的殘留容量,必定會在這條邊上出現流聚集的情況。所以我們將最大量為p的殘留網絡定義為:cf(p)=min{cf(u,v) | (u,v)在p上}。而結合之前在殘留網絡中定理,由于p一定是殘留網絡中從s到t的一條路徑,且|f'|=cf(p),所以若已知G中的流f,則有|f|+|cf(p)|>|f|且|f|+|cf(p)|不會超過容量限制。
-
流網絡G(V,E)的割(S,T)將V劃分成為S和T=V-S兩部分,使得s∈S,t∈T。如果f是一個流,則穿過割(S,T)的凈流被定義為f(S,T)=∑f(x,y) (x∈S,y∈T),割(S,T)的容量為c(S,T)。一個網絡的最小割就是網絡中所有割中具有最小容量的割。設f為G中的一個流,且(S,T)是G中的一個割,則通過割(S,T)的凈流f(S,T)=|f|。因為f(S,T)=f(S,V)-f(S,S)=f(S,V)=f(s,V)+f(S-s,V)=f(s,V)=|f|(這里的公式根據f(X,Y)=∑f(x,y) (x∈X,y∈Y)的定義,以及前面的三個限制應該還是可以推出來的,這里就不細講了)。有了上面這個定理,我們可以知道當把流不斷增大時,流f的值|f|不斷的接近最小割的容量直到相等,如果這時可以再增大流f,則f必定會超過某個最小的割得容量,則就會存在一個f(S,T)<=c(S,T)<|f|,顯然根據上面的定理這是不可能。所以最大流必定不超過網絡最小割的容量。
綜合上面所講,有一個很重要的定理:最大流最小割定理
如果f是具有源s和匯點t的流網絡G=(V,E)中的一個流,則下列條件是等價的:
1. f是G中一個最大流。
2. 殘留網絡Gf不包含增廣路徑。
3. 對G的某個割(S,T),有|f|=c(S,T)。
#include <iostream>
#include <queue>
#include<string.h>
using namespace std;
#define arraysize 201
int maxData = 0x7fffffff;
int capacity[arraysize][arraysize]; //記錄殘留網絡的容量
int flow[arraysize]; //標記從源點到當前節點實際還剩多少流量可用
int pre[arraysize]; //標記在這條路徑上當前節點的前驅,同時標記該節點是否在隊列中
int n,m;
queue<int> myqueue;
int BFS(int src,int des)
{
int i,j;
while(!myqueue.empty()) //隊列清空
myqueue.pop();
for(i=1;i<m+1;++i)
{
pre[i]=-1;
}
pre[src]=0;
flow[src]= maxData;
myqueue.push(src);
while(!myqueue.empty())
{
int index = myqueue.front();
myqueue.pop();
if(index == des) //找到了增廣路徑
break;
for(i=1;i<m+1;++i)
{
if(i!=src && capacity[index][i]>0 && pre[i]==-1)
{
pre[i] = index; //記錄前驅
flow[i] = min(capacity[index][i],flow[index]); //關鍵:迭代的找到增量
myqueue.push(i);
}
}
}
if(pre[des]==-1) //殘留圖中不再存在增廣路徑
return -1;
else
return flow[des];
}
int maxFlow(int src,int des)
{
int increasement= 0;
int sumflow = 0;
while((increasement=BFS(src,des))!=-1)
{
int k = des; //利用前驅尋找路徑
while(k!=src)
{
int last = pre[k];
capacity[last][k] -= increasement; //改變正向邊的容量
capacity[k][last] += increasement; //改變反向邊的容量
k = last;
}
sumflow += increasement;
}
return sumflow;
}
int main()
{
int i,j;
int start,end,ci;
while(cin>>n>>m)
{
memset(capacity,0,sizeof(capacity));
memset(flow,0,sizeof(flow));
for(i=0;i<n;++i)
{
cin>>start>>end>>ci;
if(start == end) //考慮起點終點相同的情況
continue;
capacity[start][end] +=ci; //此處注意可能出現多條同一起點終點的情況
}
cout<<maxFlow(1,m)<<endl;
}
return 0;
}
bool EK_Bfs (int start, int end)//廣搜用于找增廣路;
{
bool flag[Maxn];//標記數組
memset (flag, false, sizeof(flag));
memset (p, -1, sizeof(p));
flag[start] = true;
queue t;
t.push(start);
while (!t.empty())
{
int top = t.front();
if (top == end)return true;// 此時找到增廣路
t.pop();
for (int i=1; i<=n; i++)
{
if (map[top][i] && !flag[i])
{
flag[i] = true;
t.push(i);
p[i] = top;// 記錄前驅(很關鍵)
}
}
}
return false;
}
int E_K (int start,int end)
{
int u,max = 0,mn;//max用來初始化最大流為0;
while (EK_Bfs(start,end))//當增廣成功時
{
mn = 100000;
u = end;
while (p[u] != -1)//尋找”瓶頸“邊,并且記錄容量;
{
mn = min (mn, map[p[u]][u]);
u = p[u];
}
max += mn;//累加邊的最大流;
u = end;
while (p[u] != -1)//修改路徑上的邊容量;
{
map[p[u]][u] -= mn;
map[u][p[u]] += mn;
u = p[u];
}
}
return max;
}