1. 程式人生 > >幾種經典搜尋演算法以及應用

幾種經典搜尋演算法以及應用

目錄

二、 窮舉

分支定界:

A*

一、 評估你的複雜度

簡單的判斷演算法是否能滿足執行時間限制的要求

密切關注題中的給出的資料規模,選擇相應的演算法

一起試一下這個題目吧!

樣例: 輸入: 3 10 1 3  5 輸出: Yes 解析: 4次抽取的結果為 1,1,3,4 和就是10

輸入: 3 9 1 3 5 輸出: No

時間複雜度為O(n4)的列舉方法。 若n的條件限制為 1<=n <=1000   超時

for(int a=0; a<n; a++)
    for(int b=0; b<n; b++)
        for(int c=0; c<n; c++)
            for(int d=0; d<n; d++)
                if( k[a] + k[b] + k[c] + k[d] == m)
                    f=true;

改進,時間複雜度O( n2log2n) 的 方法:預處理一下(排序)

sort(k, k+n);​

for(int a=0; a<n; a++)
    for(int b=0; b<n; b++)
        for(int c=0; c<n; c++)
           
                if(  binary_search ( k+0,k+n, m - k[a] + k[b] + k[c] )  ) 
                    //在排好序的k數組裡面,在[0,n)範圍內二分查詢,返回是否存在 m-k[a]-k[b]-k[c]-k[d]
                    f=true;

​

改進,再改進:搜尋預處理(空間換時間)

#include <bits/stdc++.h>
 
using namespace std;
const  int MAX_N=51;
int k[MAX_N];
 
int kk[MAX_N * MAX_N];
 
int main()
{
      int n, m;  //n個數。取四次, 要求和為m;
      cin >>n >>m;

      for(int i=0;i<n;i++)
            cin >> k[i];

      for(int i=0;i<n;i++)
            for(int j=0;j<n;j++){
                  kk[ i*n + j] = k[i] + k[j];
            }
 
      sort(kk,kk+n*n);
 
      for(int i=0;i<n;i++)
            for(int j=0;j<n;j++){
                  if( binary_search( kk, kk+ n*n, m-k[i]-k[j] ) ){
                        cout << "Yes";
                        return 0;
                  }
            }
     cout << "No";
    return 0;
}

二、 窮舉

多重迴圈形式的列舉

本質上講深度搜索,寬度搜索本質上就是窮舉。

三、 二分查詢

查詢有序數列中的某個值

查詢數值23,你能模擬一下麼?

不只是查詢值(二分答案)

四、深度優先搜尋

簡單的深度搜索框架

深度搜索的演算法框架:(牢記)

部分和的問題: 樣例

輸入: 4 1 2 4 7 13

輸出 Yes

輸入: 4 1 2 4 7 15

輸出: No

版本一 : 將sum作為作為一個狀態,作為一個區域性變數


#include <bits/stdc++.h>

using namespace std;

const  int  MAX_N = 21;
int n;
int k;
int a[MAX_N];

bool dfs(int deep , int sum){

      if(sum==k) return true;  //這裡可以剪枝;

      if( deep == n) {
            return (sum == k);
      }

      if( dfs( deep+1 , sum + a[deep+1])  )  return true;
      if( dfs( deep+1, sum))  return true;

      return false;

}

int main()
{

      cin >>n;
      for(int i=1;i<=n;i++)
            cin >> a[i];
      cin >>k;

      if( dfs(0,0) ) cout <<"Yes";
      else   cout <<"No";

    return 0;
}

版本二:將sum作為全域性變數(要注意恢復現場)

//版本一  將sum作為全域性變數

#include <bits/stdc++.h>
using namespace std;
const  int  MAX_N = 21;
int n;
int k;
int a[MAX_N];
int sum=0;

bool dfs(int deep){

      if(sum==k) return true;  //這裡可以剪枝;
      if( deep == n) {
            return (sum == k);
      }
      sum = sum + a[deep+1];
      if( dfs( deep+1 ) )  return true;
      sum = sum- a[deep+1];

      sum = sum + 0;
      if( dfs( deep+1) )  return true;
      sum = sum - 0 ;

      return false;

}

int main()
{     cin >>n;
      for(int i=1;i<=n;i++)
            cin >> a[i];
      cin >>k;
      if( dfs(0) ) cout <<"Yes";
      else   cout <<"No";
    return 0;
}

 深度搜索的優化:迭代加深(ID DFS)

