資料結構與演算法-5大常用演算法總結
1.貪心演算法
基本思想:貪心演算法分階段工作,在每一階段,可以認為所做的決定是好的,而不考慮將來的後果。意味著選擇的是區域性最優,如果剛好是全域性最優則演算法正確,否則得到的是一個次優解。所有可以應用於不需要得到最佳答案,用貪心演算法生成近似答案。
1.1簡單的作業排程問題
問題:給出n個任務和每個任務的開始和結束時間。找出可以完成的任務的最大數量,在同一時刻只能做一個任務。
vector<int> findCompleteWorkCount(vector<int> start, vector<int> finish)
{
vector <int> result;
int i = 0;
for (int j = 1; j < start.size(); ++j)
{
if (start[j] >= finish[i])
{
result.push_back(j);
i = j;
}
}
return result;
}
1.2最小生成樹prim演算法
問題:從無向圖中尋找一顆最下生成樹,一個無向圖的最小生成樹就是由該圖的那些連線G的所有頂點的邊構成的樹,其總值最低。
方法:演算法每一個階段通過選擇邊(u,v),使得(u,v)的值是所有u在樹上、但v不在樹上邊的值中最小者。
#define v 5
void printMST(int parent[], int graph[v][v])
{
printf("Edge Weight\n");
for (int i = 1; i < v; i++)
printf("%d - %d %d \n", parent[i], i, graph[i][parent[i]]);
}
void prim(int graph[v][v])
{
int value[v];
bool finish[v];
int parent[v];
for (int i = 1; i < v; ++i)
{
value[i] = graph[0][i];
parent[i] = 0;
finish[i] = false;
}
parent[0] = -1;
finish[0] = true;
for (int i = 1; i < v; ++i)
{
int min_val = INT_MAX, min_index;
for (int j = 1; j<v; ++j)
if (!finish[j] && value[j] != 0 && value[j] < min_val)
{
min_val = value[j];
min_index = j;
}
finish[min_index] = true;
for (int j = 1; j < v; ++j)
if (!finish[j] && graph[min_index][j]!=0&&(value[j] == 0 || graph[min_index][j] < value[j]))
{
parent[j] = min_index;
value[j] = graph[min_index][j];
}
}
printMST(parent, graph);
}
2.分治演算法
基本思想:
分:遞迴解決較小的問題;
治:從子問題的解構建原問題的解
至少包含兩個遞迴呼叫的例程叫作分治演算法
2.1最大字陣列問題
問題:輸入一個整形陣列,數組裡有正數也有負數。陣列中連續的一個或多個整陣列成一個子陣列,每個子陣列都有一個和。求所有子陣列的和的最大值
int Start,End,Value;
int FindMaxCrossArray(int a[],int low,int mid,int high)
{
int left_sum=numeric_limits<int>::min();
int right_sum=numeric_limits<int>::min();
int sum=0;
for (int i=mid;i>=low;i--)
{
sum+=a[i];
if(sum>left_sum)
{
left_sum=sum;
Start=i;
}
}
sum=0;
for(int j=mid+1;j<=high;j++)
{
sum+=a[j];
if (sum>right_sum)
{
right_sum=sum;
End=j;
}
}
return left_sum+right_sum;
}
int FindMaxArray(int a[],int low,int high)
{
if (low==high)
{
Start=End=low;
return a[low];
}
int mid=(low+high)/2;
int left_sum=FindMaxArray(a,low,mid);
int right_sum=FindMaxArray(a,mid+1,high);
int cross_sum=FindMaxCrossArray(a,low,mid,high);
if (left_sum>=right_sum&&left_sum>=cross_sum)
{
Start=low;
End=mid;
return left_sum;
}
else if (right_sum>=left_sum&&right_sum>=cross_sum)
{
Start=mid+1;
End=high;
return right_sum;
}
else
return cross_sum;
}
int main()
{
int a[13]={-13,-3,-25,-20,-3,-16,-23,-18,-20,-7,-12,-5,-22};
int result=FindMaxArray(a,0,12);
return 0;
}
2.2最近點對問題
問題:給定平面上n個點,找其中的一對點,使得在n個點的所有點對中,該點對的距離最小。嚴格地說,最接近點對可能多於1對。為了簡單起見,這裡只限於找其中的一對。
程式碼待寫…
3.動態規劃
基本思想:將待求解的問題分解為若干個子問題(階段),按順序求解子階段,前一子問題的解,為後一子問題的求解提供了有用的資訊。在求解任一子問題時,列出各種可能的區域性解,通過決策保留那些有可能達到最優的區域性解,丟棄其他區域性解。依次解決各子問題,最後一個子問題就是初始問題的解。
3.1揹包問題
問題:給定n種物品和一揹包。物品i的重量是wi,其價值為vi,揹包的容量為C。問應如何選擇裝入揹包的物品,使得裝入揹包中物品的總價值最大?
int Max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int N,i,j;
int p[100][100];
int C;
vector<Goods> goods;
cout<<"請輸入商品總量";
cin>>N;
for (i=0;i<N;i++)
{
cout<<"請輸入第"<<i+1<<"個的價值和重量:";
int vi,wi;
cin>>vi>>wi;
goods.push_back(Goods(vi,wi));
}
cout<<"請輸入揹包最大重量";
cin>>C;
for (i=0;i<N;i++)
p[i][0]=0;
for (j=0;j<C;j++)
p[0][j]=0;
for (i=1;i<N;i++)
for (j=1;j<C;j++)
{
if(j<goods[i].w)p[i][j]=p[i-1][j];
else
p[i][j]=Max(p[i-1][j-goods[i].w]+goods[i].v,p[i-1][j]);
}
return 0;
}
3.2leetcode動態規劃題目
- Minimum Path Sum
int minPathSum(vector<vector<int>>& grid) {
if (grid.empty())return 0;
vector<vector<int>> result(grid);
for (int i = 1; i < result[0].size(); ++i)
result[0][i] += result[i][i - 1];
for (int i = 1; i < result.size(); ++i)
result[i][0] += result[i-1][0];
for (int i = 1; i > result.size(); ++i)
for (int j = 1; j < result[i].size(); ++j)
result[i][j] = min(result[i - 1][j] + grid[i][j], result[i][j - 1] + grid[i][j]);
return result[result.size() - 1][result[0].size() - 1];
}
4.回溯演算法
基本思想:在包含問題的所有解的解空間樹中,按照深度優先搜尋的策略,從根結點出發深度探索解空間樹。當探索到某一結點時,要先判斷該結點是否包含問題的解,如果包含,就從該結點出發繼續探索下去,如果該結點不包含問題的解,則逐層向其祖先結點回溯。
一般步驟:
(1)針對所給問題,確定問題的解空間:首先應明確定義問題的解空間,問題的解空間應至少包含問題的一個(最優)解。
(2)確定結點的擴充套件搜尋規則
(3)以深度優先方式搜尋解空間,並在搜尋過程中用剪枝函式避免無效搜尋。
4.1leetcode相關題目
- Combination Sum
class Solution {
public:
vector<vector<int>> result;
vector<int> data;
int sum(const vector<int>& v)
{
int sum = 0;
for (auto e : v)sum += e;
return sum;
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<int> temp;
sort(candidates.begin(), candidates.end());
data = candidates;
dfs(temp, target, 0);
return result;
}
void dfs(vector<int> temp, int target, int k)
{
if (k == data.size())return;
if (sum(temp) == target) {
result.push_back(temp); return;}
else if (sum(temp) > target)return;
else
{
for (int i = k; i < data.size(); ++i)
{
temp.push_back(data[i]);
dfs(temp, target, i);
temp.pop_back();
}
}
}
};
2.Subsets
class Solution {
public:
vector<vector<int>> result;
vector<int> temp;
vector<vector<int>> subsets(vector<int>& nums) {
dfs(nums, 0, nums.size() - 1);
return result;
}
void dfs(vector<int> nums, int i, int j)
{
result.push_back(temp);
for (int k = i; k <=j; ++k)
{
temp.push_back(nums[k]);
dfs(nums, k + 1, j);
temp.pop_back();
}
}
};
3.Permutations
5.分支界限法
基本思想:類似於回溯法,也是一種在問題的解空間樹T上搜索問題解的演算法。但在一般情況下,分支限界法與回溯法的求解目標不同。回溯法的求解目標是找出T中滿足約束條件的所有解,而分支限界法的求解目標則是找出滿足約束條件的一個解,或是在滿足約束條件的解中找出使某一目標函式值達到極大或極小的解,即在某種意義下的最優解。