亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

閑談C++算法封裝:窮舉法

2019-11-17 05:12:48
字體:
來源:轉載
供稿:網友

  將算法獨立抽象出來,在C++中算不上新鮮:STL中就封裝了不少高效、健壯、靈活的泛型組件及對應的基礎算法,工藝之高、適用性之強,非平常我輩所輕易能及。這里不打算(也暫沒有能力打算)以STL這樣的工業級要求來談論算法封裝,只因最近嘗翻大師名著,閱者水平有限,僅嗅觸至皮毛,理智薄弱,感情卻蓬勃發展:也欲嘗試“封裝”的味道。選擇了最簡易的窮舉算法,抽其骨架,炮制成class,套上一實際例子,觀之run之,抽象程度頗低,效率損失彌彰;然卻也自覺有可愛之處,遂作此文以記之。誠惶誠恐,便于名目之前加“閑談”二字,倘果因技術問題招致痛罵,則試以此二字為護文符,聊且一擋。

  眾所周知,窮舉法可視為最簡單的搜索:即是在一個可能存在可行狀態(可行解)的狀態全集中依次遍歷所有的元素,并判定是否為可行狀態。例如,要設計一個“從一堆蘋果中找出藍色的蘋果”這樣的窮舉算法,則定義:

  (1) 狀態全集:一堆蘋果

  (2) 可行狀態:藍色的蘋果

  噢,好,我們現在已經抽取了兩個基本概念,迫不及待要開始窮舉了,但……怎么做呢?窮舉的要害是“依次遍歷”,即做到不重、不漏。呃,我們可以讓聽話的蘋果們排成一行,放在“蘋果數組”中,然后呢,我們就可以按照0號蘋果、1號蘋果、2號蘋果、...、n號蘋果這樣的順序成功遍歷。好,我們解決了遍歷蘋果的問題……慢,我們現在是設計一個算法的抽象模型,假如一切待窮舉的對象都已經活生生地擺在那里,當然有可能把它們全部收集起來并排隊,但假如開始的時候我們并不知道所有要窮舉的對象,比如我們或許要通過一臺安裝在探測飛船內的計算機在全宇宙范圍內窮舉出除地球以外有生命的星球,那么我們的計算機可能是隨著飛船的前行方能不斷地得到新星球的信息,而不是停在地球的時候就獲得全宇宙的星球信息(就算可能,內存或許也裝不下如此大的數據量——哪怕宇宙真的是有限“大”的)。所以我們不應當要求窮舉進行之前就能獲得狀態全集中的所有狀態,這樣一來,我們的“蘋果數組”計劃就宣告流產了?,F在再看看我們激動人心的星球搜索計劃:它并沒有把所有星球收羅排隊,那么它成功的要害在哪里?在于飛船能否以適當的路徑“光顧”完所有的星球;我們把這個條件加強一下:飛船每次到達一個星球,都會看到星球上立著一個方向標,標示下一個星球的方位;而假定這樣的標示保證飛船能夠不重不漏地飛臨宇宙中的所有星球。啊喔……你是不是覺得我這是在異想天開?哦,沒關系,大不了我們不搜索星球了,而除此之外的很多現實窮舉問題都可以滿足這個加強條件。嗯,我承認本文討論的是滿足這個加強條件的稍稍狹義的窮舉:它必須保證在知道一個狀態的前提下能獲得一個新狀態,并且這樣的“狀態鏈”剛好能遍歷整個狀態全集。我們稱從當前狀態獲得并轉到下一個狀態的過程為“狀態跳轉”(我是想用“狀態轉移”的,嗨,可惜它會與動態規劃算法的術語相混淆):
 
  (3)狀態跳轉:根據當前得到的蘋果,按一定的“遍歷算法”取得下一個蘋果;這個算法保證不重不漏地取遍蘋果堆中的所有蘋果,只要所取的第一個蘋果也是按算法定義給出的

  很顯然,對于不同的窮舉任務,都會有不同的遍歷算法,所以這樣一來我們就得將其實現下放給調用我們“窮舉算法庫”的用戶們了。不過考慮到這的確是由于問題的多樣性所決定的,因而這個要求應當是合理的。

  嗯啊,現在我們已經有了蘋果源,目標蘋果,乃至遍歷蘋果的方案(用戶提供),接下來還差一個判定標準,這個倒簡單了:

  (4) 判定標準:當前蘋果是否為藍色的蘋果

  下一步,我們就可以考慮“the classof窮舉算法”的具體實現了。我們把這個class的名字定義為WalkThrough.

  首先,讓我們注重到,“狀態”是一個很重要的概念:不同的窮舉問題都有彼此不同的狀態,在蘋果問題中,“狀態”是蘋果,它包含了蘋果顏色或者更多的信息;而在星球搜索計劃中,“狀態”則是星球,它可能包含該星球的體積、平均密度、溫度、是否有大氣、是否探測到水、星表活動狀況等一系列豐富得驚人的信息。因此,不同狀態(state)對應不同的數據類型,要讓WalkThrough能處理它們,有必要使用模板,于是我們的最初定義如下:

  template

  class WalkThrough

  ;

  萬事開頭難,但現在我們顯然已經開了一個不錯的頭,嗯,繼續。在考慮具體實現之前,先幻想一下我們的WalkThrough能為用戶提供怎樣的服務——當然,它的本職工作是找到并返回可行狀態,因此它似乎應該有一個類似于getFilter()的成員函數。問題是,假如可行狀態不止一個時,getFilter()應當返回一個可行狀態還是所有的可行狀態?不難想象,返回所有可行狀態的作法并不太現實,因為:1.有時候用戶只需要一個,或者少數幾個可行狀態,此時把所有的可行狀態都窮舉出來顯然是低效而不必要的;2.甚至,有些問題的可行狀態數量是無限的,如窮舉素數,此時返回所有狀態當然不可能。同時考慮到用戶要求的仍有可能是不止一個可行解,我們現在知道,應當提供一個getNextFilter()而不是getFilter()的函數:第一次調用它時,將返回從初始狀態開始,依序遍歷到的第一個可行狀態;而此后的調用都將以上次調用為起點繼續向前遍歷,返回下一個可行狀態。需要注重的是,假如已經遍歷完了狀態全集,顯然再調用此函數是沒有意義的,所以它應當返回一個標志,反饋給用戶是否遍歷已經完成。我們將這個函數定義為bool,假如調用有效,則返回true,反之假如已經完成遍歷,則返回false.顯然,我們相應需要一個私有的State對象變量curState,它用于存儲當前的狀態值。

  我們是否應當給getNextFilter加上一個State引用參數,以向用戶返回每次窮舉到的可行狀態?在這里我們并沒有這樣做。試想,可能用戶會想獲得第5個遍歷到的可行狀態,那么他當然就要調用5次getNextFilter(),但前4次他并不要求得到所搜索到的可行狀態。所以,我們將“找到下一個可行狀態”與“獲得當前找到的可行狀態”分離開來,新增加一個getState()成員函數,它返回一個State對象,注重到getState()操作并不影響WalkThrough對象的內部狀態,所以它同時應被聲明為const成員函數。

  相應地,我們需要一個setState()成員函數,它用于改變當前的狀態值,例如設置初始狀態的時候都有可能用到。它帶一個constState&類型的參數,用以指定所要設定的State值,由于State可能是一個較大的類型,所以使用引用傳遞能保證效率,同時加上const限制則保證該函數不會更改所傳入的引用對象本身的值。

  同時用戶可能需要知道,對于一個窮舉對象,是否已經完成窮舉,當然他可以調用getNextFilter()并檢查返回值,但假如遍歷沒有完成,則getNextFilter()除了最后返回true之外還會額外地進行搜索,并將當前狀態改為下一個可行狀態,這份額外的工作可能并不是用戶所期望需要的。因此我們將增加一個成員函數isOver(),它不帶參數,返回一個bool值:假如已經完成遍歷,返回true,反之返回false.相應地,我們需要一個私有bool變量overFlag,它用于存儲isOver()所需要的狀態值。

  至此,WalkThrough的定義如下:

  template

  class WalkThrough

  public:

  void setState(const State& s) curState = s;

  State getState() const return curState;

  bool getNextFilter();

  bool isOver() const return overFlag;

  PRivate:

  State curState;

  bool overFlag;

  ;

  我們把構造函數與析構函數置后,先考慮起要害作用的getNextFilter()的實現。首先,getNextFileter()由當前的狀態跳轉為下一狀態,然后判定新狀態是否為可行,若可行,則停止跳轉并返回true,否則繼續跳轉,重復上述步驟。另一方面,假如已經完成了遍歷而還沒有找到可行狀態,則將overFlag設為false并且返回false.

  我們將跳轉操作、判定是否為可行狀態操作下放給用戶實現:用戶相應提供兩個函數,然后向WalkThrough對象傳入函數指針,供getNextFileter()調用。那么這兩個函數應該采用什么樣的接口形式比較合適呢?先看看跳轉函數,一個很直觀的實現是傳入一個State對象(或其const引用),然后返回“下一個”State對象,不過至少在返回的時候,值傳遞會產生State對象的復制操作(諸如NRV優化之類的語言標準外的特定編譯器實現不在討論之列),當State對象比較大的時候,開銷是不值得的。我們應當考慮傳入State對象的引用,然后“全權委托”跳轉函數進行直接修改——把它“變”成下一個狀態??赡軙腥速|疑這樣做是否違反了封裝原則,但即使摒棄效率方面的權衡,這樣做也是合情合理的。跳轉函數——不妨視為負責“狀態轉化”函數,就像一個煉丹爐——有權利、甚至有義務這樣做,它的職責是“轉化狀態”而非“獲得狀態”。唔……我都覺得自己在語言上過于細究了。嗯,除了轉化狀態,跳轉函數在發現遍歷完成之后也應當及時告知調用它的getNextFilter(),否則下放了大部分權力的getNextFilter()是無從知曉的。于是我們的跳轉函數接口為:接受一個State的引用,返回一個bool值。假如遍歷沒有完成,那么函數執行完畢之后State引用將變為它的后繼狀態,且函數返回true;否則State不變,函數返回false.

  判定是否為可行狀態的函數接口則很好定義了:它接受一個constState型引用作為待判定的狀態,返回bool值,其中true表示該狀態為可行狀態,false表示該狀態不是可行狀態。

  我們將跳轉函數以及判定函數的函數指針類型分別定義為StateJumper及Matcher,由于用戶可能也會用到這些函數指針類型,我們將定義加到public域中:

  public:

  typedef bool (*StateJumper)(State&);

  typedef bool (*Matcher)(const State&);

  // others...

  并且,在private域中相應加上StateJumper和Matcher的函數指針變量,存儲用戶提供的相應函數的地址:

  private:

  // others...

  StateJumper Jumper;

  Matcher IsMatch;

  相應地,內聯定義公有成員函數:

  void setJumper(const StateJumper j) Jumper = j;

  void setMatcher(const Matcher m) IsMatch = m;

  分別用于設置Jumper和IsMatch的函數指針值。
 
  現在所有的成員變量都已經浮出水面,我們可以定義構造函數和析夠函數了,我們不打算對WalkThrough的創建與繼續等方面作限制,因此它們都加在public域中。先看構造函數,有必要定義一個默認構造函數:

  WalkThrough(): overFlag(false), Jumper(0), IsMatch(0)

  這個構造函數不指定任何初始條件,包括當前狀態。可以在需要的時候使用一系列的set成員函數定義。

  接下來定義一個“全功能”的構造函數:

  WalkThrough(const State& s, StateJumper j = 0, Matcher m=0)

  : overFlag(false), curState(s), Jumper(j), IsMatch(m)

  除了overFlag外,所有的屬性都可以在這個構造函數中設定(當然,它答應缺省值)。由于沒有進行任何窮舉操作,將overFlag強制為false是合理的。

  對于拷貝構造函數,由于我們這里沒有涉及內存分配,沒有“深拷貝”的需求,因此不作定義,使用默認的位拷貝可以有不錯的效率。類似地,析構函數也沒有什么事務需要處理,不過考慮到這個WalkThrough可能用于繼續,且有可能出現delete基類指針來刪除派生對象的情況,便定義一個空的虛析構函數,以免引起錯誤:

  virtual ~WalkThrough()

  最后,我們來實現唯一的一個非內聯函數:getNextFilter(),在給出實現之前順便給出完整的WalkThrough的定義:

  template

  class WalkThrough

  public:

  typedef bool (*StateJumper)(State&);

  typedef bool (*Matcher)(const State&);

  WalkThrough(): overFlag(false), Jumper(0), IsMatch(0)

  WalkThrough(const State& s, StateJumper j = 0, Matcher m=0)

  : overFlag(false), curState(s), Jumper(j), IsMatch(m)

  virtual ~WalkThrough()

  void setJumper(const StateJumper j) Jumper = j;

  void setMatcher(const Matcher m) IsMatch = m;

  void setState(const State& s) curState = s;

  State getState() const return curState;

  bool getNextFilter();

  bool isOver() const return overFlag;

  private:

  State curState;

  bool overFlag;

  StateJumper Jumper;

  Matcher IsMatch;

  ;

  template

  bool WalkThrough::getNextFilter()

  if (overFlag) // 若已完成遍歷,則直接返回

  return false;

  if (!Jumper!IsMatch) // 若用戶未定義Jumper或IsMatch函數,則返回

  overFlag = true; // 這里將沒有定義Jumper或IsMatch的窮舉視為遍歷完成

  return false; // 不過,假如你認為兩者絕不能等同,也可以拋出異常

  while (!(overFlag = !Jumper(curState))&&!IsMatch(curState))

  ; // 獲取下一狀態,直到找到可行狀態或者遍歷完成

  if (overFlag) // 根據遍歷完成情況決定返回值

  return false;

  else

  return true;熱門推薦: 如何迅速成為java高手
 


  呼……小功告成,總算可以小松一口氣。不過假如到此就關閉計算機,就像直播球賽進行到高潮時然停電一樣,太令人不爽。——弄了這么久,總該給個example試一試,來點成就感吧?

  沒問題。蘋果問題太簡單,于是我們選擇的是一個傳說中的“和是50”超級難題,什么是“和是50”超級難題?請聽好了:所謂“和是50”超級難題,就是在0到99的整數組成的一個加法算式中,找出和是50的算式,考慮加數順序,像0+50啊,1+49啊,……,49+1啊,50+0啊……(喂,我知道你可能想殺人,但請不要讓與之匹配的眼光朝著我……)對于這樣一個超級難題,我們經過研究,決定采用……窮舉法來實現(哪來的這么多西紅柿?)。

  首先我們研究這個問題的狀態是什么——兩個加數,不是么,它們是有順序的,并且可以取0到99的整數。于是乎,我們定義狀態類如下(注:STL中有類似的東西,但作為完整的示例,我們親自手工打造):

  class Pair

  public:

  Pair(int mx = 0, int my = 0): x(mx), y(my)

  int x, y;

  ;

  Pair雖然屬于世界上最深奧的class之一,然而憑各位的聰明,我就不用多解釋了。接著我們來看一下跳轉函數的實現——當然,它應當符合WalkThrough中定義的StateJumper類型:

  bool counter(Pair& pair)

  if (pair.y < 99)

  ++pair.y;

  else

  if (pair.x < 99)

  ++pair.x;

  pair.y = 0;

  else

  return false;

  return true;

  counter的作用是試圖將pair.y增1,但假如pair.y已經到達上限99,則將pair.x加1,同時pair.y置0繼續下一輪;但假如pair.x也到了99,那么,噢,說明遍歷結束了?!牛@樣的確是一種有效的遍歷方式,對吧。它實際上類似于下面的二重循環:

  for (; pair.x <= 99; ++pair.x)

  for (pair.y = 0; pair.y <= 99; ++pair.y)

  ...

  只不過循環要求在循環體內解決一切問題,而我們“解決一切問題”的重任擔在了另一個函數里,counter不能越權,因而不能這樣直接使用循環。

  接下來是判定是否為有效狀態的函數,它最簡單了:

  bool match(const Pair& pair)

  return (pair.x + pair.y) == 50;

  萬事俱備,可以寫main:

  int main()

  WalkThrough

  sf50(Pair(0, -1), &counter, &match);

  while (sf50.getNextFilter())

  cout << sf50.getState().x << +

  << sf50.getState().y << = 50 << endl;

  return 0;

  請一定要注重,getNextFilter()中有一個Next,也就是說,假如把狀態全集中的第一個狀態作為構造的參數,——本題中就是Pair(0,0)——是可行狀態的話,getNextFilter()將不會判定到,它直接判定“下一個”狀態,就是Pair(0,1)去了。為了讓它能夠完整地判定,我們不能讓Pair(0,0)作為第一個狀態,而,是作為首次運行getNextFilter()后到達的狀態。于是,在構造初始狀態時我們就要用“前”一個狀態Pair(0,-1)作為構造參數(雖然它根本不在本題的狀態全集中),以使得getNextFilter()從Pair(0,0)開始判定。

  嗯,好,在運行之前不要忘了#include ,不要忘了打開std名字空間(usingnamespacestd;),不要忘了粘上/包含上前面的WalkThrough的完整定義……編譯,運行,好,這個超級難題被我們解決了。我的編譯平臺是Redhatlinux9, GNU C++ 3.2.2.

  最后我們分析小結一下經過封裝之后的窮舉算法。首先看效率——是啦,我知道這個“和是50”超級難題所用的算法很糟糕——我是指在相同算法的前提下,使用我們封裝好的窮舉類進行實現時的效率損失:初始化以及一系列set/get函數基本不影響效率,主開銷源自于getNextFilter()本身的調用及getNextFilter對兩個用戶提供函數的調用所產生的額外開銷。試想假如用相同的算法,但僅用兩層for來做窮舉,則少了上萬次的函數入棧、清棧操作。窮舉法本身就不是一個高效的搜索思想,所以對內層代碼的效率變化是最敏感的,這一點是我們所封裝的窮舉類的最大缺點。

  不過總算還是有一點好處,比如,封裝之后,就將設計窮舉算法分解成了相對獨立的設計狀態類、狀態跳轉函數、狀態判定函數三個部分,三者的耦合度相對降低。此外,這個設計的過程多少讓我們又多了解了一點點基于對象編程的抽象能力,嗯,等等。噢……或許這是一個學術價值大于實用價值的設計吧,呵呵?! ∽髡撸撼绦騿T之家

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产日韩精品在线| 亚洲国产一区二区三区四区| 91午夜理伦私人影院| 亚洲在线观看视频| 欧美日韩亚洲精品内裤| 国产97在线|日韩| 国产精品99久久久久久久久| 在线播放日韩精品| 亚洲精品aⅴ中文字幕乱码| 亚洲精品中文字幕女同| 久久理论片午夜琪琪电影网| 欧美韩日一区二区| 久久久91精品国产一区不卡| 韩国欧美亚洲国产| 久久精品视频导航| 国产精品福利观看| 亚洲成年网站在线观看| 国产精品高清在线| 欧美老女人www| 亚洲人成电影网| 中文字幕欧美视频在线| 欧美激情国产高清| 中文字幕日韩av综合精品| 日韩精品丝袜在线| 久久久亚洲欧洲日产国码aⅴ| 超碰日本道色综合久久综合| 欧美日韩电影在线观看| 欧美xxxx综合视频| 欧美国产日韩一区二区在线观看| 亚洲国产精品大全| 欧美激情精品久久久久久免费印度| 国产亚洲精品综合一区91| 91亚洲精品视频| 国产午夜精品免费一区二区三区| 国产噜噜噜噜噜久久久久久久久| 欧美日韩国产中文精品字幕自在自线| 欧美巨猛xxxx猛交黑人97人| 亚洲欧美日韩久久久久久| 国产精品r级在线| 97色伦亚洲国产| 亚洲va欧美va国产综合久久| 日韩在线观看你懂的| 久久亚洲国产成人| 岛国精品视频在线播放| 91福利视频在线观看| 亚洲福利视频在线| 美日韩精品免费视频| 国产精品爽爽ⅴa在线观看| 国产成人精品综合久久久| 欧美猛男性生活免费| 国产福利精品av综合导导航| 九九热精品在线| 欧美激情视频三区| 免费成人高清视频| 国产成人一区二区三区| y97精品国产97久久久久久| 亚洲国产精品久久久久秋霞不卡| 成人免费在线视频网址| 91久久久久久久久久久久久| 亚洲精品久久久久| 亚洲成人网在线| 国产日韩综合一区二区性色av| 亚洲女人被黑人巨大进入| 久久精品91久久久久久再现| 精品欧美激情精品一区| 亚洲精品97久久| 欧美在线视频一区| 中文字幕在线精品| 色综合久久88| 91精品综合久久久久久五月天| 欧美裸体男粗大视频在线观看| 夜夜躁日日躁狠狠久久88av| 亚洲视频网站在线观看| 久久成人精品一区二区三区| 国产欧美日韩免费| 狠狠色狠狠色综合日日小说| 亚洲日本中文字幕免费在线不卡| 国产精品久久久久久亚洲影视| 国产91精品不卡视频| 成人网欧美在线视频| 日韩在线视频播放| 日韩在线视频免费观看| 国产精品 欧美在线| 青青久久aⅴ北条麻妃| 国产亚洲一区二区在线| 欧美激情xxxx性bbbb| 色偷偷av一区二区三区乱| 欧美成年人视频| 成人av资源在线播放| 91久久夜色精品国产网站| 色爱精品视频一区| 久久免费视频网站| 久久91亚洲精品中文字幕奶水| 亚洲高清一二三区| 久久久精品网站| 国产在线播放不卡| 欧美精品xxx| 日韩av不卡在线| 久久在线免费观看视频| 国产精品免费视频xxxx| 国产精品热视频| 欧美精品手机在线| 欧美国产极速在线| 亚洲成人动漫在线播放| 欧美高清视频在线| 欧美裸体男粗大视频在线观看| 国产亚洲精品久久久久久牛牛| 国产伊人精品在线| 欧美一级视频一区二区| 久久精品视频在线观看| 另类少妇人与禽zozz0性伦| 国产午夜精品全部视频播放| 日本精品中文字幕| 91系列在线播放| 日韩a**中文字幕| 91欧美精品成人综合在线观看| 日韩av日韩在线观看| 91香蕉嫩草影院入口| 欧美诱惑福利视频| 久久精品久久久久电影| 久久久免费电影| 亲爱的老师9免费观看全集电视剧| 青青久久av北条麻妃黑人| 亚洲精品女av网站| 青草青草久热精品视频在线观看| 亚洲性av在线| 欧美国产乱视频| 日韩精品在线视频美女| 欧美亚洲国产另类| 国产精品视频区1| 国产成人综合av| 成人免费网站在线| 一区二区三区在线播放欧美| 欧美巨乳美女视频| 中文字幕欧美日韩精品| 欧美激情一区二区三区在线视频观看| 麻豆国产精品va在线观看不卡| 国产精品日本精品| 久久久久久久久久久成人| 日韩有码在线视频| 伊人久久男人天堂| 欧美极品少妇全裸体| 高清视频欧美一级| 久热精品视频在线| 国产69精品久久久久9| 国产成人精品亚洲精品| 97在线视频精品| 欧美午夜精品久久久久久人妖| 国产美女久久精品香蕉69| 亚洲精品资源美女情侣酒店| 97视频免费看| 国产日韩欧美成人| 欧美精品videos| 日韩中文字幕视频| 欧美精品电影在线| 国产精品久久精品| 秋霞午夜一区二区| 久久精品福利视频| 国产精品h在线观看| 亚洲视频专区在线| 久久久久女教师免费一区| 一区二区福利视频| 亚洲国产精品小视频| 日韩三级成人av网|