ID DFS是什麼:在DFS的搜尋裡面,可能會面臨一個答案的層數很低,但是DFS搜到了另為一個層數很深的分支裡面導致時間很慢,但是又卡BFS的空間,這個時候,可以限制DFS的搜尋層數,一次又一次放寬搜尋的層數,知道搜尋到答案就退出,時間可觀,結合了BFS和DFS的優點於一身。

以下是迭代加深的簡潔的框架,簡稱ID演算法:

bool  bk;
void  dfs(int  limit,int  x)
{
	if(...)
	{
		bk=true;
		return  ;
	}
	if(x==limit)return  ;
	for(...)dfs(limit,x+1);
}
for(int  i=1;i<=100;i++)
{
	dfs(i,0);
	if(bk==true)return  0;
}

深度搜索的優化: IDA* 

迭代加深搜尋框架為基礎,將原來簡單的深度限制加強為:  當前深度 + 未來估算步數 >  深度限制,則立即從單籤分枝回溯。

*重疊子問題(記憶化搜尋)

備忘錄可以記憶化,下次就可以直接呼叫之前搜尋過的結果 ,考慮使用位運算,速度可以加快些.對於一些可以轉移的狀態之間的搜尋,我們可以考慮Hash或者是狀壓下來,進行記憶化,那麼對於以後再次搜到這個狀態我們就可以直接呼叫了.

記憶化搜尋中你很可能會用到的:Hash方法 與 狀態壓縮方法

五、寬度優先搜尋

寬度優先搜尋框架:

寬度優先搜尋, 儘可能利用STL中的 Queue 。

例如:  網路爬蟲在抓取網頁的時候,會將連結指向的網頁的所有URL加入佇列功供後續處理。

例如:分油問題,一個一進的瓶子裝滿油,另有一個七兩盒一個三兩的空瓶,在沒有其他工具。只用這三個瓶子怎樣精確地把一斤油分成兩個半斤。

#include <bits/stdc++.h>
using namespace std;

struct P{
      int x;
      int y;
      int dis;
      P(int x,int y ,int dis){
            this->x =x;
            this->y=y;
            this->dis=dis;
      }
};

int N,M;
char yard[100][100];

int dx[]={0,  0 ,  1, -1  };
int dy[]={1 , -1 , 0,  0  };
int sx,sy,gx,gy;


void  bfs(){

      queue<P> Q;
      Q.push( P(sx,sy,0) );

      while( !Q.empty() ) {
            P p=Q.front();  Q.pop();

            for(int i=0;i<4;i++){
                  int  newx = p.x + dx[i];
                  int  newy = p.y + dy[i];
                   if( newx == gx &&  newy == gy ) {
                              cout <<  p.dis + 1;
                              return;
                   }

                  if( newx>=0 && newx<N && newy>0 && newy<M && yard[newx][newy]=='.' )
                       {
                             Q.push( P( newx, newy, p.dis+1 ) );
                             yard[newx][newy] = '#';
                      }

            }

      }

}

int main()
{

      cin >> N >> M ;
      for(int i=0;i<N;i++){
            for(int j=0;j<M;j++){
                  char t;   //這裡除錯了半個小時。  誤輸入了int
                  cin >>  t;
                  if( t=='S') { sx=i; sy=j;}
                  if( t=='G') { gx=i; gy=j;}
                  yard[i][j] = t;
            }
      }
      bfs();

    return 0;
}

深度優先於寬度搜索優先的區別與聯絡: 都會擴充套件每個節點的所有節點,而不同的是:在對於擴充套件節點的選取上, 深度搜索下一次擴充套件是本次擴充套件的子節點中的一個,而廣度搜索是本次擴充套件的節點的兄弟節點。廣度搜索一般只用於找最優解。深度搜索可以搜尋出所有的解。

分支定界:

分支定界實際上是A*演算法的一種雛形,其對於每個擴展出來的節點給出一個預期值,如果這個預期值不如當前已經搜尋出來的結果好的話,則將這個節點(包括其子節點)從解答樹中刪去,從而達到加快搜索速度的目的。  例如:某公司於乙城市的銷售點急需一批成品,該公司成品生產基地在甲城市。甲城市與乙城市之間共有n 座城市,互相以公路連通。甲城市、乙城市以及其它各城市之間的公路連通情況及每段公路的長度由矩陣M1 給出。每段公路均由地方政府收取不同額度的養路費等費用,具體數額由矩陣M2 給出。 請給出在需付養路費總額不超過1500 的情況下,該公司貨車運送其產品從甲城市到乙城市的最短運送路線。

A*

A*是對於bfs的優化,啟發式搜尋。(本質是一個帶有估價函式的優先佇列BFS)

未完待續。