posts - 195, comments - 34, trackbacks - 0, articles - 1


          -------------------start of 1
          板書:
          了解公司
          去公司網(wǎng)站對其了解
          去搜索引擎中了解對公司的評價(jià)
          網(wǎng)上搜公司的人員,和他們聊天來了解
          去IT公司速查手冊查對公司的評價(jià)(如深圳軟媒)
          珠三角求職注意事項(xiàng):深圳、廣州、東莞。。。
          簡歷
          把自己的簡歷以純文本的形式貼在郵件里,同時(shí)以Word文檔(低版本)加在附件中。附件千萬不能有病毒。
          寫簡歷的基本原則:簡歷是向用人單位推薦你、幫助用人單位了解你的一個(gè)工具!它不是公文流程化的表格,不是履歷

          表。不要什么都寫。
          要根據(jù)應(yīng)聘職位的職位描述(Job Description)來個(gè)性化自己的簡歷,不要所有的職位都用一個(gè)簡歷,這是大忌,貌似

          省事,實(shí)則大大降低了成功率。
          簡歷中的項(xiàng)目經(jīng)歷盡量不要寫:圖書管理系統(tǒng)、網(wǎng)上商店之類的,看的頭都疼了,第一反應(yīng)是反感。不能跟別人雷同!

          !!
          簡歷注意事項(xiàng)
          控制在兩頁之內(nèi)
          什么刀槍跟棍棒,都耍得有模有樣,什么兵器最喜歡,雙截棍柔中帶剛。不要C、Java、C#、php、linux都懂!不要就是

          一句:精通java就ok了,你用java寫過什么?做過什么?有什么認(rèn)識?
          盡量壓縮政治面貌、小學(xué)中學(xué)、大學(xué)獲獎(jiǎng)證書、小學(xué)三好學(xué)生等用人單位不關(guān)心的內(nèi)容。
          項(xiàng)目經(jīng)歷不要寫太多,每個(gè)項(xiàng)目控制在5行左右,要重點(diǎn)突出項(xiàng)目人數(shù)、耗時(shí)、功能、系統(tǒng)架構(gòu)等信息。突出:我在項(xiàng)目

          做了什么!我不是打雜的,我不是端茶的。
          如果和同學(xué)一起去應(yīng)聘,不要兩個(gè)人的簡歷一樣
          簡歷注意事項(xiàng)
          可以多突出自己的特色,跟別人區(qū)隔開。簡歷上寫的一定要經(jīng)得起拷問(端茶倒水的),沒把握的不要亂寫,反感。面

          試官一般都會按照就簡歷上寫的進(jìn)行提問:“看到你簡歷上寫的。。。我想問你下你對。。。的看法”。
          簡歷不用弄的太花哨,搞太高檔的紙。面試官看的是內(nèi)容,而不是紙!
          人才庫=垃圾桶

           

          ---------------------end of 1----------------------------------

           

          -------------start of 2--------------
          板書:
          先介紹自己
          禁忌的回答:我都寫到簡歷里邊了;我出生在陜北一個(gè)小山村,我有三個(gè)弟弟,我媽身體不好。。。;
          無關(guān)的事情不要超過十幾秒,因?yàn)楹唵谓榻B自己的時(shí)間最多2分鐘。很快的將重點(diǎn)話題轉(zhuǎn)到與工作有關(guān)的技能和經(jīng)驗(yàn)上來


          首先要把簡歷中寫的總結(jié)一下(概述,不要全盤背出來),然后再以口語化的方式談?wù)勁c這個(gè)工作、軟件開發(fā)相關(guān)的話

          題。
          多主動說,不要總等著考官提問,那樣會被動,但是也不能搶話說。聊天的效果!!和考官處于平等的地位。
          當(dāng)話說完了的時(shí)候要及時(shí)說“這就是我的看法。”,千萬不能與主考官面面相覷。
          說話不用太快,說話要稍微慢于思維,否則說話就不連貫了。

          常見面試問題:
          1、你的優(yōu)點(diǎn)是什么,你的缺點(diǎn)是什么?
          禁忌回答:我的優(yōu)點(diǎn)是沒有缺點(diǎn)(找抽!)
          優(yōu)點(diǎn)要講與工作相關(guān)的,不要說“我籃球打得好”,不要吹的太厲害;談優(yōu)點(diǎn)的時(shí)候不能枚舉形容詞,要舉實(shí)例。
          缺點(diǎn)要是那種可以容忍的或者大家都有的小缺點(diǎn),比如“當(dāng)我注意力集中在工作上的時(shí)候,容易忽略別人說的話”(某

          種程度來講是優(yōu)點(diǎn));“當(dāng)事情比較多的時(shí)候我會忘記一些事項(xiàng),造成工作疏忽”。談缺點(diǎn)的時(shí)候還要提到自己是怎么

          改的“我隨身帶著筆記本,記下要做的事情,這樣容易忘記事情的毛病已經(jīng)改了很多了”

          2、你還有什么問題嗎?
          不能答“沒有了”,這說明你對這個(gè)工作根本沒放在心上,也不能問太敏感的問題。要讓別人感覺你是來做事業(yè)的,而

          不是來找糊口的飯碗。不要問太多的待遇、補(bǔ)助、伙食、幾個(gè)老板、公司銷售額之類的問題,多關(guān)心公司的業(yè)務(wù)、產(chǎn)品

          、發(fā)展以及個(gè)人進(jìn)入公司以后的問題等等。問剩下的半年時(shí)間我該學(xué)些什么?
          你為什么要來我們公司?不要回答“你們公司給的錢多”、“我看到了你們的招聘啟示”。參考回答“我以前就對**有

          了解,**是。。。,所以我一直向往在**工作,也希望能在這個(gè)行業(yè)中與**共同成長。”

          3、你能說一下你的職業(yè)發(fā)展規(guī)劃嗎?
          不要說“2年內(nèi)成為技術(shù)骨干,5年內(nèi)稱為項(xiàng)目經(jīng)理,8年內(nèi)自己創(chuàng)業(yè)。。。”之類的。參考解答“我有非常強(qiáng)的工作能力

          和積極性,而且我相信我一定能夠?yàn)楣緞?chuàng)造越來越多的價(jià)值,從而個(gè)人的能力也得到提升,由于我工作經(jīng)驗(yàn)還是有限

          的,而且對IT行業(yè)的發(fā)展也有待于逐步加深了解,所以我希望在初期能夠服從公司的工作安排,完成公司交給公司的任

          務(wù),相信隨著我經(jīng)驗(yàn)的增長,我會對自己的職業(yè)發(fā)展規(guī)劃更加明確,今后無論是做技術(shù)專家、業(yè)務(wù)專家還是管理人員,

          我都能夠找到適合自己發(fā)展的道路,與公司共同成長!”愿意在你這干一輩子。

          4、你希望的月薪是多少?
          這個(gè)問題很難回答,而且不同的面試官也有不同的喜好。不過總的原則是首先不要就月薪問題進(jìn)行無意義的爭論“你們

          怎么能才給三千呀,我同學(xué)都四千,三千還不夠在北京生存的呢,我還有八十歲的老母。。。”,而是說自己的優(yōu)勢、

          對公司的價(jià)值。“我期望的月薪是四千,不過我知道每個(gè)公司都有自己的薪酬體系結(jié)構(gòu),我也充分尊重公司在考慮我個(gè)

          人能力的基礎(chǔ)上按照公司的規(guī)定給予我的報(bào)酬”。著眼發(fā)展!

           

          1、簡單介紹自己時(shí):姓名專業(yè),然后是專業(yè)相關(guān)的。談到工作上和以口語話的方式聊一聊。


          要主動的和考官聊,要主動的出擊不能應(yīng)對呀。可以互動,這樣效果更好,也可以啟發(fā)自己。聊天的效果最好。不能搶

          話,一定要讓人把話說完。陳述看法。要有個(gè)總結(jié),這就是我的看法。說完了就說完了。一些和人交流的基本功。

          說話不要快。快了容易出錯(cuò)。


          你的優(yōu)點(diǎn)是什么?缺點(diǎn)是什么?講與工作相關(guān)的,不是談別的。這個(gè)才是關(guān)鍵點(diǎn)。

          你還有什么問題嗎?這個(gè)工作要放在心上,對企業(yè)了解嗎?對職業(yè)了解嗎?不能問敏感的問題。要感覺是做事業(yè)的。

          在這半年時(shí)間要學(xué)習(xí)些什么?******

          你為什么來我們公司???以前就公司有所了解,


          對自己的職業(yè)規(guī)劃是不是明確。是不是和公司共同發(fā)展。??為公司創(chuàng)造價(jià)值,對IT行業(yè)的還有待了解,


          與公司共同成長。


          創(chuàng)業(yè)的人不安穩(wěn)。



          板書:

          紙上寫代碼很土嗎?
          面試時(shí)的Code題通常有難度,有些題目就是想看你在遇到困難問題時(shí)候的應(yīng)對能力。不要以答不對而懊惱

          一時(shí)找不到解決方案的話,也不能看著天花板面無表情的發(fā)呆,而是要嘴里描述你的想法和思路。編寫代碼過程中隨時(shí)對寫的代碼進(jìn)行解釋。“我要先創(chuàng)建一個(gè)HashMap,然后。。。”。

          因?yàn)榭赡苁窃诩埳蠈懘a,所以不要拘泥于細(xì)節(jié)。

          遇到一個(gè)小障礙,可以求助主考官,“這個(gè)只要使用String類的一個(gè)分割字符串的方法就可以,不過我忘了這個(gè)方法的名稱。。。”。遇到難題就問。

          如果一個(gè)題只會用最笨的方法解,那么也要寫,并且解釋“這種方式雖然可以實(shí)現(xiàn),不過效率非常低,我雖然想通過發(fā)現(xiàn)其中的規(guī)律來優(yōu)化,不過最終沒有發(fā)現(xiàn),如果我在工作中碰到類似的問題,我會尋求他人幫助”

          怎么樣及時(shí)無法完美的寫的情況下也能的高分。比如我在中軟筆試的時(shí)候考xml操作,那時(shí)候我只用delphi操作過xml,所以我沒有按照題目要求用java寫,而是按delphi寫的,最后注明。。。企業(yè)面試、筆試沒有嚴(yán)格的評分標(biāo)準(zhǔn),不同于高考等考試。

          代碼要考慮邊界條件,這是主考官非常注意的。(得高分的技巧)輸入?yún)?shù)的合法性等等。

          如果是在紙上Coding的話,代碼書寫一定要清晰;無論是機(jī)試還是“紙?jiān)?#8221;,都要適當(dāng)?shù)膶懽⑨專?br /> 機(jī)試由于是在不熟悉的機(jī)器中開發(fā),所以不要慌,必要時(shí)尋求幫忙
          著眼發(fā)展,要有平和的就業(yè)心態(tài)。
          在一個(gè)公司要沉淀一段時(shí)間,不要頻繁跳槽
          工作中80%的時(shí)間是在干無聊的事情,因?yàn)槟愕墓ぷ鞑皇莿?chuàng)新大賽,老板是要你為他產(chǎn)生效益。
          把看似無聊的80%的工作做的Perfect,把20%的時(shí)間用來創(chuàng)造!你就是未來的牛人!
          注意學(xué)習(xí)不能停止!!!

          posted @ 2009-11-20 22:24 小強(qiáng)摩羯座 閱讀(218) | 評論 (0)編輯 收藏

          xml特殊字符
          2008-07-03 14:31
          轉(zhuǎn)義字符
          不合法的XML字符必須被替換為相應(yīng)的實(shí)體。

          如果在XML文檔中使用類似"<" 的字符, 那么解析器將會出現(xiàn)錯(cuò)誤,因?yàn)榻馕銎鲿J(rèn)為這是一個(gè)新元素的開始。所以不應(yīng)該象下面那樣書寫代碼:

          <message>if salary < 1000 then</message>
          為了避免出現(xiàn)這種情況,必須將字符"<" 轉(zhuǎn)換成實(shí)體,象下面這樣:
          <message>if salary &lt; 1000 then</message>
          下面是五個(gè)在XML文檔中預(yù)定義好的實(shí)體:

          &lt; < 小于號
          &gt; > 大于號
          &amp; & 和
          &apos; ' 單引號
          &quot; " 雙引號
          實(shí)體必須以符號"&"開頭,以符號";"結(jié)尾。
          注意: 只有"<" 字符和"&"字符對于XML來說是嚴(yán)格禁止使用的。剩下的都是合法的,為了減少出錯(cuò),使用實(shí)體是一個(gè)好習(xí)慣。

          CDATA部件
          在CDATA內(nèi)部的所有內(nèi)容都會被解析器忽略。

          如果文本包含了很多的"<"字符和"&"字符——就象程序代碼一樣,那么最好把他們都放到CDATA部件中。

          一個(gè) CDATA 部件以"<![CDATA[" 標(biāo)記開始,以"]]>"標(biāo)記結(jié)束:

          <script>
          <![CDATA[
          function matchwo(a,b)
          {
          if (a < b && a < 0) then
          {
          return 1
          }
          else
          {
          return 0
          }
          }
          ]]>
          </script>
          在前面的例子中,所有在CDATA部件之間的文本都會被解析器忽略。

          CDATA注意事項(xiàng):
          CDATA部件之間不能再包含CDATA部件(不能嵌套)。如果CDATA部件包含了字符"]]>" 或者"<![CDATA[" ,將很有可能出錯(cuò)哦。

          同樣要注意在字符串"]]>"之間沒有空格或者換行符。

          posted @ 2009-11-16 00:32 小強(qiáng)摩羯座 閱讀(239) | 評論 (0)編輯 收藏

          MySql數(shù)據(jù)引擎簡介與選擇方法
          2009-04-18 16:17

          一、數(shù)據(jù)引擎簡介

          MySQL 5.1中,MySQL AB引入了新的插件式存儲引擎體系結(jié)構(gòu),允許將存儲引擎加載到正在運(yùn)新的MySQL服務(wù)器中。

          使用MySQL插件式存儲引擎體系結(jié)構(gòu),允許數(shù)據(jù)庫專 業(yè)人員為特定的應(yīng)用需求選擇專門的存儲引擎,完全不需要管理任何特殊的應(yīng)用編碼要求。采用MySQL服務(wù)器體系結(jié)構(gòu),由于在存儲級別上提供了一致和簡單的 應(yīng)用模型和API,應(yīng)用程序編程人員和DBA可不再考慮所有的底層實(shí)施細(xì)節(jié)。因此,盡管不同的存儲引擎具有不同的能力,應(yīng)用程序是與之分離的。

          MySQL支持?jǐn)?shù)個(gè)存儲引擎作為對不同表的類型的處理器。MySQL存儲引擎包括處理事務(wù)安全表的引擎和處理非事務(wù)安全表的引擎:

          ·         MyISAM管理非事務(wù)表。它提供高速存儲和檢索,以及全文搜索能力。MyISAM在所有MySQL配置里被支持,它是默認(rèn)的存儲引擎,除非你配置MySQL默認(rèn)使用另外一個(gè)引擎。

          ·         MEMORY存儲引擎提供“內(nèi)存中”表。MERGE存儲引擎允許集合將被處理同樣的MyISAM表作為一個(gè)單獨(dú)的表。就像MyISAM一樣,MEMORY和MERGE存儲引擎處理非事務(wù)表,這兩個(gè)引擎也都被默認(rèn)包含在MySQL中。

          注釋:MEMORY存儲引擎正式地被確定為HEAP引擎。

          ·         InnoDB和BDB存儲引擎提供事務(wù)安全表。BDB被包含在為支持它的操作系統(tǒng)發(fā)布的MySQL-Max二進(jìn)制分發(fā)版里。InnoDB也默認(rèn)被包括在所有MySQL 5.1二進(jìn)制分發(fā)版里,你可以按照喜好通過配置MySQL來允許或禁止任一引擎。

          ·         EXAMPLE存儲引擎是一個(gè)“存根”引擎,它不做什么。你可以用這個(gè)引擎創(chuàng)建表,但沒有數(shù)據(jù)被存儲于其中或從其中檢索。這個(gè)引擎的目的是服務(wù),在MySQL源代碼中的一個(gè)例子,它演示說明如何開始編寫新存儲引擎。同樣,它的主要興趣是對開發(fā)者。

          ·         NDB Cluster是被MySQL Cluster用來實(shí)現(xiàn)分割到多臺計(jì)算機(jī)上的表的存儲引擎。它在MySQL-Max 5.1二進(jìn)制分發(fā)版里提供。這個(gè)存儲引擎當(dāng)前只被Linux, Solaris, 和Mac OS X 支持。在未來的MySQL分發(fā)版中,我們想要添加其它平臺對這個(gè)引擎的支持,包括Windows。

          ·         ARCHIVE存儲引擎被用來無索引地,非常小地覆蓋存儲的大量數(shù)據(jù)。

          ·         CSV存儲引擎把數(shù)據(jù)以逗號分隔的格式存儲在文本文件中。

          ·         BLACKHOLE存儲引擎接受但不存儲數(shù)據(jù),并且檢索總是返回一個(gè)空集。

          ·         FEDERATED存儲引擎把數(shù)據(jù)存在遠(yuǎn)程數(shù)據(jù)庫中。在MySQL 5.1中,它只和MySQL一起工作,使用MySQL C Client API。在未來的分發(fā)版中,我們想要讓它使用其它驅(qū)動器或客戶端連接方法連接到另外的數(shù)據(jù)源。

          插件式存儲引擎體系結(jié)構(gòu)提供了標(biāo)準(zhǔn)的管理和支持服務(wù)集合,它們對所有的基本存儲引擎來說是共同的。存儲引擎本身是數(shù)據(jù)庫服務(wù)器的組件,負(fù)責(zé)對在物理服務(wù)器層面上維護(hù)的基本數(shù)據(jù)進(jìn)行實(shí)際操作。

          這是一種高效的模塊化體系結(jié)構(gòu),它為那些希望專注于特定應(yīng)用需求的人員提供了巨大的便利和益處,這類特殊應(yīng)用需求包括數(shù)據(jù)倉儲、事務(wù)處理、高可用性情形等,同時(shí)還能利用獨(dú)立于任何存儲引擎的一組接口和服務(wù)。

          應(yīng)用程序編程人員和DBA通過位于存儲引擎之上的連接器API和服務(wù)層來處理MySQL數(shù)據(jù)庫。如果 應(yīng)用程序的變化需要改變底層存儲引擎,或需要增加1個(gè)或多個(gè)額外的存儲引擎以支持新的需求,不需要進(jìn)行大的編碼或進(jìn)程更改就能實(shí)現(xiàn)這類要求。MySQL服 務(wù)器體系結(jié)構(gòu)提供了一致和易于使用的API,這類API適用于多種存儲引擎,通過該方式,該結(jié)構(gòu)將應(yīng)用程序與存儲引擎的底層復(fù)雜性隔離開來。

          在下圖中,以圖形方式介紹了MySQL插件式存儲引擎體系結(jié)構(gòu):
          The MySQL pluggable storage enginearchitecture

          二、選擇存儲引擎

          與MySQL一起提供的各種存儲引擎在設(shè)計(jì)時(shí)考慮了不同的使用情況。為了更有效地使用插件式存儲體系結(jié)構(gòu),最好了解各種存儲引擎的優(yōu)點(diǎn)和缺點(diǎn)。

          在下面的表格中,概要介紹了與MySQL一起提供的存儲引擎:

          Storage engine comparison

          下述存儲引擎是最常用的:

          ·         MyISAM:默認(rèn)的MySQL插件式存儲引擎,它是在Web、數(shù)據(jù)倉儲和其他應(yīng)用環(huán)境下最常使用的存儲引擎之一。注意,通過更改STORAGE_ENGINE配置變量,能夠方便地更改MySQL服務(wù)器的默認(rèn)存儲引擎。

          ·         InnoDB:用于事務(wù)處理應(yīng)用程序,具有眾多特性,包括ACID事務(wù)支持。

          ·         BDB:可替代InnoDB的事務(wù)引擎,支持COMMIT、ROLLBACK和其他事務(wù)特性。

          ·         Memory:將所有數(shù)據(jù)保存在RAM中,在需要快速查找引用和其他類似數(shù)據(jù)的環(huán)境下,可提供極快的訪問。

          ·         Merge:允許MySQL DBA或開發(fā)人員將一系列等同的MyISAM表以邏輯方式組合在一起,并作為1個(gè)對象引用它們。對于諸如數(shù)據(jù)倉儲等VLDB環(huán)境十分適合。

          ·         Archive:為大量很少引用的歷史、歸檔、或安全審計(jì)信息的存儲和檢索提供了完美的解決方案。

          ·         Federated:能夠?qū)⒍鄠€(gè)分離的MySQL服務(wù)器鏈接起來,從多個(gè)物理服務(wù)器創(chuàng)建一個(gè)邏輯數(shù)據(jù)庫。十分適合于分布式環(huán)境或數(shù)據(jù)集市環(huán)境。

          ·         Cluster/NDB:MySQL的簇式數(shù)據(jù)庫引擎,尤其適合于具有高性能查找要求的應(yīng)用程序,這類查找需求還要求具有最高的正常工作時(shí)間和可用性。

          ·         Other:其他存儲引擎包括CSV(引用由逗號隔開的用作數(shù)據(jù)庫表的文件),Blackhole(用于臨時(shí)禁止對數(shù)據(jù)庫的應(yīng)用程序輸入),以及Example引擎(可為快速創(chuàng)建定制的插件式存儲引擎提供幫助)。

          請記住,對于整個(gè)服務(wù)器或方案,你并不一定要使用相同的存儲引擎,你可以為方案中的每個(gè)表使用不同的存儲引擎,這點(diǎn)很重要。

          三、將存儲引擎指定給表

          可以在創(chuàng)建新表時(shí)指定存儲引擎,或通過使用ALTER TABLE語句指定存儲引擎。

          要想在創(chuàng)建表時(shí)指定存儲引擎,可使用ENGINE參數(shù):

          CREATE TABLE engineTest(
          id INT
          ) ENGINE = MyISAM;
          也可以使用TYPE選項(xiàng)到CREATE TABLE語句來告訴MySQL你要?jiǎng)?chuàng)建什么類型的表。
          CREATE TABLE engineTest(
          id INT
          ) TYPE = MyISAM;
          雖然TYPE仍然在MySQL 5.1中被支持,現(xiàn)在ENGINE是首選的術(shù)語。
          如果你省略掉ENGINE或TYPE選項(xiàng),默認(rèn)的存儲引擎被使用。一般的默認(rèn)是MyISAM,但 你可以用--default-storage-engine或--default-table-type服務(wù)器啟動選項(xiàng)來改變它,或者通過設(shè)置 storage_engine或table_type系統(tǒng)變量來改變。

          要想更改已有表的存儲引擎,可使用ALTER TABLE語句:

          ALTER TABLEengineTestENGINE =ARCHIVE;
          ALTER TABLE t ENGINE = MYISAM;
          ALTER TABLE t TYPE = BDB;
          如果你試著使用一個(gè)未被編譯進(jìn)MySQL的存儲引擎,或者試著用一個(gè)被編譯進(jìn)MySQL但沒有被 激活的存儲引擎,MySQL取而代之地創(chuàng)建一個(gè)MyISAM類型的表。當(dāng)你在支持不同存儲引擎的MySQL服務(wù)器之間拷貝表的時(shí)候,上述的行為是很方便 的。(例如,在一個(gè)復(fù)制建立中,可能你的主服務(wù)器為增加安全而支持事務(wù)存儲引擎,但從服務(wù)器為更快的速度而僅使用非事務(wù)存儲引擎。)
          在不可用的類型被指定時(shí),自動用MyISAM表來替代,這會對MySQL的新用戶造成混淆。無論何時(shí)一個(gè)表被自動改變之時(shí),產(chǎn)生一個(gè)警告。
          MySQL總是創(chuàng)建一個(gè).frm文件來保持表和列的定義。表的索引和數(shù)據(jù)可能被存儲在一個(gè)或多個(gè)文件里,這取決于表的類型。服務(wù)器在存儲引擎級別之上創(chuàng)建.frm文件。單獨(dú)的存儲引擎創(chuàng)建任何需要用來管理表的額外文件。
          一個(gè)數(shù)據(jù)庫可以包含不同類型的表。
          四、存儲引擎和事務(wù)
          下述存儲引擎支持事務(wù):

          ·         InnoDB:通過MVCC支持事務(wù),允許COMMIT、ROLLBACK和保存點(diǎn)。

          ·         NDB:通過MVCC支持事務(wù),允許COMMIT和ROLLBACK。

          ·         BDB:支持事務(wù),允許COMMIT和ROLLBACK。

          事務(wù)安全表(TST) 比起非事務(wù)安全表 (NTST)有幾大優(yōu)勢:

          ·         更安全。即使MySQL崩潰或遇到硬件問題,要么自動恢復(fù),要么從備份加事務(wù)日志恢復(fù),你可以取回?cái)?shù)據(jù)。

          ·         你可以合并許多語句,并用COMMIT語句同時(shí)接受它們?nèi)浚ㄈ绻鸻utocommit被禁止掉)。

          ·         你可以執(zhí)行ROLLBACK來忽略你的改變(如果autocommit被禁止掉)。

          ·         如果更新失敗,你的所有改變都變回原來。(用非事務(wù)安全表,所有發(fā)生的改變都是永久的)。

          ·         事務(wù)安全存儲引擎可以給那些當(dāng)前用讀得到許多更新的表提供更好的部署。

          非事務(wù)安全表自身有幾個(gè)優(yōu)點(diǎn),因?yàn)闆]有事務(wù)開支,所有優(yōu)點(diǎn)都能出現(xiàn):

          ·         更快

          ·         需要更少的磁盤空間

          ·         執(zhí)行更新需要更少的內(nèi)存

          你可以在同一個(gè)語句中合并事務(wù)安全和非事務(wù)安全表來獲得兩者最好的情況。盡管如此,在autocommit被禁止掉的事務(wù)里,變換到非事務(wù)安全表依舊即時(shí)提交,并且不會被回滾。

          雖然MySQL支持?jǐn)?shù)個(gè)事務(wù)安全存儲引擎,為獲得最好結(jié)果,你不應(yīng)該在一個(gè)事務(wù)那混合不同表類型。如果你混合表類型會發(fā)生問題,

          五、插入搜索引擎

          能夠使用存儲引擎之前,必須使用INSTALL PLUGIN語句將存儲引擎plugin(插件)裝載到mysql。例如,要想加載example引擎,首先應(yīng)加載ha_example.so模塊:

          INSTALL PLUGINha_exampleSONAME 'ha_example.so';

          文件.so必須位于MySQL服務(wù)器庫目錄下(典型情況下是installdir/lib)。

          六、拔出存儲引擎

          要想拔出存儲引擎,可使用UNINSTALL PLUGIN語句:

          UNINSTALL PLUGINha_example;

          如果拔出了正被已有表使用的存儲引擎,這些表將成為不可訪問的。拔出存儲引擎之前,請確保沒有任何表使用該存儲引擎。

          為了安裝插件式存儲引擎,plugin文件必須位于恰當(dāng)?shù)腗ySQL庫目錄下,而且發(fā)出INSTALL PLUGIN語句的用戶必須具有SUPER權(quán)限。

           

          創(chuàng)建table時(shí)可以通過engine關(guān)鍵字指定使用的存儲引擎,如果省略則使用系統(tǒng)默認(rèn)的存儲引擎:

          CREATE TABLE t (i INT) ENGINE = MYISAM;

          查看系統(tǒng)中支持的存儲引擎類型:

          mysql> show engines;

          標(biāo)準(zhǔn)安裝程序中只提供部分引擎的支持,如果需要使用其他的存儲引擎,需要使用源代碼加不同的參數(shù)重新編譯。其中DEFAULT表明系統(tǒng)的默認(rèn)存儲引擎,可以通過修改配置參數(shù)來變更:

          default-storage-engine=MyISAM

          查看某個(gè)存儲引擎的具體信息

          mysql> show engine InnoDB status\G;

          posted @ 2009-11-16 00:31 小強(qiáng)摩羯座 閱讀(372) | 評論 (0)編輯 收藏



          如果我們把二叉樹看成一個(gè)圖,父子節(jié)點(diǎn)之間的連線看成是雙向的,我們姑且定義“距
          離”為兩個(gè)節(jié)點(diǎn)之間邊的個(gè)數(shù)。
          寫一個(gè)程序求一棵二叉樹中相距最遠(yuǎn)的兩個(gè)節(jié)點(diǎn)之間的距離。
          如圖 3-11 所示,粗箭頭的邊表示最長距離:
          圖 3-11
          樹中相距最遠(yuǎn)的兩個(gè)節(jié)點(diǎn) A,B

          寫書評,贏取《編程之美——微軟技術(shù)面試心得》www.ieee.org.cn/BCZM.asp
          分析與解法
          我們先畫幾個(gè)不同形狀的二叉樹,(如圖 3-12 所示),看看能否得到一些啟示。
          圖 3-12
          幾個(gè)例子
          從例子中可以看出,相距最遠(yuǎn)的兩個(gè)節(jié)點(diǎn),一定是兩個(gè)葉子節(jié)點(diǎn),或者是一個(gè)葉子節(jié)點(diǎn)
          到它的根節(jié)點(diǎn)。(為什么?)
          【解法一】
          根據(jù)相距最遠(yuǎn)的兩個(gè)節(jié)點(diǎn)一定是葉子節(jié)點(diǎn)這個(gè)規(guī)律,我們可以進(jìn)一步討論。
          對于任意一個(gè)節(jié)點(diǎn),以該節(jié)點(diǎn)為根,假設(shè)這個(gè)根有 K 個(gè)孩子節(jié)點(diǎn),那么相距最遠(yuǎn)的兩
          個(gè)節(jié)點(diǎn) U 和 V 之間的路徑與這個(gè)根節(jié)點(diǎn)的關(guān)系有兩種情況:
          1. 若路徑經(jīng)過根Root,則U和V是屬于不同子樹的,且它們都是該子樹中到根節(jié)點(diǎn)最遠(yuǎn)
          的節(jié)點(diǎn),否則跟它們的距離最遠(yuǎn)相矛盾。這種情況如圖3-13所示:
          圖 3-13
          相距最遠(yuǎn)的節(jié)點(diǎn)在左右最長的子樹中

          寫書評,贏取《編程之美——微軟技術(shù)面試心得》www.ieee.org.cn/BCZM.asp
          2. 如果路徑不經(jīng)過Root,那么它們一定屬于根的K個(gè)子樹之一。并且它們也是該子樹中
          相距最遠(yuǎn)的兩個(gè)頂點(diǎn)。如圖3-14中的節(jié)點(diǎn)A:
          圖 3-14
          相距最遠(yuǎn)的節(jié)點(diǎn)在某個(gè)子樹下
          因此,問題就可以轉(zhuǎn)化為在子樹上的解,從而能夠利用動態(tài)規(guī)劃來解決。
          設(shè)第 K 棵子樹中相距最遠(yuǎn)的兩個(gè)節(jié)點(diǎn):Uk 和 Vk,其距離定義為 d(Uk, Vk),那么節(jié)點(diǎn)
          Uk 或 Vk 即為子樹 K 到根節(jié)點(diǎn) Rk 距離最長的節(jié)點(diǎn)。不失一般性,我們設(shè) Uk 為子樹 K 中到根
          節(jié)點(diǎn) Rk 距離最長的節(jié)點(diǎn),其到根節(jié)點(diǎn)的距離定義為 d(Uk, R)。取 d(Ui, R)(1≤i≤k)中
          最大的兩個(gè)值 max1 和 max2,那么經(jīng)過根節(jié)點(diǎn) R 的最長路徑為 max1+max2+2,所以樹 R 中
          相距最遠(yuǎn)的兩個(gè)點(diǎn)的距離為:max{d(U1, V1), …, d(Uk, Vk),max1+max2+2}。
          采用深度優(yōu)先搜索如圖 3-15,只需要遍歷所有的節(jié)點(diǎn)一次,時(shí)間復(fù)雜度為 O(|E|)= O
          (|V|-1),其中 V 為點(diǎn)的集合,E 為邊的集合。
          圖 3-15
          深度遍歷示意圖
          示例代碼如下,我們使用二叉樹來實(shí)現(xiàn)該算法。
          代碼清單 3-11
          // 數(shù)據(jù)結(jié)構(gòu)定義

          寫書評,贏取《編程之美——微軟技術(shù)面試心得》www.ieee.org.cn/BCZM.asp
          struct NODE
          {
              NODE* pLeft;
              NODE* pRight;
              int nMaxLeft;
              int nMaxRight;
              char chValue;
          };
          int nMaxLen = 0;
          // 尋找樹中最長的兩段距離
          void FindMaxLen(NODE* pRoot)
          {
              // 遍歷到葉子節(jié)點(diǎn),返回
              if(pRoot == NULL)
              {
                  return;
              }
          // 如果左子樹為空,那么該節(jié)點(diǎn)的左邊最長距離為0
          if(pRoot -> pLeft == NULL)
          {
              pRoot -> nMaxLeft = 0;
          }
          // 如果右子樹為空,那么該節(jié)點(diǎn)的右邊最長距離為0
          if(pRoot -> pRight == NULL)
          {
              pRoot -> nMaxRight = 0;
          }
          // 如果左子樹不為空,遞歸尋找左子樹最長距離
          if(pRoot -> pLeft != NULL)
          {
              FindMaxLen(pRoot -> pLeft);
          }
          // 如果右子樹不為空,遞歸尋找右子樹最長距離
          if(pRoot -> pRight != NULL)
          {
              FindMaxLen(pRoot -> pRight);
          }
          // 計(jì)算左子樹最長節(jié)點(diǎn)距離
          if(pRoot -> pLeft != NULL)
          {
              int nTempMax = 0;
              if(pRoot -> pLeft -> nMaxLeft > pRoot -> pLeft -> nMaxRight)
              {
                  nTempMax = pRoot -> pLeft -> nMaxLeft;
              }
              else
              {
                  nTempMax = pRoot -> pLeft -> nMaxRight;
              }
              pRoot -> nMaxLeft = nTempMax + 1;
          }
          // 計(jì)算右子樹最長節(jié)點(diǎn)距離
          if(pRoot -> pRight != NULL)
          //
          //
          //
          //
          //
          左孩子
          右孩子
          左子樹中的最長距離
          右子樹中的最長距離
          該節(jié)點(diǎn)的值

          寫書評,贏取《編程之美——微軟技術(shù)面試心得》www.ieee.org.cn/BCZM.asp
          {
          int nTempMax = 0;
          if(pRoot -> pRight -> nMaxLeft > pRoot -> pRight -> nMaxRight)
          {
              nTempMax = pRoot -> pRight -> nMaxLeft;
          }
          else
          {
              nTempMax = pRoot -> pRight -> nMaxRight;
          }
          pRoot -> nMaxRight = nTempMax + 1;
          }
          // 更新最長距離
          if(pRoot -> nMaxLeft + pRoot -> nMaxRight > nMaxLen)
          {
              nMaxLen = pRoot -> nMaxLeft + pRoot -> nMaxRight;
          }
          }
          擴(kuò)展問題
          在代碼中,我們使用了遞歸的辦法來完成問題的求解。那么是否有非遞歸的算法來解決
          這個(gè)問題呢?
          總結(jié)
          對于遞歸問題的分析,筆者有一些小小的體會:
          1. 先弄清楚遞歸的順序。在遞歸的實(shí)現(xiàn)中,往往需要假設(shè)后續(xù)的調(diào)用已經(jīng)完成,在此
          基礎(chǔ)之上,才實(shí)現(xiàn)遞歸的邏輯。在該題中,我們就是假設(shè)已經(jīng)把后面的長度計(jì)算出
          來了,然后繼續(xù)考慮后面的邏輯;
          2. 分析清楚遞歸體的邏輯,然后寫出來。比如在上面的問題中,遞歸體的邏輯就是如
          何計(jì)算兩邊最長的距離;
          3. 考慮清楚遞歸退出的邊界條件。也就說,哪些地方應(yīng)該寫return。
          注意到以上 3 點(diǎn),在面對遞歸問題的時(shí)候,我們將總是有章可循。
          -----------------------------------------------------------------------------------

          《編程之美》讀書筆記:第3.8節(jié)“求二叉樹中節(jié)點(diǎn)的最大距離”擴(kuò)展問題 收藏
           感謝azuryy為大家分享《編程之美》第3.8節(jié)擴(kuò)展問題的答案:用非遞歸的算法求一顆二叉樹中相距最遠(yuǎn)的兩個(gè)節(jié)點(diǎn)之間的距離。(原博客地址:http://hi.baidu.com/azuryy/blog/item/30ad10ea192424d5d439c96d.html)

          #include <stack>
          #include <algorithm>
          using namespace std;

          struct Node
          {
              bool _visited;

              Node* left;
              Node* right;
              int maxLeft;
              int maxRight;

              Node()
              {
                  _visited = false;
                  maxLeft = 0;
                  maxRight = 0;
                  left = NULL;
                  right = NULL;
              }
          };

          int maxLen   = 0;

          stack<Node*> nodeStack;

          void findMaxLen( Node* root )
          {
              Node* node;

              if ( root == NULL )
              {
                  return ;
              }

              nodeStack.push( root );
              while( !nodeStack.empty())
              {
                  node = nodeStack.top();

                  if ( node->left == NULL && node->right == NULL )
                  {
                      nodeStack.pop();
                      node->_visited = true;
                      continue;
                  }
                  if ( node->left )
                  {
                      if ( !node->left->_visited )
                      {
                          nodeStack.push( node->left ) ;
                      }           
                      else
                      {
                          node->maxLeft = max( node->left->maxLeft,node->left->maxRight ) + 1;
                      }
                  }
                  if ( ( !node->left || node->left->_visited ) && node->right )
                  {
                      if ( !node->right->_visited )
                      {
                          nodeStack.push( node->right ) ;
                      }           
                      else
                      {
                          node->maxRight = max( node->right->maxLeft,node->right->maxRight ) + 1;
                      }
                  }

                  if (( !node->left || node->left->_visited ) && ( !node->right || node->right->_visited ))
                  {
                      maxLen = max( maxLen, node->maxLeft + node->maxRight );
                      node->_visited = true;
                      nodeStack.pop();           
                  }
              }
          }


          Immediate test case 1:

          int main()
          {
              Node *tmp ;
              Node* root = new Node();

              tmp = new Node();
              root->left = tmp ;

              tmp = new Node();
              root->right = tmp;

              tmp = new Node();
              root->right->right = tmp;

              tmp = new Node();
              root->right->right->right = tmp;

              tmp = new Node();
              root->right->right->right->left = tmp;
              findMaxLen( root );

              cout << maxLen << endl;
              return 0;
          }

           


          本文來自CSDN博客,轉(zhuǎn)載請標(biāo)明出處:http://blog.csdn.net/bvbook/archive/2008/07/25/2710209.aspx

          posted @ 2009-11-14 21:58 小強(qiáng)摩羯座 閱讀(929) | 評論 (0)編輯 收藏

          JDBC優(yōu)化策略總結(jié)
          相比Hibernate、iBatis、DBUtils等,理論上JDBC的性能都超過它們。JDBC提供更底層更精細(xì)的數(shù)據(jù)訪問策略,這是Hibernate等框架所不具備的。

            在一些高性能的數(shù)據(jù)操作中,越高級的框架越不適合使用。這里是我在開發(fā)中對JDBC使用過程中一些優(yōu)化經(jīng)驗(yàn)總結(jié)。

            1、選擇純Java的JDBC驅(qū)動。

            2、使用連接池--使用一個(gè)“池”來管理JDBC連接,并精心調(diào)試池配置的參數(shù),目前可用的數(shù)據(jù)庫連接池很多很多。

            如何配置合適的參數(shù)呢,需要的是測試,而不是感覺。

            3、重用Connection--最大限度使用每個(gè)數(shù)據(jù)庫連接,得到了就不要輕易“丟棄”。

            有時(shí)候在一個(gè)過程中,會多次操作數(shù)據(jù)庫,而僅僅需要一個(gè)連接就夠了,沒必用一次就獲取一個(gè)連接,用完后關(guān)閉或者入池。這樣會增加“池”管理的成本,千萬別以為你用了“池”就可以隨便申請和歸還連接,都是有代價(jià)的。如果是一個(gè)龐大循環(huán)塊中操作數(shù)據(jù)庫,更應(yīng)該注意此問題!

            4、重用Statement--對于一些預(yù)定義SQL,設(shè)置為靜態(tài)常量,并盡可能重用預(yù)定義SQL產(chǎn)生的PreparedStatement對象。對于多次使用一種模式的SQL,使用預(yù)定義SQL可以獲取更好的性能。

            5、使用批處理SQL。

            6、優(yōu)化結(jié)果集ResultSet--查詢時(shí)候,返回的結(jié)果集有不同的類型,優(yōu)先選擇只讀結(jié)果集、不可滾動的屬性。

            這里是很容易出現(xiàn)問題的地方:

          java.sql.ResultSet

          static int CLOSE_CURSORS_AT_COMMIT    
                              該常量指示調(diào)用 Connection.commit 方法時(shí)應(yīng)該關(guān)閉 ResultSet 對象。    
          static int CONCUR_READ_ONLY    
                              該常量指示不可以更新的 ResultSet 對象的并發(fā)模式。    
          static int CONCUR_UPDATABLE    
                              該常量指示可以更新的 ResultSet 對象的并發(fā)模式。    
          static int FETCH_FORWARD    
                              該常量指示將按正向(即從第一個(gè)到最后一個(gè))處理結(jié)果集中的行。    
          static int FETCH_REVERSE    
                              該常量指示將按反向(即從最后一個(gè)到第一個(gè))處理結(jié)果集中的行處理。    
          static int FETCH_UNKNOWN    
                              該常量指示結(jié)果集中的行的處理順序未知。    
          static int HOLD_CURSORS_OVER_COMMIT    
                              該常量指示調(diào)用 Connection.commit 方法時(shí)不應(yīng)關(guān)閉 ResultSet 對象。    
          static int TYPE_FORWARD_ONLY    
                              該常量指示指針只能向前移動的 ResultSet 對象的類型。    
          static int TYPE_SCROLL_INSENSITIVE    
                              該常量指示可滾動但通常不受其他的更改影響的 ResultSet 對象的類型。    
          static int TYPE_SCROLL_SENSITIVE    
                              該常量指示可滾動并且通常受其他的更改影響的 ResultSet 對象的類型。
           
            說明下:

            結(jié)果集分兩種類型:只讀和可更改,只讀的話,更省內(nèi)存,查詢的結(jié)果集不能更改。如果結(jié)果集在查詢后,更改了值又要保存,則使用可更改結(jié)果集。

            結(jié)果集的游標(biāo)也有兩種類型:如果沒必要讓游標(biāo)自由滾動,則選擇單方向移動的游標(biāo)類型。

            對于是否并發(fā)操作:如果不需要考慮線程安全,則選擇忽略并發(fā)的結(jié)果集類型,否則選擇并發(fā)安全的類型。

            另外,還要控制結(jié)果的大小,幾乎所有的數(shù)據(jù)庫都有查詢記錄條數(shù)控制的策略,可以海量數(shù)據(jù)進(jìn)行分批處理,一次一批,這樣不至于把系統(tǒng)搞死。

            7、事物優(yōu)化--如果數(shù)據(jù)庫不支持事物,就不要寫回滾代碼,如果不考慮事物,就不要做事務(wù)的控制。

            8、安全優(yōu)化--管理好你的Connection對象,在異常時(shí)候能“入池”或者關(guān)閉。因此應(yīng)該將Connection釋放的代碼寫在異常處理的finally塊中。

            9、異常處理優(yōu)化--不要輕易吞噬SQLException,對于DAO、Service層次的數(shù)據(jù)訪問,一般在DAO中跑出異常,在Service中處理異常。但DAO中也可以處理異常,并做轉(zhuǎn)義拋出,不要隨便拋出RuntimeExeption,因?yàn)檫@是JVM拋出的,不需要你可以去拋出,因?yàn)镽untimeException往往會導(dǎo)致系統(tǒng)掛起。

            10、代碼高層優(yōu)化--在以上的基礎(chǔ)上,優(yōu)化封裝你的數(shù)據(jù)訪問方式,盡可能讓代碼簡潔好維護(hù),如果你還覺得性能不行,那就該從整個(gè)系統(tǒng)角度考慮優(yōu)化了,比如加上緩存服務(wù)器,集群、負(fù)載均衡、優(yōu)化數(shù)據(jù)庫服務(wù)器等等,以獲取更好的系能。

            本文出自 “熔 巖” 博客,請務(wù)必保留此出處http://lavasoft.blog.51cto.com/62575/225828

          posted @ 2009-11-14 19:18 小強(qiáng)摩羯座 閱讀(172) | 評論 (0)編輯 收藏

          java代碼優(yōu)化編程(2)

            17、不用new關(guān)鍵詞創(chuàng)建類的實(shí)例

            用new關(guān)鍵詞創(chuàng)建類的實(shí)例時(shí),構(gòu)造函數(shù)鏈中的所有構(gòu)造函數(shù)都會被自動調(diào)用。但如果一個(gè)對象實(shí)現(xiàn)了Cloneable接口,我們可以調(diào)用它的clone()方法。clone()方法不會調(diào)用任何類構(gòu)造函數(shù)。

            在使用設(shè)計(jì)模式(Design Pattern)的場合,如果用Factory模式創(chuàng)建對象,則改用clone()方法創(chuàng)建新的對象實(shí)例非常簡單。例如,下面是Factory模式的一個(gè)典型實(shí)現(xiàn):

            public static Credit getNewCredit() {

            return new Credit();

            }

            改進(jìn)后的代碼使用clone()方法,如下所示:

            private static Credit BaseCredit = new Credit();

            public static Credit getNewCredit() {

            return (Credit) BaseCredit.clone();

            }

            上面的思路對于數(shù)組處理同樣很有用。

            18、乘法和除法

            考慮下面的代碼:

            for (val = 0; val < 100000; val +=5) {

            alterX = val * 8; myResult = val * 2;

            }

            用移位操作替代乘法操作可以極大地提高性能。下面是修改后的代碼:

            for (val = 0; val < 100000; val += 5) {

            alterX = val << 3; myResult = val << 1;

            }

            修改后的代碼不再做乘以8的操作,而是改用等價(jià)的左移3位操作,每左移1位相當(dāng)于乘以2。相應(yīng)地,右移1位操作相當(dāng)于除以2。值得一提的是,雖然移位操作速度快,但可能使代碼比較難于理解,所以最好加上一些注釋。

            19、在JSP頁面中關(guān)閉無用的會話。

            一個(gè)常見的誤解是以為session在有客戶端訪問時(shí)就被創(chuàng)建,然而事實(shí)是直到某server端程序調(diào)用 HttpServletRequest.getSession(true)這樣的語句時(shí)才被創(chuàng)建,注意如果JSP沒有顯示的使用 <%@page session="false"%> 關(guān)閉session,則JSP文件在編譯成Servlet時(shí)將會自動加上這樣一條語句HttpSession session = HttpServletRequest.getSession(true);這也是JSP中隱含的 session對象的來歷。由于session會消耗內(nèi)存資源,因此,如果不打算使用session,應(yīng)該在所有的JSP中關(guān)閉它。

            對于那些無需跟蹤會話狀態(tài)的頁面,關(guān)閉自動創(chuàng)建的會話可以節(jié)省一些資源。使用如下page指令:<%@ page session="false"%>

            20、JDBC與I/O

            如果應(yīng)用程序需要訪問一個(gè)規(guī)模很大的數(shù)據(jù)集,則應(yīng)當(dāng)考慮使用塊提取方式。默認(rèn)情況下,JDBC每次提取32行數(shù)據(jù)。舉例來說,假設(shè)我們要遍歷一個(gè)5000行的記錄集,JDBC必須調(diào)用數(shù)據(jù)庫157次才能提取到全部數(shù)據(jù)。如果把塊大小改成512,則調(diào)用數(shù)據(jù)庫的次數(shù)將減少到10次。

            [p][/p]21、Servlet與內(nèi)存使用

            許多開發(fā)者隨意地把大量信息保存到用戶會話之中。一些時(shí)候,保存在會話中的對象沒有及時(shí)地被垃圾回收機(jī)制回收。從性能上看,典型的癥狀是用戶感到系統(tǒng)周期性地變慢,卻又不能把原因歸于任何一個(gè)具體的組件。如果監(jiān)視JVM的堆空間,它的表現(xiàn)是內(nèi)存占用不正常地大起大落。

            解決這類內(nèi)存問題主要有二種辦法。第一種辦法是,在所有作用范圍為會話的Bean中實(shí)現(xiàn)HttpSessionBindingListener接口。這樣,只要實(shí)現(xiàn)valueUnbound()方法,就可以顯式地釋放Bean使用的資源。 另外一種辦法就是盡快地把會話作廢。大多數(shù)應(yīng)用服務(wù)器都有設(shè)置會話作廢間隔時(shí)間的選項(xiàng)。另外,也可以用編程的方式調(diào)用會話的setMaxInactiveInterval()方法,該方法用來設(shè)定在作廢會話之前,Servlet容器允許的客戶請求的最大間隔時(shí)間,以秒計(jì)。

            22、使用緩沖標(biāo)記

            一些應(yīng)用服務(wù)器加入了面向JSP的緩沖標(biāo)記功能。例如,BEA的WebLogic Server從6.0版本開始支持這個(gè)功能,Open Symphony工程也同樣支持這個(gè)功能。JSP緩沖標(biāo)記既能夠緩沖頁面片斷,也能夠緩沖整個(gè)頁面。當(dāng)JSP頁面執(zhí)行時(shí),如果目標(biāo)片斷已經(jīng)在緩沖之中,則生成該片斷的代碼就不用再執(zhí)行。頁面級緩沖捕獲對指定 URL的請求,并緩沖整個(gè)結(jié)果頁面。對于購物籃、目錄以及門戶網(wǎng)站的主頁來說,這個(gè)功能極其有用。對于這類應(yīng)用,頁面級緩沖能夠保存頁面執(zhí)行的結(jié)果,供后繼請求使用。

            23、選擇合適的引用機(jī)制

            在典型的JSP應(yīng)用系統(tǒng)中,頁頭、頁腳部分往往被抽取出來,然后根據(jù)需要引入頁頭、頁腳。當(dāng)前,在JSP頁面中引入外部資源的方法主要有兩種:include指令,以及include動作。

            include 指令:例如<%@ include file="copyright.html" %>。該指令在編譯時(shí)引入指定的資源。在編譯之前,帶有 include指令的頁面和指定的資源被合并成一個(gè)文件。被引用的外部資源在編譯時(shí)就確定,比運(yùn)行時(shí)才確定資源更高效。

            include動作:例如<jsp:include page="copyright.jsp" />。該動作引入指定頁面執(zhí)行后生成的結(jié)果。由于它在運(yùn)行時(shí)完成,因此對輸出結(jié)果的控制更加靈活。但時(shí),只有當(dāng)被引用的內(nèi)容頻繁地改變時(shí),或者在對主頁面的請求沒有出現(xiàn)之前,被引用的頁面無法確定時(shí),使用include 動作才合算。

          24、及時(shí)清除不再需要的會話

            為了清除不再活動的會話,許多應(yīng)用服務(wù)器都有默認(rèn)的會話超時(shí)時(shí)間,一般為30分鐘。當(dāng)應(yīng)用服務(wù)器需要保存更多會話時(shí),如果內(nèi)存容量不足,操作系統(tǒng)會把部分內(nèi)存數(shù)據(jù)轉(zhuǎn)移到磁盤,應(yīng)用服務(wù)器也可能根據(jù)“最近最頻繁使用 ”(Most Recently Used)算法把部分不活躍的會話轉(zhuǎn)儲到磁盤,甚至可能拋出“內(nèi)存不足”異常。在大規(guī)模系統(tǒng)中,串行化會話的代價(jià)是很昂貴的。當(dāng)會話不再需要時(shí),應(yīng)當(dāng)及時(shí)調(diào)用HttpSession.invalidate()方法清除會話。 HttpSession.invalidate()方法通常可以在應(yīng)用的退出頁面調(diào)用。

            25、不要將數(shù)組聲明為:public static final 。

            26、HashMap的遍歷效率討論

            經(jīng)常遇到對HashMap中的key和value值對的遍歷操作,有如下兩種方法:Map<String, String[]> paraMap = new HashMap<String, String[]>();

            ................//第一個(gè)循環(huán)

            Set<String> appFieldDefIds = paraMap.keySet();

            for (String appFieldDefId : appFieldDefIds) {

            String[] values = paraMap.get(appFieldDefId);

            ......

            }

            //第二個(gè)循環(huán)

            for(Entry<String, String[]> entry : paraMap.entrySet()){

            String appFieldDefId = entry.getKey();

            String[] values = entry.getValue();

            .......

            }

            第一種實(shí)現(xiàn)明顯的效率不如第二種實(shí)現(xiàn)。

            分析如下 Set<String> appFieldDefIds = paraMap.keySet(); 是先從HashMap中取得keySet

            代碼如下:

            public Set<K> keySet() {

            Set<K> ks = keySet;

            return (ks != null ? ks : (keySet = new KeySet()));

            }

            private class KeySet extends AbstractSet<K> {

            public Iterator<K> iterator() {

            return newKeyIterator();

            }

            public int size() {

            return size;

            }

            public boolean contains(Object o) {

            return containsKey(o);

            }

            public boolean remove(Object o) {

            return HashMap.this.removeEntryForKey(o) != null;

            }

            public void clear() {

            HashMap.this.clear();

            }

            }

            其實(shí)就是返回一個(gè)私有類KeySet, 它是從AbstractSet繼承而來,實(shí)現(xiàn)了Set接口。

            再來看看for/in循環(huán)的語法

            for(declaration : expression)

            statement

            在執(zhí)行階段被翻譯成如下各式

            for(Iterator<E> #i = (expression).iterator(); #i.hashNext();){

            declaration = #i.next();

            statement

            }

          因此在第一個(gè)for語句for (String appFieldDefId : appFieldDefIds) 中調(diào)用了HashMap.keySet().iterator() 而這個(gè)方法調(diào)用了newKeyIterator()

            Iterator<K> newKeyIterator() {

            return new KeyIterator();

            }

            private class KeyIterator extends HashIterator<K> {

            public K next() {

            return nextEntry().getKey();

            }

            }

            所以在for中還是調(diào)用了

            在第二個(gè)循環(huán)for(Entry<String, String[]> entry : paraMap.entrySet())中使用的Iterator是如下的一個(gè)內(nèi)部類

            private class EntryIterator extends HashIterator<Map.Entry<K,V>> {

            public Map.Entry<K,V> next() {

            return nextEntry();

            }

            }

            此時(shí)第一個(gè)循環(huán)得到key,第二個(gè)循環(huán)得到HashMap的Entry

            效率就是從循環(huán)里面體現(xiàn)出來的第二個(gè)循環(huán)此致可以直接取key和value值

            而第一個(gè)循環(huán)還是得再利用HashMap的get(Object key)來取value值

            現(xiàn)在看看HashMap的get(Object key)方法

            public V get(Object key) {

            Object k = maskNull(key);

            int hash = hash(k);

            int i = indexFor(hash, table.length); //Entry[] table

            Entry<K,V> e = table;

            while (true) {

            if (e == null)

            return null;

            if (e.hash == hash && eq(k, e.key))

            return e.value;

            e = e.next;

            }

            }

            其實(shí)就是再次利用Hash值取出相應(yīng)的Entry做比較得到結(jié)果,所以使用第一中循環(huán)相當(dāng)于兩次進(jìn)入HashMap的Entry中

            而第二個(gè)循環(huán)取得Entry的值之后直接取key和value,效率比第一個(gè)循環(huán)高。其實(shí)按照Map的概念來看也應(yīng)該是用第二個(gè)循環(huán)好一點(diǎn),它本來就是key和value的值對,將key和value分開操作在這里不是個(gè)好選擇。

            27、array(數(shù)組) 和 ArryList的使用

            array([]):最高效;但是其容量固定且無法動態(tài)改變;

            ArrayList:容量可動態(tài)增長;但犧牲效率;

            基于效率和類型檢驗(yàn),應(yīng)盡可能使用array,無法確定數(shù)組大小時(shí)才使用ArrayList!

            ArrayList是Array的復(fù)雜版本

            ArrayList內(nèi)部封裝了一個(gè)Object類型的數(shù)組,從一般的意義來說,它和數(shù)組沒有本質(zhì)的差別,甚至于ArrayList的許多方法,如Index、IndexOf、Contains、Sort等都是在內(nèi)部數(shù)組的基礎(chǔ)上直接調(diào)用Array的對應(yīng)方法。

            ArrayList存入對象時(shí),拋棄類型信息,所有對象屏蔽為Object,編譯時(shí)不檢查類型,但是運(yùn)行時(shí)會報(bào)錯(cuò)。

            注:jdk5中加入了對泛型的支持,已經(jīng)可以在使用ArrayList時(shí)進(jìn)行類型檢查。

            從這一點(diǎn)上看來,ArrayList與數(shù)組的區(qū)別主要就是由于動態(tài)增容的效率問題了

            28、盡量使用HashMap 和ArrayList ,除非必要,否則不推薦使用HashTable和Vector ,后者由于使用同步機(jī)制,而導(dǎo)致了性能的開銷。

            29、StringBuffer 和StringBuilder的區(qū)別:

            java.lang.StringBuffer 線程安全的可變字符序列。一個(gè)類似于 String 的字符串緩沖區(qū),但不能修改。StringBuilder。與該類相比,通常應(yīng)該優(yōu)先使用 java.lang.StringBuilder類,因?yàn)樗С炙邢嗤牟僮鳎捎谒粓?zhí)行同步,所以速度更快。為了獲得更好的性能,在構(gòu)造 StirngBuffer 或 StirngBuilder 時(shí)應(yīng)盡可能指定它的容量。當(dāng)然,如果你操作的字符串長度不超過 16 個(gè)字符就不用了。 相同情況下使用 StirngBuilder 相比使用 StringBuffer 僅能獲得 10%-15% 左右的性能提升,但卻要冒多線程不安全的風(fēng)險(xiǎn)。而在現(xiàn)實(shí)的模塊化編程中,負(fù)責(zé)某一模塊的程序員不一定能清晰地判斷該模塊是否會放入多線程的環(huán)境中運(yùn)行,因此:除非你能確定你的系統(tǒng)的瓶頸是在 StringBuffer 上,并且確定你的模塊不會運(yùn)行在多線程模式下,否則還是用 StringBuffer 吧。

          posted @ 2009-11-14 19:15 小強(qiáng)摩羯座 閱讀(341) | 評論 (0)編輯 收藏

           

            可供程序利用的資源(內(nèi)存、CPU時(shí)間、網(wǎng)絡(luò)帶寬等)是有限的,優(yōu)化的目的就是讓程序用盡可能少的資源完成預(yù)定的任務(wù)。優(yōu)化通常包含兩方面的內(nèi)容:減小代碼的體積,提高代碼的運(yùn)行效率。本文討論的主要是如何提高代碼的效率。

            在 Java程序中,性能問題的大部分原因并不在于Java語言,而是在于程序本身。養(yǎng)成好的代碼編寫習(xí)慣非常重要,比如正確地、巧妙地運(yùn)用 java.lang.String類和java.util.Vector類,它能夠顯著地提高程序的性能。下面我們就來具體地分析一下這方面的問題。

            1、    盡量指定類的final修飾符 帶有final修飾符的類是不可派生的。在Java核心API中,有許多應(yīng)用final的例子,例如 java.lang.String。為String類指定final防止了人們覆蓋length()方法。另外,如果指定一個(gè)類為final,則該類所有的方法都是final。Java編譯器會尋找機(jī)會內(nèi)聯(lián)(inline)所有的final方法(這和具體的編譯器實(shí)現(xiàn)有關(guān))。此舉能夠使性能平均提高 50% 。

            2、    盡量重用對象。特別是String 對象的使用中,出現(xiàn)字符串連接情況時(shí)應(yīng)用StringBuffer 代替。由于系統(tǒng)不僅要花時(shí)間生成對象,以后可能還需花時(shí)間對這些對象進(jìn)行垃圾回收和處理。因此,生成過多的對象將會給程序的性能帶來很大的影響。

            3、    盡量使用局部變量,調(diào)用方法時(shí)傳遞的參數(shù)以及在調(diào)用中創(chuàng)建的臨時(shí)變量都保存在棧(Stack)中,速度較快。其他變量,如靜態(tài)變量、實(shí)例變量等,都在堆(Heap)中創(chuàng)建,速度較慢。另外,依賴于具體的編譯器/JVM,局部變量還可能得到進(jìn)一步優(yōu)化。請參見《盡可能使用堆棧變量》。

            4、    不要重復(fù)初始化變量  默認(rèn)情況下,調(diào)用類的構(gòu)造函數(shù)時(shí), Java會把變量初始化成確定的值:所有的對象被設(shè)置成null,整數(shù)變量(byte、 short、int、long)設(shè)置成0,float和double變量設(shè)置成0.0,邏輯值設(shè)置成false。當(dāng)一個(gè)類從另一個(gè)類派生時(shí),這一點(diǎn)尤其應(yīng)該注意,因?yàn)橛胣ew關(guān)鍵詞創(chuàng)建一個(gè)對象時(shí),構(gòu)造函數(shù)鏈中的所有構(gòu)造函數(shù)都會被自動調(diào)用。

            5、    在JAVA + ORACLE 的應(yīng)用系統(tǒng)開發(fā)中,java中內(nèi)嵌的SQL語句盡量使用大寫的形式,以減輕ORACLE解析器的解析負(fù)擔(dān)。

            6、    Java 編程過程中,進(jìn)行數(shù)據(jù)庫連接、I/O流操作時(shí)務(wù)必小心,在使用完畢后,即使關(guān)閉以釋放資源。因?yàn)閷@些大對象的操作會造成系統(tǒng)大的開銷,稍有不慎,會導(dǎo)致嚴(yán)重的后果。

            7、    由于JVM的有其自身的GC機(jī)制,不需要程序開發(fā)者的過多考慮,從一定程度上減輕了開發(fā)者負(fù)擔(dān),但同時(shí)也遺漏了隱患,過分的創(chuàng)建對象會消耗系統(tǒng)的大量內(nèi)存,嚴(yán)重時(shí)會導(dǎo)致內(nèi)存泄露,因此,保證過期對象的及時(shí)回收具有重要意義。JVM回收垃圾的條件是:對象不在被引用;然而,JVM的GC并非十分的機(jī)智,即使對象滿足了垃圾回收的條件也不一定會被立即回收。所以,建議我們在對象使用完畢,應(yīng)手動置成null。

            8、    在使用同步機(jī)制時(shí),應(yīng)盡量使用方法同步代替代碼塊同步。

            9、    盡量減少對變量的重復(fù)計(jì)算

            例如:for(int i = 0;i < list.size; i ++) {

            …

            }

            應(yīng)替換為:

            for(int i = 0,int len = list.size();i < len; i ++) {

            …

            }

            10、盡量采用lazy loading 的策略,即在需要的時(shí)候才開始創(chuàng)建。

            例如:    String str = “aaa”;

            if(i == 1) {

            list.add(str);

            }

            應(yīng)替換為:

            if(i == 1) {

            String str = “aaa”;

            list.add(str);

            }

            11、慎用異常

            異常對性能不利。拋出異常首先要?jiǎng)?chuàng)建一個(gè)新的對象。Throwable接口的構(gòu)造函數(shù)調(diào)用名為fillInStackTrace()的本地(Native)方法,fillInStackTrace()方法檢查堆棧,收集調(diào)用跟蹤信息。只要有異常被拋出,VM就必須調(diào)整調(diào)用堆棧,因?yàn)樵谔幚磉^程中創(chuàng)建了一個(gè)新的對象。 異常只能用于錯(cuò)誤處理,不應(yīng)該用來控制程序流程。

          12、不要在循環(huán)中使用:

            Try {

            } catch() {

            }

            應(yīng)把其放置在最外層。

            13、StringBuffer 的使用:

            StringBuffer表示了可變的、可寫的字符串。

            有三個(gè)構(gòu)造方法 :

            StringBuffer ();            //默認(rèn)分配16個(gè)字符的空間

            StringBuffer (int size);  //分配size個(gè)字符的空間

            StringBuffer (String str);  //分配16個(gè)字符+str.length()個(gè)字符空間

            你可以通過StringBuffer的構(gòu)造函數(shù)來設(shè)定它的初始化容量,這樣可以明顯地提升性能。這里提到的構(gòu)造函數(shù)是 StringBuffer(int length),length參數(shù)表示當(dāng)前的StringBuffer能保持的字符數(shù)量。你也可以使用 ensureCapacity(int minimumcapacity)方法在StringBuffer對象創(chuàng)建之后設(shè)置它的容量。首先我們看看 StringBuffer的缺省行為,然后再找出一條更好的提升性能的途徑。

            StringBuffer在內(nèi)部維護(hù)一個(gè)字符數(shù)組,當(dāng)你使用缺省的構(gòu)造函數(shù)來創(chuàng)建StringBuffer對象的時(shí)候,因?yàn)闆]有設(shè)置初始化字符長度,StringBuffer的容量被初始化為16個(gè)字符,也就是說缺省容量就是16個(gè)字符。當(dāng)StringBuffer達(dá)到最大容量的時(shí)候,它會將自身容量增加到當(dāng)前的2倍再加2,也就是(2*舊值+2)。如果你使用缺省值,初始化之后接著往里面追加字符,在你追加到第16個(gè)字符的時(shí)候它會將容量增加到34(2*16+2),當(dāng)追加到34個(gè)字符的時(shí)候就會將容量增加到 70(2*34+2)。無論何事只要StringBuffer到達(dá)它的最大容量它就不得不創(chuàng)建一個(gè)新的字符數(shù)組然后重新將舊字符和新字符都拷貝一遍――這也太昂貴了點(diǎn)。所以總是給StringBuffer設(shè)置一個(gè)合理的初始化容量值是錯(cuò)不了的,這樣會帶來立竿見影的性能增益。

            StringBuffer初始化過程的調(diào)整的作用由此可見一斑。所以,使用一個(gè)合適的容量值來初始化StringBuffer永遠(yuǎn)都是一個(gè)最佳的建議。

            14、合理的使用Java類 java.util.Vector。

            簡單地說,一個(gè)Vector就是一個(gè)java.lang.Object實(shí)例的數(shù)組。Vector與數(shù)組相似,它的元素可以通過整數(shù)形式的索引訪問。但是,Vector類型的對象在創(chuàng)建之后,對象的大小能夠根據(jù)元素的增加或者刪除而擴(kuò)展、縮小。請考慮下面這個(gè)向Vector加入元素的例子:

            Object obj = new Object();

            Vector v = new Vector(100000);

            for(int I=0;

            I<100000; I++) { v.add(0,obj); }

            除非有絕對充足的理由要求每次都把新元素插入到Vector的前面,否則上面的代碼對性能不利。在默認(rèn)構(gòu)造函數(shù)中,Vector的初始存儲能力是10個(gè)元素,如果新元素加入時(shí)存儲能力不足,則以后存儲能力每次加倍。Vector類就象StringBuffer類一樣,每次擴(kuò)展存儲能力時(shí),所有現(xiàn)有的元素都要復(fù)制到新的存儲空間之中。下面的代碼片段要比前面的例子快幾個(gè)數(shù)量級:

            Object obj = new Object();

            Vector v = new Vector(100000);

            for(int I=0; I<100000; I++) { v.add(obj); }

            同樣的規(guī)則也適用于Vector類的remove()方法。由于Vector中各個(gè)元素之間不能含有“空隙”,刪除除最后一個(gè)元素之外的任意其他元素都導(dǎo)致被刪除元素之后的元素向前移動。也就是說,從Vector刪除最后一個(gè)元素要比刪除第一個(gè)元素“開銷”低好幾倍。

            假設(shè)要從前面的Vector刪除所有元素,我們可以使用這種代碼:

            for(int I=0; I<100000; I++)

            {

            v.remove(0);

            }

            但是,與下面的代碼相比,前面的代碼要慢幾個(gè)數(shù)量級:

            for(int I=0; I<100000; I++)

            {

            v.remove(v.size()-1);

            }

            從Vector類型的對象v刪除所有元素的最好方法是:

            v.removeAllElements();


          假設(shè)Vector類型的對象v包含字符串“Hello”。考慮下面的代碼,它要從這個(gè)Vector中刪除“Hello”字符串:

            String s = "Hello";

            int i = v.indexOf(s);

            if(I != -1) v.remove(s);

            這些代碼看起來沒什么錯(cuò)誤,但它同樣對性能不利。在這段代碼中,indexOf()方法對v進(jìn)行順序搜索尋找字符串“Hello”,remove(s)方法也要進(jìn)行同樣的順序搜索。改進(jìn)之后的版本是:

            String s = "Hello";

            int i = v.indexOf(s);

            if(I != -1) v.remove(i);

            這個(gè)版本中我們直接在remove()方法中給出待刪除元素的精確索引位置,從而避免了第二次搜索。一個(gè)更好的版本是:

            String s = "Hello"; v.remove(s);

            最后,我們再來看一個(gè)有關(guān)Vector類的代碼片段:

            for(int I=0; I++;I < v.length)

            如果v包含100,000個(gè)元素,這個(gè)代碼片段將調(diào)用v.size()方法100,000次。雖然size方法是一個(gè)簡單的方法,但它仍舊需要一次方法調(diào)用的開銷,至少JVM需要為它配置以及清除堆棧環(huán)境。在這里,for循環(huán)內(nèi)部的代碼不會以任何方式修改Vector類型對象v的大小,因此上面的代碼最好改寫成下面這種形式:

            int size = v.size(); for(int I=0; I++;I<size)

            雖然這是一個(gè)簡單的改動,但它仍舊贏得了性能。畢竟,每一個(gè)CPU周期都是寶貴的。

            15、當(dāng)復(fù)制大量數(shù)據(jù)時(shí),使用System.arraycopy()命令。

            16、代碼重構(gòu):增強(qiáng)代碼的可讀性。

            例如:

            public class ShopCart {

            private List carts ;

            …

            public void add (Object item) {

            if(carts == null) {

            carts = new ArrayList();

            }

            crts.add(item);

            }

            public void remove(Object item) {

            if(carts. contains(item)) {

            carts.remove(item);

            }

            }

            public List getCarts() {

            //返回只讀列表

            return Collections.unmodifiableList(carts);

            }

            //不推薦這種方式

            //this.getCarts().add(item);

            }

          posted @ 2009-11-14 19:12 小強(qiáng)摩羯座 閱讀(229) | 評論 (0)編輯 收藏

          精華游戲算法整理
          Author Author: 一滴蔚藍(lán)色 | Date Date: 2007-05-17 | View Count View: 8311 | Section & Category 開發(fā)技術(shù) - 程序設(shè)計(jì) | Digg Digg: 12

          游戲算法整理 算法一:A*尋路初探

          作者: Patrick Lester
          譯者:Panic 2005年3月18日

          譯者序:很久以前就知道了A*算法,但是從未認(rèn)真讀過相關(guān)的文章,也沒有看過代碼,只是腦子里有個(gè)模糊的概念。這次決定從頭開始,研究一下這個(gè)被人推崇備至的簡單方法,作為學(xué)習(xí)人工智能的開始。
          這篇文章非常知名,國內(nèi)應(yīng)該有不少人翻譯過它,我沒有查找,覺得翻譯本身也是對自身英文水平的鍛煉。經(jīng)過努力,終于完成了文檔,也明白的A*算法的原理。毫無疑問,作者用形象的描述,簡潔詼諧的語言由淺入深的講述了這一神奇的算法,相信每個(gè)讀過的人都會對此有所認(rèn)識(如果沒有,那就是偶的翻譯太差了 --b)。
          現(xiàn)在是2005年7月19日的版本,應(yīng)原作者要求,對文中的某些算法細(xì)節(jié)做了修改。
          原文鏈接:http://www.gamedev.net/reference/articles/article2003.asp
          原作者文章鏈接:http://www.policyalmanac.org/games/aStarTutorial.htm
          以下是翻譯的正文。

          會者不難,A*(念作A星)算法對初學(xué)者來說的確有些難度。

          這篇文章并不試圖對這個(gè)話題作權(quán)威的陳述。取而代之的是,它只是描述算法的原理,使你可以在進(jìn)一步的閱讀中理解其他相關(guān)的資料。

          最后,這篇文章沒有程序細(xì)節(jié)。你盡可以用任意的計(jì)算機(jī)程序語言實(shí)現(xiàn)它。如你所愿,我在文章的末尾包含了一個(gè)指向例子程序的鏈接。 壓縮包包括C++和Blitz Basic兩個(gè)語言的版本,如果你只是想看看它的運(yùn)行效果,里面還包含了可執(zhí)行文件。

          我們正在提高自己。讓我們從頭開始。。。

          序:搜索區(qū)域

          假設(shè)有人想從A點(diǎn)移動到一墻之隔的B點(diǎn),如下圖,綠色的是起點(diǎn)A,紅色是終點(diǎn)B,藍(lán)色方塊是中間的墻。


          [圖1]

          你首先注意到,搜索區(qū)域被我們劃分成了方形網(wǎng)格。像這樣,簡化搜索區(qū)域,是尋路的第一步。這一方法把搜索區(qū)域簡化成了一個(gè)二維數(shù)組。數(shù)組的每一個(gè)元素是網(wǎng)格的一個(gè)方塊,方塊被標(biāo)記為可通過的和不可通過的。路徑被描述為從A到B我們經(jīng)過的方塊的集合。一旦路徑被找到,我們的人就從一個(gè)方格的中心走向另一個(gè),直到到達(dá)目的地。

          這些中點(diǎn)被稱為“節(jié)點(diǎn)”。當(dāng)你閱讀其他的尋路資料時(shí),你將經(jīng)常會看到人們討論節(jié)點(diǎn)。為什么不把他們描述為方格呢?因?yàn)橛锌赡苣愕穆窂奖环指畛善渌皇欠礁竦慕Y(jié)構(gòu)。他們完全可以是矩形,六角形,或者其他任意形狀。節(jié)點(diǎn)能夠被放置在形狀的任意位置-可以在中心,或者沿著邊界,或其他什么地方。我們使用這種系統(tǒng),無論如何,因?yàn)樗亲詈唵蔚摹?/p>

          開始搜索

          正如我們處理上圖網(wǎng)格的方法,一旦搜索區(qū)域被轉(zhuǎn)化為容易處理的節(jié)點(diǎn),下一步就是去引導(dǎo)一次找到最短路徑的搜索。在A*尋路算法中,我們通過從點(diǎn)A開始,檢查相鄰方格的方式,向外擴(kuò)展直到找到目標(biāo)。

          我們做如下操作開始搜索:


             1,從點(diǎn)A開始,并且把它作為待處理點(diǎn)存入一個(gè)“開啟列表”。開啟列表就像一張購物清單。盡管現(xiàn)在列表里只有一個(gè)元素,但以后就會多起來。你的路徑可能會通過它包含的方格,也可能不會。基本上,這是一個(gè)待檢查方格的列表。
             2,尋找起點(diǎn)周圍所有可到達(dá)或者可通過的方格,跳過有墻,水,或其他無法通過地形的方格。也把他們加入開啟列表。為所有這些方格保存點(diǎn)A作為“父方格”。當(dāng)我們想描述路徑的時(shí)候,父方格的資料是十分重要的。后面會解釋它的具體用途。
             3,從開啟列表中刪除點(diǎn)A,把它加入到一個(gè)“關(guān)閉列表”,列表中保存所有不需要再次檢查的方格。

          在這一點(diǎn),你應(yīng)該形成如圖的結(jié)構(gòu)。在圖中,暗綠色方格是你起始方格的中心。它被用淺藍(lán)色描邊,以表示它被加入到關(guān)閉列表中了。所有的相鄰格現(xiàn)在都在開啟列表中,它們被用淺綠色描邊。每個(gè)方格都有一個(gè)灰色指針反指他們的父方格,也就是開始的方格。


          [圖2]

          接著,我們選擇開啟列表中的臨近方格,大致重復(fù)前面的過程,如下。但是,哪個(gè)方格是我們要選擇的呢?是那個(gè)F值最低的。

          路徑評分

          選擇路徑中經(jīng)過哪個(gè)方格的關(guān)鍵是下面這個(gè)等式:

          F = G + H

          這里:
              * G = 從起點(diǎn)A,沿著產(chǎn)生的路徑,移動到網(wǎng)格上指定方格的移動耗費(fèi)。
              * H = 從網(wǎng)格上那個(gè)方格移動到終點(diǎn)B的預(yù)估移動耗費(fèi)。這經(jīng)常被稱為啟發(fā)式的,可能會讓你有點(diǎn)迷惑。這樣叫的原因是因?yàn)樗皇莻€(gè)猜測。我們沒辦法事先知道路徑的長度,因?yàn)槁飞峡赡艽嬖诟鞣N障礙(墻,水,等等)。雖然本文只提供了一種計(jì)算H的方法,但是你可以在網(wǎng)上找到很多其他的方法。

          我們的路徑是通過反復(fù)遍歷開啟列表并且選擇具有最低F值的方格來生成的。文章將對這個(gè)過程做更詳細(xì)的描述。首先,我們更深入的看看如何計(jì)算這個(gè)方程。

          正如上面所說,G表示沿路徑從起點(diǎn)到當(dāng)前點(diǎn)的移動耗費(fèi)。在這個(gè)例子里,我們令水平或者垂直移動的耗費(fèi)為10,對角線方向耗費(fèi)為14。我們?nèi)∵@些值是因?yàn)檠貙蔷€的距離是沿水平或垂直移動耗費(fèi)的的根號2(別怕),或者約1.414倍。為了簡化,我們用10和14近似。比例基本正確,同時(shí)我們避免了求根運(yùn)算和小數(shù)。這不是只因?yàn)槲覀兣侣闊┗蛘卟幌矚g數(shù)學(xué)。使用這樣的整數(shù)對計(jì)算機(jī)來說也更快捷。你不就就會發(fā)現(xiàn),如果你不使用這些簡化方法,尋路會變得很慢。

          既然我們在計(jì)算沿特定路徑通往某個(gè)方格的G值,求值的方法就是取它父節(jié)點(diǎn)的G值,然后依照它相對父節(jié)點(diǎn)是對角線方向或者直角方向(非對角線),分別增加14和10。例子中這個(gè)方法的需求會變得更多,因?yàn)槲覀儚钠瘘c(diǎn)方格以外獲取了不止一個(gè)方格。

          H值可以用不同的方法估算。我們這里使用的方法被稱為曼哈頓方法,它計(jì)算從當(dāng)前格到目的格之間水平和垂直的方格的數(shù)量總和,忽略對角線方向。然后把結(jié)果乘以10。這被成為曼哈頓方法是因?yàn)樗雌饋硐裼?jì)算城市中從一個(gè)地方到另外一個(gè)地方的街區(qū)數(shù),在那里你不能沿對角線方向穿過街區(qū)。很重要的一點(diǎn),我們忽略了一切障礙物。這是對剩余距離的一個(gè)估算,而非實(shí)際值,這也是這一方法被稱為啟發(fā)式的原因。想知道更多?你可以在這里找到方程和額外的注解。

          F的值是G和H的和。第一步搜索的結(jié)果可以在下面的圖表中看到。F,G和H的評分被寫在每個(gè)方格里。正如在緊挨起始格右側(cè)的方格所表示的,F(xiàn)被打印在左上角,G在左下角,H則在右下角。


          [圖3]

          現(xiàn)在我們來看看這些方格。寫字母的方格里,G = 10。這是因?yàn)樗辉谒椒较蚱x起始格一個(gè)格距。緊鄰起始格的上方,下方和左邊的方格的G值都等于10。對角線方向的G值是14。

          H值通過求解到紅色目標(biāo)格的曼哈頓距離得到,其中只在水平和垂直方向移動,并且忽略中間的墻。用這種方法,起點(diǎn)右側(cè)緊鄰的方格離紅色方格有3格距離,H值就是30。這塊方格上方的方格有4格距離(記住,只能在水平和垂直方向移動),H值是40。你大致應(yīng)該知道如何計(jì)算其他方格的H值了~。

          每個(gè)格子的F值,還是簡單的由G和H相加得到

          繼續(xù)搜索

          為了繼續(xù)搜索,我們簡單的從開啟列表中選擇F值最低的方格。然后,對選中的方格做如下處理:

             4,把它從開啟列表中刪除,然后添加到關(guān)閉列表中。
             5,檢查所有相鄰格子。跳過那些已經(jīng)在關(guān)閉列表中的或者不可通過的(有墻,水的地形,或者其他無法通過的地形),把他們添加進(jìn)開啟列表,如果他們還不在里面的話。把選中的方格作為新的方格的父節(jié)點(diǎn)。
             6,如果某個(gè)相鄰格已經(jīng)在開啟列表里了,檢查現(xiàn)在的這條路徑是否更好。換句話說,檢查如果我們用新的路徑到達(dá)它的話,G值是否會更低一些。如果不是,那就什么都不做。
                另一方面,如果新的G值更低,那就把相鄰方格的父節(jié)點(diǎn)改為目前選中的方格(在上面的圖表中,把箭頭的方向改為指向這個(gè)方格)。最后,重新計(jì)算F和G的值。如果這看起來不夠清晰,你可以看下面的圖示。

          好了,讓我們看看它是怎么運(yùn)作的。我們最初的9格方格中,在起點(diǎn)被切換到關(guān)閉列表中后,還剩8格留在開啟列表中。這里面,F(xiàn)值最低的那個(gè)是起始格右側(cè)緊鄰的格子,它的F值是40。因此我們選擇這一格作為下一個(gè)要處理的方格。在緊隨的圖中,它被用藍(lán)色突出顯示。


          [圖4]

          首先,我們把它從開啟列表中取出,放入關(guān)閉列表(這就是他被藍(lán)色突出顯示的原因)。然后我們檢查相鄰的格子。哦,右側(cè)的格子是墻,所以我們略過。左側(cè)的格子是起始格。它在關(guān)閉列表里,所以我們也跳過它。

          其他4格已經(jīng)在開啟列表里了,于是我們檢查G值來判定,如果通過這一格到達(dá)那里,路徑是否更好。我們來看選中格子下面的方格。它的G值是14。如果我們從當(dāng)前格移動到那里,G值就會等于20(到達(dá)當(dāng)前格的G值是10,移動到上面的格子將使得G值增加10)。因?yàn)镚值20大于14,所以這不是更好的路徑。如果你看圖,就能理解。與其通過先水平移動一格,再垂直移動一格,還不如直接沿對角線方向移動一格來得簡單。

          當(dāng)我們對已經(jīng)存在于開啟列表中的4個(gè)臨近格重復(fù)這一過程的時(shí)候,我們發(fā)現(xiàn)沒有一條路徑可以通過使用當(dāng)前格子得到改善,所以我們不做任何改變。既然我們已經(jīng)檢查過了所有鄰近格,那么就可以移動到下一格了。

          于是我們檢索開啟列表,現(xiàn)在里面只有7格了,我們?nèi)匀贿x擇其中F值最低的。有趣的是,這次,有兩個(gè)格子的數(shù)值都是54。我們?nèi)绾芜x擇?這并不麻煩。從速度上考慮,選擇最后添加進(jìn)列表的格子會更快捷。這種導(dǎo)致了尋路過程中,在靠近目標(biāo)的時(shí)候,優(yōu)先使用新找到的格子的偏好。但這無關(guān)緊要。(對相同數(shù)值的不同對待,導(dǎo)致不同版本的A*算法找到等長的不同路徑。)

          那我們就選擇起始格右下方的格子,如圖。


          [圖5]

          這次,當(dāng)我們檢查相鄰格的時(shí)候,發(fā)現(xiàn)右側(cè)是墻,于是略過。上面一格也被略過。我們也略過了墻下面的格子。為什么呢?因?yàn)槟悴荒茉诓淮┰綁堑那闆r下直接到達(dá)那個(gè)格子。你的確需要先往下走然后到達(dá)那一格,按部就班的走過那個(gè)拐角。(注解:穿越拐角的規(guī)則是可選的。它取決于你的節(jié)點(diǎn)是如何放置的。)

          這樣一來,就剩下了其他5格。當(dāng)前格下面的另外兩個(gè)格子目前不在開啟列表中,于是我們添加他們,并且把當(dāng)前格指定為他們的父節(jié)點(diǎn)。其余3格,兩個(gè)已經(jīng)在關(guān)閉列表中(起始格,和當(dāng)前格上方的格子,在表格中藍(lán)色高亮顯示),于是我們略過它們。最后一格,在當(dāng)前格的左側(cè),將被檢查通過這條路徑,G值是否更低。不必?fù)?dān)心,我們已經(jīng)準(zhǔn)備好檢查開啟列表中的下一格了。

          我們重復(fù)這個(gè)過程,直到目標(biāo)格被添加進(jìn)關(guān)閉列表(注解),就如在下面的圖中所看到的。


          [圖6]

          注意,起始格下方格子的父節(jié)點(diǎn)已經(jīng)和前面不同的。之前它的G值是28,并且指向右上方的格子。現(xiàn)在它的G值是20,指向它上方的格子。這在尋路過程中的某處發(fā)生,當(dāng)應(yīng)用新路徑時(shí),G值經(jīng)過檢查變得低了-于是父節(jié)點(diǎn)被重新指定,G和F值被重新計(jì)算。盡管這一變化在這個(gè)例子中并不重要,在很多場合,這種變化會導(dǎo)致尋路結(jié)果的巨大變化。

          那么,我們怎么確定這條路徑呢?很簡單,從紅色的目標(biāo)格開始,按箭頭的方向朝父節(jié)點(diǎn)移動。這最終會引導(dǎo)你回到起始格,這就是你的路徑!看起來應(yīng)該像圖中那樣。從起始格A移動到目標(biāo)格B只是簡單的從每個(gè)格子(節(jié)點(diǎn))的中點(diǎn)沿路徑移動到下一個(gè),直到你到達(dá)目標(biāo)點(diǎn)。就這么簡單。


          [圖7]

          A*方法總結(jié)

          好,現(xiàn)在你已經(jīng)看完了整個(gè)說明,讓我們把每一步的操作寫在一起:

             1,把起始格添加到開啟列表。
             2,重復(fù)如下的工作:
                a) 尋找開啟列表中F值最低的格子。我們稱它為當(dāng)前格。
                b) 把它切換到關(guān)閉列表。
                c) 對相鄰的8格中的每一個(gè)?
                    * 如果它不可通過或者已經(jīng)在關(guān)閉列表中,略過它。反之如下。
                    * 如果它不在開啟列表中,把它添加進(jìn)去。把當(dāng)前格作為這一格的父節(jié)點(diǎn)。記錄這一格的F,G,和H值。
                    * 如果它已經(jīng)在開啟列表中,用G值為參考檢查新的路徑是否更好。更低的G值意味著更好的路徑。如果是這樣,就把這一格的父節(jié)點(diǎn)改成當(dāng)前格,并且重新計(jì)算這一格的G和F值。如果你保持你的開啟列表按F值排序,改變之后你可能需要重新對開啟列表排序。

                d) 停止,當(dāng)你
                    * 把目標(biāo)格添加進(jìn)了關(guān)閉列表(注解),這時(shí)候路徑被找到,或者
                    * 沒有找到目標(biāo)格,開啟列表已經(jīng)空了。這時(shí)候,路徑不存在。
             3.保存路徑。從目標(biāo)格開始,沿著每一格的父節(jié)點(diǎn)移動直到回到起始格。這就是你的路徑。

          (注解:在這篇文章的較早版本中,建議的做法是當(dāng)目標(biāo)格(或節(jié)點(diǎn))被加入到開啟列表,而不是關(guān)閉列表的時(shí)候停止尋路。這么做會更迅速,而且幾乎總是能找到最短的路徑,但不是絕對的。當(dāng)從倒數(shù)第二個(gè)節(jié)點(diǎn)到最后一個(gè)(目標(biāo)節(jié)點(diǎn))之間的移動耗費(fèi)懸殊很大時(shí)-例如剛好有一條河穿越兩個(gè)節(jié)點(diǎn)中間,這時(shí)候舊的做法和新的做法就會有顯著不同。)

          題外話

          離題一下,見諒,值得一提的是,當(dāng)你在網(wǎng)上或者相關(guān)論壇看到關(guān)于A*的不同的探討,你有時(shí)會看到一些被當(dāng)作A*算法的代碼而實(shí)際上他們不是。要使用 A*,你必須包含上面討論的所有元素--特定的開啟和關(guān)閉列表,用F,G和H作路徑評價(jià)。有很多其他的尋路算法,但他們并不是A*,A*被認(rèn)為是他們當(dāng)中最好的。Bryan Stout在這篇文章后面的參考文檔中論述了一部分,包括他們的一些優(yōu)點(diǎn)和缺點(diǎn)。有時(shí)候特定的場合其他算法會更好,但你必須很明確你在作什么。好了,夠多的了。回到文章。

          實(shí)現(xiàn)的注解

          現(xiàn)在你已經(jīng)明白了基本原理,寫你的程序的時(shí)候還得考慮一些額外的東西。下面這些材料中的一些引用了我用C++和Blitz Basic寫的程序,但對其他語言寫的代碼同樣有效。

          1.其他單位(避免碰撞):如果你恰好看了我的例子代碼,你會發(fā)現(xiàn)它完全忽略了其他單位。我的尋路者事實(shí)上可以相互穿越。取決于具體的游戲,這也許可以,也許不行。如果你打算考慮其他單位,希望他們能互相繞過,我建議你只考慮靜止或那些在計(jì)算路徑時(shí)臨近當(dāng)前單位的單位,把它們當(dāng)前的位置標(biāo)志為可通過的。對于臨近的運(yùn)動著的單位,你可以通過懲罰它們各自路徑上的節(jié)點(diǎn),來鼓勵(lì)這些尋路者找到不同的路徑(更多的描述見#2).

          如果你選擇了把其他正在移動并且遠(yuǎn)離當(dāng)前尋路單位的那些單位考慮在內(nèi),你將需要實(shí)現(xiàn)一種方法及時(shí)預(yù)測在何時(shí)何地碰撞可能會發(fā)生,以便恰當(dāng)?shù)谋苊狻7駝t你極有可能得到一條怪異的路徑,單位突然轉(zhuǎn)彎試圖避免和一個(gè)已經(jīng)不存在的單位發(fā)生碰撞。

          當(dāng)然,你也需要寫一些碰撞檢測的代碼,因?yàn)闊o論計(jì)算的時(shí)候路徑有多完美,它也會因時(shí)間而改變。當(dāng)碰撞發(fā)生時(shí),一個(gè)單位必須尋找一條新路徑,或者,如果另一個(gè)單位正在移動并且不是正面碰撞,在繼續(xù)沿當(dāng)前路徑移動之前,等待那個(gè)單位離開。

          這些提示大概可以讓你開始了。如果你想了解更多,這里有些你可能會覺得有用的鏈接:

              * 自治角色的指導(dǎo)行為:Craig Reynold在指導(dǎo)能力上的工作和尋路有些不同,但是它可以和尋路整合從而形成更完整的移動和碰撞檢測系統(tǒng)。
              * 電腦游戲中的長短距指導(dǎo):指導(dǎo)和尋路方面著作的一個(gè)有趣的考察。這是一個(gè)pdf文件。
              * 協(xié)同單位移動:一個(gè)兩部分系列文章的第一篇,內(nèi)容是關(guān)于編隊(duì)和基于分組的移動,作者是帝國時(shí)代(Age of Empires)的設(shè)計(jì)者Dave Pottinger.
              * 實(shí)現(xiàn)協(xié)同移動:Dave Pottinger文章系列的第二篇。

          2. 不同的地形損耗:在這個(gè)教程和我附帶的程序中,地形只能是二者之-可通過的和不可通過的。但是你可能會需要一些可通過的地形,但是移動耗費(fèi)更高-沼澤,小山,地牢的樓梯,等等。這些都是可通過但是比平坦的開闊地移動耗費(fèi)更高的地形。類似的,道路應(yīng)該比自然地形移動耗費(fèi)更低。

          這個(gè)問題很容易解決,只要在計(jì)算任何地形的G值的時(shí)候增加地形損耗就可以了。簡單的給它增加一些額外的損耗就可以了。由于A*算法已經(jīng)按照尋找最低耗費(fèi)的路徑來設(shè)計(jì),所以很容易處理這種情況。在我提供的這個(gè)簡單的例子里,地形只有可通過和不可通過兩種,A*會找到最短,最直接的路徑。但是在地形耗費(fèi)不同的場合,耗費(fèi)最低的路徑也許會包含很長的移動距離-就像沿著路繞過沼澤而不是直接穿過它。

          一種需額外考慮的情況是被專家稱之為“influence mapping”的東西(暫譯為影響映射圖)。就像上面描述的不同地形耗費(fèi)一樣,你可以創(chuàng)建一格額外的分?jǐn)?shù)系統(tǒng),并把它應(yīng)用到尋路的AI中。假設(shè)你有一張有大批尋路者的地圖,他們都要通過某個(gè)山區(qū)。每次電腦生成一條通過那個(gè)關(guān)口的路徑,它就會變得更擁擠。如果你愿意,你可以創(chuàng)建一個(gè)影響映射圖對有大量屠殺事件的格子施以不利影響。這會讓計(jì)算機(jī)更傾向安全些的路徑,并且?guī)椭苊饪偸莾H僅因?yàn)槁窂蕉?但可能更危險(xiǎn))而持續(xù)把隊(duì)伍和尋路者送到某一特定路徑。

          另一個(gè)可能得應(yīng)用是懲罰周圍移動單位路徑上得節(jié)點(diǎn)。A*的一個(gè)底限是,當(dāng)一群單位同時(shí)試圖尋路到接近的地點(diǎn),這通常會導(dǎo)致路徑交疊。以為一個(gè)或者多個(gè)單位都試圖走相同或者近似的路徑到達(dá)目的地。對其他單位已經(jīng)“認(rèn)領(lǐng)”了的節(jié)點(diǎn)增加一些懲罰會有助于你在一定程度上分離路徑,降低碰撞的可能性。然而,如果有必要,不要把那些節(jié)點(diǎn)看成不可通過的,因?yàn)槟闳匀幌M鄠€(gè)單位能夠一字縱隊(duì)通過擁擠的出口。同時(shí),你只能懲罰那些臨近單位的路徑,而不是所有路徑,否則你就會得到奇怪的躲避行為例如單位躲避路徑上其他已經(jīng)不在那里的單位。 還有,你應(yīng)該只懲罰路徑當(dāng)前節(jié)點(diǎn)和隨后的節(jié)點(diǎn),而不應(yīng)處理已經(jīng)走過并甩在身后的節(jié)點(diǎn)。

          3. 處理未知區(qū)域:你是否玩過這樣的PC游戲,電腦總是知道哪條路是正確的,即使它還沒有偵察過地圖?對于游戲,尋路太好會顯得不真實(shí)。幸運(yùn)的是,這是一格可以輕易解決的問題。

          答案就是為每個(gè)不同的玩家和電腦(每個(gè)玩家,而不是每個(gè)單位--那樣的話會耗費(fèi)大量的內(nèi)存)創(chuàng)建一個(gè)獨(dú)立的“knownWalkability”數(shù)組,每個(gè)數(shù)組包含玩家已經(jīng)探索過的區(qū)域,以及被當(dāng)作可通過區(qū)域的其他區(qū)域,直到被證實(shí)。用這種方法,單位會在路的死端徘徊并且導(dǎo)致錯(cuò)誤的選擇直到他們在周圍找到路。一旦地圖被探索了,尋路就像往常那樣進(jìn)行。

          4. 平滑路徑:盡管A*提供了最短,最低代價(jià)的路徑,它無法自動提供看起來平滑的路徑。看一下我們的例子最終形成的路徑(在圖7)。最初的一步是起始格的右下方,如果這一步是直接往下的話,路徑不是會更平滑一些嗎?

          有幾種方法來解決這個(gè)問題。當(dāng)計(jì)算路徑的時(shí)候可以對改變方向的格子施加不利影響,對G值增加額外的數(shù)值。也可以換種方法,你可以在路徑計(jì)算完之后沿著它跑一遍,找那些用相鄰格替換會讓路徑看起來更平滑的地方。想知道完整的結(jié)果,查看Toward More Realistic Pathfinding,一篇(免費(fèi),但是需要注冊)Marco Pinter發(fā)表在Gamasutra.com的文章

          5. 非方形搜索區(qū)域:在我們的例子里,我們使用簡單的2D方形圖。你可以不使用這種方式。你可以使用不規(guī)則形狀的區(qū)域。想想冒險(xiǎn)棋的游戲,和游戲中那些國家。你可以設(shè)計(jì)一個(gè)像那樣的尋路關(guān)卡。為此,你可能需要建立一個(gè)國家相鄰關(guān)系的表格,和從一個(gè)國家移動到另一個(gè)的G值。你也需要估算H值的方法。其他的事情就和例子中完全一樣了。當(dāng)你需要向開啟列表中添加新元素的時(shí)候,不需使用相鄰的格子,取而代之的是從表格中尋找相鄰的國家。

          類似的,你可以為一張確定的地形圖創(chuàng)建路徑點(diǎn)系統(tǒng),路徑點(diǎn)一般是路上,或者地牢通道的轉(zhuǎn)折點(diǎn)。作為游戲設(shè)計(jì)者,你可以預(yù)設(shè)這些路徑點(diǎn)。兩個(gè)路徑點(diǎn)被認(rèn)為是相鄰的如果他們之間的直線上沒有障礙的話。在冒險(xiǎn)棋的例子里,你可以保存這些相鄰信息在某個(gè)表格里,當(dāng)需要在開啟列表中添加元素的時(shí)候使用它。然后你就可以記錄關(guān)聯(lián)的G值(可能使用兩點(diǎn)間的直線距離),H值(可以使用到目標(biāo)點(diǎn)的直線距離),其他都按原先的做就可以了。

          Amit Patel 寫了其他方法的摘要。另一個(gè)在非方形區(qū)域搜索RPG地圖的例子,查看我的文章Two-Tiered A* Pathfinding。(譯者注:譯文:  A*分層尋路)

          6. 一些速度方面的提示:當(dāng)你開發(fā)你自己的A*程序,或者改寫我的,你會發(fā)現(xiàn)尋路占據(jù)了大量的CPU時(shí)間,尤其是在大地圖上有大量對象在尋路的時(shí)候。如果你閱讀過網(wǎng)上的其他材料,你會明白,即使是開發(fā)了星際爭霸或帝國時(shí)代的專家,這也無可奈何。如果你覺得尋路太過緩慢,這里有一些建議也許有效:

              * 使用更小的地圖或者更少的尋路者。

              * 不要同時(shí)給多個(gè)對象尋路。取而代之的是把他們加入一個(gè)隊(duì)列,把尋路過程分散在幾個(gè)游戲周期中。如果你的游戲以40周期每秒的速度運(yùn)行,沒人能察覺。但是當(dāng)大量尋路者計(jì)算自己路徑的時(shí)候,他們會發(fā)覺游戲速度突然變慢。

              * 盡量使用更大的地圖網(wǎng)格。這降低了尋路中搜索的總網(wǎng)格數(shù)。如果你有志氣,你可以設(shè)計(jì)兩個(gè)或者更多尋路系統(tǒng)以便使用在不同場合,取決于路徑的長度。這也正是專業(yè)人士的做法,用大的區(qū)域計(jì)算長的路徑,然后在接近目標(biāo)的時(shí)候切換到使用小格子/區(qū)域的精細(xì)尋路。如果你對這個(gè)觀點(diǎn)感興趣,查閱我的文章Two-Tiered A* Pathfinding。(譯者注:譯文 :A*分層尋路)

              * 使用路徑點(diǎn)系統(tǒng)計(jì)算長路徑,或者預(yù)先計(jì)算好路徑并加入到游戲中。
             
              * 預(yù)處理你的地圖,表明地圖中哪些區(qū)域是不可到達(dá)的。我把這些區(qū)域稱作“孤島”。事實(shí)上,他們可以是島嶼或其他被墻壁包圍等無法到達(dá)的任意區(qū)域。A*的下限是,當(dāng)你告訴它要尋找通往那些區(qū)域的路徑時(shí),它會搜索整個(gè)地圖,直到所有可到達(dá)的方格/節(jié)點(diǎn)都被通過開啟列表和關(guān)閉列表的計(jì)算。這會浪費(fèi)大量的CPU時(shí)間。可以通過預(yù)先確定這些區(qū)域(比如通過flood-fill或類似的方法)來避免這種情況的發(fā)生,用某些種類的數(shù)組記錄這些信息,在開始尋路前檢查它。
             
              * 在一個(gè)擁擠的類似迷宮的場合,把不能連通的節(jié)點(diǎn)看作死端。這些區(qū)域可以在地圖編輯器中預(yù)先手動指定,或者如果你有雄心壯志,開發(fā)一個(gè)自動識別這些區(qū)域的算法。給定死端的所有節(jié)點(diǎn)可以被賦予一個(gè)唯一的標(biāo)志數(shù)字。然后你就可以在尋路過程中安全的忽略所有死端,只有當(dāng)起點(diǎn)或者終點(diǎn)恰好在死端的某個(gè)節(jié)點(diǎn)的時(shí)候才需要考慮它們。

          7. 維護(hù)開啟列表:這是A*尋路算法最重要的組成部分。每次你訪問開啟列表,你都需要尋找F值最低的方格。有幾種不同的方法實(shí)現(xiàn)這一點(diǎn)。你可以把路徑元素隨意保存,當(dāng)需要尋找F值最低的元素的時(shí)候,遍歷開啟列表。這很簡單,但是太慢了,尤其是對長路徑來說。這可以通過維護(hù)一格排好序的列表來改善,每次尋找F值最低的方格只需要選取列表的首元素。當(dāng)我自己實(shí)現(xiàn)的時(shí)候,這種方法是我的首選。

          在小地圖。這種方法工作的很好,但它并不是最快的解決方案。更苛求速度的A*程序員使用叫做二叉堆的方法,這也是我在代碼中使用的方法。憑我的經(jīng)驗(yàn),這種方法在大多數(shù)場合會快2~3倍,并且在長路經(jīng)上速度呈幾何級數(shù)提升(10倍以上速度)。如果你想了解更多關(guān)于二叉堆的內(nèi)容,查閱我的文章,Using Binary Heaps in A* Pathfinding。(譯者注:譯文:在A*尋路中使用二叉堆)

          另一個(gè)可能的瓶頸是你在多次尋路之間清除和保存你的數(shù)據(jù)結(jié)構(gòu)的方法。我個(gè)人更傾向把所有東西都存儲在數(shù)組里面。雖然節(jié)點(diǎn)可以以面向?qū)ο蟮娘L(fēng)格被動態(tài)的產(chǎn)生,記錄和保存,我發(fā)現(xiàn)創(chuàng)建和刪除對象所增加的大量時(shí)間,以及多余的管理層次減慢的整個(gè)過程的速度。但是,如果你使用數(shù)組,你需要在調(diào)用之間清理數(shù)據(jù)。這中情形你想做的最后一件事就是在尋路調(diào)用之后花點(diǎn)時(shí)間把一切歸零,尤其是你的地圖很大的時(shí)候。

          我通過使用一個(gè)叫做whichList(x,y)的二維數(shù)組避免這種開銷,數(shù)組的每個(gè)元素表明了節(jié)點(diǎn)在開啟列表還是在關(guān)閉列表中。嘗試尋路之后,我沒有清零這個(gè)數(shù)組。取而代之的是,我在新的尋路中重置onClosedList和onOpenList的數(shù)值,每次尋路兩個(gè)都+5或者類似其他數(shù)值。這種方法,算法可以安全的跳過前面尋路留下的臟數(shù)據(jù)。我還在數(shù)組中儲存了諸如F,G和H的值。這樣一來,我只需簡單的重寫任何已經(jīng)存在的值而無需被清除數(shù)組的操作干擾。將數(shù)據(jù)存儲在多維數(shù)組中需要更多內(nèi)存,所以這里需要權(quán)衡利弊。最后,你應(yīng)該使用你最得心應(yīng)手的方法。

          8. Dijkstra的算法:盡管A*被認(rèn)為是通常最好的尋路算法(看前面的“題外話”),還是有一種另外的算法有它的可取之處-Dijkstra算法。 Dijkstra算法和A*本質(zhì)是相同的,只有一點(diǎn)不同,就是Dijkstra算法沒有啟發(fā)式(H值總是0)。由于沒有啟發(fā)式,它在各個(gè)方向上平均搜索。正如你所預(yù)料,由于Dijkstra算法在找到目標(biāo)前通常會探索更大的區(qū)域,所以一般會比A*更慢一些。

          那么為什么要使用這種算法呢?因?yàn)橛袝r(shí)候我們并不知道目標(biāo)的位置。比如說你有一個(gè)資源采集單位,需要獲取某種類型的資源若干。它可能知道幾個(gè)資源區(qū)域,但是它想去最近的那個(gè)。這種情況,Dijkstra算法就比A*更適合,因?yàn)槲覀儾恢滥膫€(gè)更近。用A*,我們唯一的選擇是依次對每個(gè)目標(biāo)許路并計(jì)算距離,然后選擇最近的路徑。我們尋找的目標(biāo)可能會有不計(jì)其數(shù)的位置,我們只想找其中最近的,而我們并不知道它在哪里,或者不知道哪個(gè)是最近的。

          進(jìn)一步的閱讀

          好,現(xiàn)在你對一些進(jìn)一步的觀點(diǎn)有了初步認(rèn)識。這時(shí),我建議你研究我的源代碼。包里面包含兩個(gè)版本,一個(gè)是用C++寫的,另一個(gè)用Blitz Basic。順便說一句,兩個(gè)版本都注釋詳盡,容易閱讀,這里是鏈接。

              * 例子代碼: A* Pathfinder (2D) Version 1.9

          如果你既不用C++也不用Blitz Basic,在C++版本里有兩個(gè)小的可執(zhí)行文件。Blitz Basic可以在從Blitz Basic網(wǎng)站免費(fèi)下載的Blitz Basic 3D(不是Blitz Plus)演示版上運(yùn)行。Ben O'Neill提供一個(gè)聯(lián)機(jī)演示可以在這里找到。

          你也該看看以下的網(wǎng)頁。讀了這篇教程后,他們應(yīng)該變得容易理解多了。

              * Amit的 A* 頁面:這是由Amit Patel制作,被廣泛引用的頁面,如果你沒有事先讀這篇文章,可能會有點(diǎn)難以理解。值得一看。尤其要看Amit關(guān)于這個(gè)問題的自己的看法
              * Smart Moves:智能尋路:Bryan Stout發(fā)表在Gamasutra.com的這篇文章需要注冊才能閱讀。注冊是免費(fèi)的而且比起這篇文章和網(wǎng)站的其他資源,是非常物有所值的。Bryan用Delphi寫的程序幫助我學(xué)習(xí)A*,也是我的A*代碼的靈感之源。它還描述了A*的幾種變化。
              * 地形分析:這是一格高階,但是有趣的話題,Dave Pottinge撰寫,Ensemble Studios的專家。這家伙參與了帝國時(shí)代和君王時(shí)代的開發(fā)。別指望看懂這里所有的東西,但是這是篇有趣的文章也許會讓你產(chǎn)生自己的想法。它包含一些對 mip-mapping,influence mapping以及其他一些高級AI/尋路觀點(diǎn)。對"flood filling"的討論使我有了我自己的“死端”和“孤島”的代碼的靈感,這些包含在我Blitz版本的代碼中。

          其他一些值得一看的網(wǎng)站:

              * aiGuru: Pathfinding
              * Game AI Resource: Pathfinding
              * GameDev.net: Pathfinding

          我同樣高度推薦下面這幾本書, 里面有很多關(guān)于尋路和其他AI話題的文章。 它們也附帶了實(shí)例代碼的CD。這些書我都買了。另外,如果你通過下面的鏈接購買了它們,我會從Amazon得到幾個(gè)美分。:)

          好了,這就是全部。如果你剛好寫一個(gè)運(yùn)用這些觀點(diǎn)的程序,我想拜讀一下。你可以這樣聯(lián)系我:

          現(xiàn)在,好運(yùn)!

          譯者參考文獻(xiàn):
           在A*尋路中使用二叉堆

          A*分層尋

           

           


          游戲算法整理 算法二:碰撞


          1.   碰撞檢測和響應(yīng)

          碰撞在游戲中運(yùn)用的是非常廣泛的,運(yùn)用理論實(shí)現(xiàn)的碰撞,再加上一些小技巧,可以讓碰撞檢測做得非常精確,效率也非常高。從而增加游戲的功能和可玩性。

          2D碰撞檢測

          2D的碰撞檢測已經(jīng)非常穩(wěn)定,可以在許多著作和論文中查詢到。3D的碰撞還沒有找到最好的方法,現(xiàn)在使用的大多數(shù)方法都是建立在2D基礎(chǔ)上的。

          碰撞檢測

          碰撞的檢測不僅僅是運(yùn)用在游戲中,事實(shí)上,一開始的時(shí)候是運(yùn)用在模擬和機(jī)器人技術(shù)上的。這些工業(yè)上的碰撞檢測要求非常高,而碰撞以后的響應(yīng)也是需要符合現(xiàn)實(shí)生活的,是需要符合人類常規(guī)認(rèn)識的。游戲中的碰撞有些許的不一樣,況且,更重要的,我們制作的東西充其量是商業(yè)級別,還不需要接觸到紛繁復(fù)雜的數(shù)學(xué)公式。

          圖1

          最理想的碰撞,我想莫過于上圖,完全按照多邊形的外形和運(yùn)行路徑規(guī)劃一個(gè)范圍,在這個(gè)范圍當(dāng)中尋找會產(chǎn)生阻擋的物體,不管是什么物體,產(chǎn)生阻擋以后,我們運(yùn)動的物體都必須在那個(gè)位置產(chǎn)生一個(gè)碰撞的事件。最美好的想法總是在實(shí)現(xiàn)上有一些困難,事實(shí)上我們可以這么做,但是效率卻是非常非常低下的,游戲中,甚至于工業(yè)中無法忍受這種速度,所以我們改用其它的方法來實(shí)現(xiàn)。

          圖2

          最簡單的方法如上圖,我們尋找物體的中心點(diǎn),然后用這個(gè)中心點(diǎn)來畫一個(gè)圓,如果是一個(gè)3D的物體,那么我們要畫的就是一個(gè)球體。在檢測物體碰撞的時(shí)候,我們只要檢測兩個(gè)物體的半徑相加是否大于這兩個(gè)物體圓心的實(shí)際距離。

          圖3

          這個(gè)算法是最簡單的一種,現(xiàn)在還在用,但是不是用來做精確的碰撞檢測,而是用來提高效率的模糊碰撞檢測查詢,到了這個(gè)范圍以后,再進(jìn)行更加精密的碰撞檢測。一種比較精密的碰撞檢測查詢就是繼續(xù)這種畫圓的思路,然后把物體細(xì)分,對于物體的每個(gè)部件繼續(xù)畫圓,然后再繼續(xù)進(jìn)行碰撞檢測,直到系統(tǒng)規(guī)定的,可以容忍的誤差范圍以后才觸發(fā)碰撞事件,進(jìn)行碰撞的一些操作。

          有沒有更加簡單的方法呢?2D游戲中有許多圖片都是方方正正的,所以我們不必把碰撞的范圍畫成一個(gè)圓的,而是畫成一個(gè)方的。這個(gè)正方形,或者說是一個(gè)四邊形和坐標(biāo)軸是對齊的,所以運(yùn)用數(shù)學(xué)上的一些方法,比如距離計(jì)算等還是比較方便的。這個(gè)檢測方法就叫AABBs(Axis-aligned Bounding Boxes)碰撞檢測,游戲中已經(jīng)運(yùn)用的非常廣泛了,因?yàn)槠渌俣瓤欤矢撸?jì)算起來非常方便,精確度也是可以忍受的。

          做到這一步,許多游戲的需求都已經(jīng)滿足了。但是,總是有人希望近一步優(yōu)化,而且方法也是非常陳舊的:繼續(xù)對物體的各個(gè)部分進(jìn)行細(xì)分,對每個(gè)部件做AABB 的矩形,那這個(gè)優(yōu)化以后的系統(tǒng)就叫做OBB系統(tǒng)。雖然說這個(gè)優(yōu)化以后的系統(tǒng)也不錯(cuò),但是,許多它可以運(yùn)用到的地方,別人卻不愛使用它,這是后面會繼續(xù)介紹的地方。

          John Carmack不知道看的哪本書,他早在DOOM中已經(jīng)使用了BSP系統(tǒng)(二分空間分割),再加上一些小技巧,他的碰撞做得就非常好了,再加上他發(fā)明的 castray算法,DOOM已經(jīng)不存在碰撞的問題,解決了這樣的關(guān)鍵技術(shù),我想他不再需要在什么地方分心了,只要繼續(xù)研究渲染引擎就可以了。(Windows游戲編程大師技巧P392~P393介紹)(凸多邊形,多邊形退化,左手定律)SAT系統(tǒng)非常復(fù)雜,是SHT(separating hyperplane theorem,分離超平面理論)的一種特殊情況。這個(gè)理論闡述的就是兩個(gè)不相關(guān)的曲面,是否能夠被一個(gè)超平面所分割開來,所謂分割開來的意思就是一個(gè)曲面貼在平面的一邊,而另一個(gè)曲面貼在平面的另一邊。我理解的就是有點(diǎn)像相切的意思。SAT是SHT的特殊情況,所指的就是兩個(gè)曲面都是一些多邊形,而那個(gè)超平面也是一個(gè)多邊形,這個(gè)超平面的多邊形可以在場景中的多邊形列表中找到,而超平面可能就是某個(gè)多邊形的表面,很巧的就是,這個(gè)表面的法線和兩個(gè)曲面的切面是相對應(yīng)的。接下來的證明,我想是非常復(fù)雜的事情,希望今后能夠找到源代碼直接運(yùn)用上去。而我們現(xiàn)在講究的快速開發(fā),我想AABB就足以滿足了。

          3D碰撞檢測

          3D的檢測就沒有什么很標(biāo)準(zhǔn)的理論了,都建立在2D的基礎(chǔ)上,我們可以沿用AABB或者OBB,或者先用球體做粗略的檢測,然后用AABB和OBB作精細(xì)的檢測。BSP技術(shù)不流行,但是效率不錯(cuò)。微軟提供了D3DIntersect函數(shù)讓大家使用,方便了許多,但是和通常一樣,當(dāng)物體多了以后就不好用了,明顯的就是速度慢許多。

          碰撞反應(yīng)

          碰撞以后我們需要做一些反應(yīng),比如說產(chǎn)生反沖力讓我們反彈出去,或者停下來,或者讓阻擋我們的物體飛出去,或者穿墻,碰撞最討厭的就是穿越,本來就不合邏輯,查閱了那么多資料以后,從來沒有看到過需要穿越的碰撞,有摩擦力是另外一回事。首先看看彈性碰撞。彈性碰撞就是我們初中物理中說的動量守恒。物體在碰撞前后的動量守恒,沒有任何能量損失。這樣的碰撞運(yùn)用于打磚塊的游戲中。引入質(zhì)量的話,有的物體會是有一定的質(zhì)量,這些物體通常來說是需要在碰撞以后進(jìn)行另外一個(gè)方向的運(yùn)動的,另外一些物體是設(shè)定為質(zhì)量無限大的,這些物體通常是碰撞墻壁。

          當(dāng)物體碰到質(zhì)量非常大的物體,默認(rèn)為碰到了一個(gè)彈性物體,其速度會改變,但是能量不會受到損失。一般在代碼上的做法就是在速度向量上加上一個(gè)負(fù)號。

          絕對的彈性碰撞是很少有的,大多數(shù)情況下我們運(yùn)用的還是非彈性碰撞。我們現(xiàn)在玩的大多數(shù)游戲都用的是很接近現(xiàn)實(shí)的非彈性碰撞,例如Pain-Killer 中的那把吸力槍,它彈出去的子彈吸附到NPC身上時(shí)的碰撞響應(yīng)就是非彈性碰撞;那把殘忍的分尸刀把墻打碎的初始算法就是一個(gè)非彈性碰撞,其后使用的剛體力學(xué)就是先建立在這個(gè)算法上的。那么,是的,如果需要非彈性碰撞,我們需要介入摩擦力這個(gè)因素,而我們也無法簡單使用動量守恒這個(gè)公式。

          我們可以采取比較簡單的方法,假設(shè)摩擦系數(shù)μ非常大,那么只要物體接觸,并且擁有一個(gè)加速度,就可以產(chǎn)生一個(gè)無窮大的摩擦力,造成物體停止的狀態(tài)。

          基于別人的引擎寫出一個(gè)讓自己滿意的碰撞是不容易的,那么如果自己建立一個(gè)碰撞系統(tǒng)的話,以下內(nèi)容是無法缺少的:

          –     一個(gè)能夠容忍的碰撞系統(tǒng)

          –     一個(gè)從概念上可以接受的物理系統(tǒng)

          –     質(zhì)量

          –     速度

          –     摩擦系數(shù)

          –     地心引力

          http://www.gamasutra.com/features/20000330/bobic_01.htm
          http://www.gamasutra.com/features/20000330/bobic_02.htm
          http://www.gamasutra.com/features/20000330/bobic_03.htm

          這三篇是高級碰撞檢測。

           

           


          游戲算法整理 算法三:尋路算法新思維

          目前常用尋路算法是A*方式,原理是通過不斷搜索逼近目的地的路點(diǎn)來獲得。

          如果通過圖像模擬搜索點(diǎn),可以發(fā)現(xiàn):非啟發(fā)式的尋路算法實(shí)際上是一種窮舉法,通過固定順序依次搜索人物周圍的路點(diǎn),直到找到目的地,搜索點(diǎn)在圖像上的表現(xiàn)為一個(gè)不斷擴(kuò)大的矩形。如下:

             

          很快人們發(fā)現(xiàn)如此窮舉導(dǎo)致搜索速度過慢,而且不是很符合邏輯,試想:如果要從(0,0)點(diǎn)到達(dá)(100,0)點(diǎn),如果每次向東搜索時(shí)能夠走通,那么干嗎還要搜索其他方向呢?所以,出現(xiàn)了啟發(fā)式的A*尋路算法,一般通過 已經(jīng)走過的路程 + 到達(dá)目的地的直線距離 代價(jià)值作為搜索時(shí)的啟發(fā)條件,每個(gè)點(diǎn)建立一個(gè)代價(jià)值,每次搜索時(shí)就從代價(jià)低的最先搜索,如下:

             

          綜上所述,以上的搜索是一種矩陣式的不斷逼近終點(diǎn)的搜索做法。優(yōu)點(diǎn)是比較直觀,缺點(diǎn)在于距離越遠(yuǎn)、搜索時(shí)間越長。

          現(xiàn)在,我提出一種新的AI尋路方式——矢量尋路算法

          通過觀察,我們可以發(fā)現(xiàn),所有的最優(yōu)路線,如果是一條折線,那么、其每一個(gè)拐彎點(diǎn)一定發(fā)生在障礙物的突出邊角,而不會在還沒有碰到障礙物就拐彎的情況:如下圖所示:

           

          我們可以發(fā)現(xiàn),所有的紅色拐彎點(diǎn)都是在障礙物(可以認(rèn)為是一個(gè)凸多邊形)的頂點(diǎn)處,所以,我們搜索路徑時(shí),其實(shí)只需要搜索這些凸多邊形頂點(diǎn)不就可以了嗎?如果將各個(gè)頂點(diǎn)連接成一條通路就找到了最優(yōu)路線,而不需要每個(gè)點(diǎn)都檢索一次,這樣就大大減少了搜索次數(shù),不會因?yàn)榫嚯x的增大而增大搜索時(shí)間。

          這種思路我尚未將其演變?yōu)樗惴ǎ们姨岢鲆粋€(gè)偽程序給各位參考:

          1.建立各個(gè)凸多邊形頂點(diǎn)的通路表TAB,表示頂點(diǎn)A到頂點(diǎn)B是否可達(dá),將可達(dá)的頂點(diǎn)分組保存下來。如: ( (0,0) (100,0) ),這一步驟在程序剛開始時(shí)完成,不要放在搜索過程中空耗時(shí)間。

          2.開始搜索A點(diǎn)到B點(diǎn)的路線

          3.檢測A點(diǎn)可以直達(dá)凸多邊形頂點(diǎn)中的哪一些,挑選出最合適的頂點(diǎn)X1。

          4.檢測與X1相連(能夠接通)的有哪些頂點(diǎn),挑出最合適的頂點(diǎn)X2。

          5.X2是否是終點(diǎn)B?是的話結(jié)束,否則轉(zhuǎn)步驟4(X2代入X1)

          如此下來,搜索只發(fā)生在凸多邊形的頂點(diǎn),節(jié)省了大量的搜索時(shí)間,而且找到的路線無需再修剪鋸齒,保證了路線的最優(yōu)性。

          這種方法搜索理論上可以減少大量搜索點(diǎn)、缺點(diǎn)是需要實(shí)現(xiàn)用一段程序得出TAB表,從本質(zhì)上來說是一種空間換時(shí)間的方法,而且搜索時(shí)A*能夠用的啟發(fā)條件,在矢量搜索時(shí)依然可以使用。

           

           


          游戲算法整理 算法四:戰(zhàn)略游戲中的戰(zhàn)爭模型算法的初步探討

           
            《三國志》系列游戲相信大家都有所了解,而其中的(宏觀)戰(zhàn)斗時(shí)關(guān)于雙方兵力,士氣,兵種克制,攻擊力,增援以及隨戰(zhàn)爭進(jìn)行兵力減少等數(shù)值的算法是十分值得研究的。或許是由于簡單的緣故,我在網(wǎng)上幾乎沒有找到相關(guān)算法的文章。下面給出這個(gè)戰(zhàn)爭的數(shù)學(xué)模型算法可以保證游戲中戰(zhàn)爭的游戲性與真實(shí)性兼顧,希望可以給有需要這方面開發(fā)的人一些啟迪。
          假設(shè)用x(t)和y(t)表示甲乙交戰(zhàn)雙方在t時(shí)刻的兵力,如果是開始時(shí)可視為雙方士兵人數(shù)。

            假設(shè)每一方的戰(zhàn)斗減員率取決于雙方兵力和戰(zhàn)斗力,用f(x,y)和g(x,y)表示,每一方的增援率是給定函數(shù)用u(t)和v(t)表示。

            如果雙方用正規(guī)部隊(duì)作戰(zhàn)(可假設(shè)是相同兵種),先分析甲方的戰(zhàn)斗減員率f(x,y)。可知甲方士兵公開活動,處于乙方?jīng)]一個(gè)士兵的監(jiān)視和殺傷范圍之內(nèi),一但甲方的某個(gè)士兵被殺傷,乙方的火力立即集中在其余士兵身上,所以甲方的戰(zhàn)斗減員率只與乙方的兵力有關(guān)可射為f與y成正比,即f=ay,a表示乙方平均每個(gè)士兵對甲方士兵的殺傷率(單位時(shí)間的殺傷數(shù)),成為乙方的戰(zhàn)斗有效系數(shù)。類似g= -bx
          這個(gè)戰(zhàn)爭模型模型方程1為

          x’(t)= -a*y(t)+u(t) x’(t)是x(t)對于t 的導(dǎo)數(shù)
          y’(t)= -b*x(t)+v(t) y’(t)是y(t)對于t的導(dǎo)數(shù)

          利用給定的初始兵力,戰(zhàn)爭持續(xù)時(shí)間,和增援兵力可以求出雙方兵力在戰(zhàn)爭中的變化函數(shù)。
          (本文中解法略)

          如果考慮由于士氣和疾病等引起的非戰(zhàn)斗減員率(一般與本放兵力成正比,設(shè)甲乙雙方分別為h,w)

          可得到改進(jìn)戰(zhàn)爭模型方程2:

          x’(t)= -a*y(t)-h*x(t)+u(t)
          y’(t)= -b*x(t)-w*y(t)+v(t)

          利用初始條件同樣可以得到雙方兵力在戰(zhàn)爭中的變化函數(shù)和戰(zhàn)爭結(jié)果。

          此外還有不同兵種作戰(zhàn)(兵種克制)的數(shù)學(xué)模型:
          模型1中的戰(zhàn)斗有效系數(shù)a可以進(jìn)一步分解為a=ry*py*(sry/sx),其中ry是乙方的攻擊率(每個(gè)士兵單位的攻擊次數(shù)),py是每次攻擊的命中率。(sry/sx)是乙方攻擊的有效面積sry與甲方活動范圍sx之比。類似甲方的戰(zhàn)斗有效系數(shù)b=rx*px*(srx/sy),rx和px是甲方的攻擊率和命中率,(srx/sy)是甲方攻擊的有效面積與乙方活動范圍sy之比。由于增加了兵種克制的攻擊范圍,所以戰(zhàn)斗減員率不光與對方兵力有關(guān),而且隨著己放兵力增加而增加。因?yàn)樵谝欢▍^(qū)域內(nèi),士兵越多被殺傷的就越多。

          方程
          x’(t)= -ry*py*(sry/sx)*x(t)*y(t)-h*x(t)+u(t)
          y’(t)= -rx*px*(srx/sy)*x(t)*y(t)-w*y(t)+u(t)

           

           


          游戲算法整理 算法五:飛行射擊游戲中的碰撞檢測

            在游戲中物體的碰撞是經(jīng)常發(fā)生的,怎樣檢測物體的碰撞是一個(gè)很關(guān)鍵的技術(shù)問題。在RPG游戲中,一般都將場景分為許多矩形的單元,碰撞的問題被大大的簡化了,只要判斷精靈所在的單元是不是有其它的東西就可以了。而在飛行射擊游戲(包括象荒野大鏢客這樣的射擊游戲)中,碰撞卻是最關(guān)鍵的技術(shù),如果不能很好的解決,會影響玩游戲者的興趣。因?yàn)轱w行射擊游戲說白了就是碰撞的游戲——躲避敵人的子彈或飛機(jī),同時(shí)用自己的子彈去碰撞敵人。

            碰撞,這很簡單嘛,只要兩個(gè)物體的中心點(diǎn)距離小于它們的半徑之和就可以了。確實(shí),而且我也看到很多人是這樣做的,但是,這只適合圓形的物體——圓形的半徑處處相等。如果我們要碰撞的物體是兩艘威力巨大的太空飛船,它是三角形或矩形或其他的什么形狀,就會出現(xiàn)讓人尷尬的情景:兩艘飛船眼看就要擦肩而過,卻出人意料的發(fā)生了爆炸;或者敵人的子彈穿透了你的飛船的右弦,你卻安然無恙,這不是我們希望發(fā)生的。于是,我們需要一種精確的檢測方法。

            那么,怎樣才能達(dá)到我們的要求呢?其實(shí)我們的前輩們已經(jīng)總結(jié)了許多這方面的經(jīng)驗(yàn),如上所述的半徑檢測法,三維中的標(biāo)準(zhǔn)平臺方程法,邊界框法等等。大多數(shù)游戲程序員都喜歡用邊界框法,這也是我采用的方法。邊界框是在編程中加進(jìn)去的不可見的邊界。邊界框法,顧名思義,就是用邊界框來檢測物體是否發(fā)生了碰撞,如果兩個(gè)物體的邊界框相互干擾,則發(fā)生了碰撞。用什么樣的邊界框要視不同情況而定,用最近似的幾何形狀。當(dāng)然,你可以用物體的準(zhǔn)確幾何形狀作邊界框,但出于效率的考慮,我不贊成這樣做,因?yàn)橛螒蛑械奈矬w一般都很復(fù)雜,用復(fù)雜的邊界框?qū)⒃黾哟罅康挠?jì)算,尤其是浮點(diǎn)計(jì)算,而這正是我們想盡量避免的。但邊界框也不能與準(zhǔn)確幾何形狀有太大的出入,否則就象用半徑法一樣出現(xiàn)奇怪的現(xiàn)象。

            在飛行射擊游戲中,我們的飛機(jī)大多都是三角形的,我們可以用三角形作近似的邊界框。現(xiàn)在我們假設(shè)飛機(jī)是一個(gè)正三角形(或等要三角形,我想如果誰把飛機(jī)設(shè)計(jì)成左右不對稱的怪物,那他的審美觀一定有問題),我的飛機(jī)是正著的、向上飛的三角形,敵人的飛機(jī)是倒著的、向下飛的三角形,且飛機(jī)不會旋轉(zhuǎn)(大部分游戲中都是這樣的)。我們可以這樣定義飛機(jī):中心點(diǎn)O(Xo,Yo),三個(gè)頂點(diǎn)P0(X0,Y0)、P1(X1,Y1)、P2(X2,Y2)。中心點(diǎn)為正三角形的中心點(diǎn),即中心點(diǎn)到三個(gè)頂點(diǎn)的距離相等。接下來的問題是怎樣確定兩個(gè)三角形互相干擾了呢?嗯,現(xiàn)在我們接觸到問題的實(shí)質(zhì)了。如果你學(xué)過平面解析幾何,我相信你可以想出許多方法解決這個(gè)問題。判斷一個(gè)三角形的各個(gè)頂點(diǎn)是否在另一個(gè)三角形里面,看起來是個(gè)不錯(cuò)的方法,你可以這樣做,但我卻發(fā)現(xiàn)一個(gè)小問題:一個(gè)三角形的頂點(diǎn)沒有在另一個(gè)三角形的里面,卻可能發(fā)生了碰撞,因?yàn)榱硪粋€(gè)三角形的頂點(diǎn)在這個(gè)三角形的里面,所以要判斷兩次,這很麻煩。有沒有一次判斷就可以的方法?我們把三角形放到極坐標(biāo)平面中,中心點(diǎn)為原點(diǎn),水平線即X軸為零度角。我們發(fā)現(xiàn)三角形成了這個(gè)樣子:在每個(gè)角度我們都可以找到一個(gè)距離,用以描述三角形的邊。既然我們找到了邊到中心點(diǎn)的距離,那就可以用這個(gè)距離來檢測碰撞。如圖一,兩個(gè)三角形中心點(diǎn)坐標(biāo)分別為(Xo,Yo)和 (Xo1,Yo1),由這兩個(gè)點(diǎn)的坐標(biāo)求出兩點(diǎn)的距離及兩點(diǎn)連線和X軸的夾角θ,再由θ求出中心點(diǎn)連線與三角形邊的交點(diǎn)到中心點(diǎn)的距離,用這個(gè)距離與兩中心點(diǎn)距離比較,從而判斷兩三角形是否碰撞。因?yàn)槿切巫笥覍ΨQ,所以θ取-90~90度區(qū)間就可以了。哈,現(xiàn)在問題有趣多了,-90~90度區(qū)間正是正切函數(shù)的定義域,求出θ之后再找對應(yīng)的邊到中心點(diǎn)的距離就容易多了,利用幾何知識,如圖二,將三角形的邊分為三部分,即圖2中紅綠藍(lán)三部分,根據(jù)θ在那一部分而分別對待。用正弦定理求出邊到中心點(diǎn)的距離,即圖2中淺綠色線段的長度。但是,如果飛機(jī)每次移動都這樣判斷一次,效率仍然很低。我們可以結(jié)合半徑法來解決,先用半徑法判斷是否可能發(fā)生碰撞,如果可能發(fā)生碰撞,再用上面的方法精確判斷是不是真的發(fā)生了碰撞,這樣基本就可以了。如果飛機(jī)旋轉(zhuǎn)了怎么辦呢,例如,如圖三所示飛機(jī)旋轉(zhuǎn)了一個(gè)角度α,仔細(xì)觀察圖三會發(fā)現(xiàn),用(θ-α)就可以求出邊到中心點(diǎn)的距離,這時(shí)你要注意邊界情況,即(θ-α)可能大于90度或小于-90度。啰羅嗦嗦說了這么多,不知道大家明白了沒有。我編寫了一個(gè)簡單的例程,用于說明我的意圖。在例子中假設(shè)所有飛機(jī)的大小都一樣,并且沒有旋轉(zhuǎn)。



          /////////////////////////////////////////////////////////////////////
                      //example.cpp
                      //碰撞檢測演示
                      //作者 李韜
                      /////////////////////////////////////////////////////////////////////
                      //限于篇幅,這里只給出了碰撞檢測的函數(shù)
                      //define/////////////////////////////////////////////////////////////
                      #define NUM_VERTICES 3
                      #define ang_30 -0.5236
                      #define ang60    1.0472
                      #define ang120 2.0944
                      //deftype////////////////////////////////////////////////////////////
                      struct object
                      {
                      float xo, yo;
                      float radio;
                      float x_vel, y_vel;
                      float vertices[NUM_VERTICES][2];
                      }
                       
                      //faction/////////////////////////////////////////////////////////////
                      //根據(jù)角度求距離
                      float AngToDis(struct object obj, float angle)
                      {
                      float dis, R;
                      R = obj.radius;
                      if (angle <= ang_30)
                      dis = R / (2 * sin(-angle));
                      else if (angle >= 0)
                      dis = R / (2 * sin(angle + ang60));
                      else dis = R / (2 * sin(ang120 - angle));
                      return dis;
                      }
                       
                      //碰撞檢測
                      int CheckHit(struct object obj1, struct object obj2)
                      {
                      float deltaX, deltaY, angle, distance, bumpdis;
                      deltaX = abs(obj1.xo - obj2.xo);
                      deltaY = obj1.yo - obj2.yo;
                      distance = sqrt(deltaX * deltaX + deltaY * deltaY);
                      if (distance <= obj.radio)
                      {
                      angle = atan2(deltaY, deltaX);
                      bumpdis1 = AngToDis(obj1, angle);
                      return (distance <= 2 * bumpdis);
                      }
                      ruturn 0;
                      }
                      //End//////////////////////////////////////////////////////////////

            上面程序只是用于演示,并不適合放在游戲中,但你應(yīng)該明白它的意思,以便寫出適合你自己的碰撞檢測。游戲中的情況是多種多樣的,沒有哪種方法能適應(yīng)所有情況,你一定能根據(jù)自己的情況找到最適合自己的方法。

           

           


          游戲算法整理 算法六:關(guān)于SLG中人物可到達(dá)范圍計(jì)算的想法

          下面的沒有經(jīng)過實(shí)踐,因此很可能是錯(cuò)誤的,覺得有用的初學(xué)朋友讀一讀吧:)
          希望高人指點(diǎn)一二 :)

          簡介:
          在標(biāo)準(zhǔn)的SLG游戲中,當(dāng)在一個(gè)人物處按下鼠標(biāo)時(shí),會以人物為中心,向四周生成一個(gè)菱形的可移動區(qū)范圍,如下:

              0
            000
          00s00
            000
             0

          這個(gè)圖形在剛開始學(xué)習(xí)PASCAL時(shí)就應(yīng)該寫過一個(gè)畫圖的程序(是否有人懷念?)。那個(gè)圖形和SLG的擴(kuò)展路徑一樣。

          一、如何生成路徑:
          從人物所在的位置開始,向四周的四個(gè)方向擴(kuò)展,之后的點(diǎn)再進(jìn)行擴(kuò)展。即從人物所在的位置從近到遠(yuǎn)進(jìn)行擴(kuò)展(類似廣寬優(yōu)先)。

          二、擴(kuò)展時(shí)會遇到的問題:
          1、當(dāng)擴(kuò)展到一個(gè)點(diǎn)時(shí),人物的移動力沒有了。
          2、當(dāng)擴(kuò)展的時(shí)候遇到了一個(gè)障礙點(diǎn)。
          3、當(dāng)擴(kuò)展的時(shí)候這個(gè)結(jié)點(diǎn)出了地圖。
          4、擴(kuò)展的時(shí)候遇到了一個(gè)人物正好站在這個(gè)點(diǎn)(與2同?)。
          5、擴(kuò)展的點(diǎn)已經(jīng)被擴(kuò)展過了。當(dāng)擴(kuò)展節(jié)點(diǎn)的時(shí)候,每個(gè)節(jié)點(diǎn)都是向四周擴(kuò)展,因此會產(chǎn)生重復(fù)的節(jié)點(diǎn)。

          當(dāng)遇到這些問題的時(shí)候,我們就不對這些節(jié)點(diǎn)處理了。在程序中使用ALLPATH[]數(shù)組記錄下每一個(gè)等擴(kuò)展的節(jié)點(diǎn),不處理這些問題節(jié)點(diǎn)的意思就是不把它們加入到ALLPATH[]數(shù)組中。我們?nèi)绾稳U(kuò)展一個(gè)結(jié)點(diǎn)周圍的四個(gè)結(jié)點(diǎn),使用這個(gè)結(jié)點(diǎn)的坐標(biāo)加上一個(gè)偏移量就可以了,方向如下:

             3
             0 2
             1

          偏移量定義如下:
          int offx[4] = { -1, 0, 1, 0 };
          int offy[4] = { 0, 1, 0, -1 };


          擴(kuò)展一個(gè)節(jié)點(diǎn)的相鄰的四個(gè)節(jié)點(diǎn)的坐標(biāo)為:
          for(int i=0; i<4; i )
          {
               temp.x = temp1.x offx[i];
               temp.y = temp1.y offy[i];
          }


          三、關(guān)于地圖的結(jié)構(gòu):
          1、地圖的二維坐標(biāo),用于確定每個(gè)圖塊在地圖中的位置。
          2、SLG中還要引入一個(gè)變量decrease表示人物經(jīng)過這個(gè)圖塊后他的移動力的減少值。例如,一個(gè)人物現(xiàn)在的移動力為CurMP=5,與之相領(lǐng)的圖塊的decrease=2;這時(shí),如果人物移動到這里,那它的移動力變成CurMP-decrease。
          3、Flag域:寬度優(yōu)先中好像都有這個(gè)變量,有了它,每一個(gè)點(diǎn)保證只被擴(kuò)展一次。防止一個(gè)點(diǎn)被擴(kuò)展多次。(一個(gè)點(diǎn)只被擴(kuò)展一次真的能得到正確的結(jié)果嗎?)
          4、一個(gè)地圖上的圖塊是否可以通過,我們使用了一個(gè)Block代表。1---不可以通過;0---可以通過。

          這樣,我們可以定義一個(gè)簡單的地圖結(jié)構(gòu)數(shù)組了:

          #define MAP_MAX_WIDTH 50
          #define MAP_MAX_HEIGHT 50
          typedef struct tagTILE{
               int x,y,decrease,flag,block;
          }TILE,*LPTILE;
          TILE pMap[MAP_MAX_WIDTH][MAP_MAX_HEIGHT];


          以上是順序數(shù)組,是否使用動態(tài)的分配更好些?畢竟不能事先知道一個(gè)地圖的寬、高。

          四、關(guān)于路徑:
          SLG游戲中的擴(kuò)展路徑是一片區(qū)域(以人物為中心向四周擴(kuò)展,當(dāng)然,當(dāng)人物移動時(shí)路徑只有一個(gè))。這些擴(kuò)展的路徑必須要存儲起來,所有要有一個(gè)好的結(jié)構(gòu)。我定義了一個(gè)結(jié)構(gòu),不是很好:

          typedef struct tagNODE{
               int x,y;   //擴(kuò)展路徑中的一個(gè)點(diǎn)在地圖中的坐標(biāo)。
               int curmp; //人物到了這個(gè)點(diǎn)以后的當(dāng)前的移動力。
          }NODE,*LPNODE;

          上面的結(jié)構(gòu)是定義擴(kuò)展路徑中的一個(gè)點(diǎn)的結(jié)構(gòu)。擴(kuò)展路徑是點(diǎn)的集合,因此用如下的數(shù)組進(jìn)行定義:

          NODE AllPath[PATH_MAX_LENGTH];

          其中的PATH_MAX_LENGTH代表擴(kuò)展路徑的點(diǎn)的個(gè)數(shù),我們不知道這個(gè)擴(kuò)展的路徑中包含多少個(gè)點(diǎn),因此定義一個(gè)大一點(diǎn)的數(shù)字使這個(gè)數(shù)組不會產(chǎn)生溢出:

          #define PATH_MAX_LENGTH 200


          上面的這個(gè)數(shù)組很有用處,以后的擴(kuò)展就靠它來實(shí)現(xiàn),它應(yīng)該帶有兩個(gè)變量nodecount 代表當(dāng)前的數(shù)組中有多少個(gè)點(diǎn)。當(dāng)然,數(shù)組中的點(diǎn)分成兩大部分,一部分是已經(jīng)擴(kuò)展的結(jié)點(diǎn),存放在數(shù)組的前面;另一部分是等擴(kuò)展的節(jié)點(diǎn),放在數(shù)組的后面為什么會出現(xiàn)已擴(kuò)展節(jié)點(diǎn)和待擴(kuò)展節(jié)點(diǎn)?如下例子:

          當(dāng)前的人物坐標(biāo)為x,y;移動力為mp。將它存放到AllPath數(shù)組中,這時(shí)的起始節(jié)點(diǎn)為等擴(kuò)展的節(jié)點(diǎn)。這時(shí)我們擴(kuò)展它的四個(gè)方向,對于合法的節(jié)點(diǎn)(如沒有出地圖,也沒有障礙......),我們將它們存放入AllPath數(shù)組中,這時(shí)的新加入的節(jié)點(diǎn)(起始節(jié)點(diǎn)的子節(jié)點(diǎn))就是等擴(kuò)展結(jié)點(diǎn),而起始節(jié)點(diǎn)就成了已擴(kuò)展節(jié)點(diǎn)了。下一次再擴(kuò)展節(jié)點(diǎn)的時(shí)候,我們不能再擴(kuò)展起始節(jié)點(diǎn),因?yàn)樗且呀?jīng)擴(kuò)展的節(jié)點(diǎn)了。我們只擴(kuò)展那幾個(gè)新加入的節(jié)點(diǎn)(待擴(kuò)展節(jié)點(diǎn)),之后的情況以此類推。那么我們?nèi)绾沃滥男┦且呀?jīng)擴(kuò)展的結(jié)點(diǎn),哪些是等擴(kuò)展的節(jié)點(diǎn)?我們使用另一個(gè)變量cutflag,在這個(gè)變量所代表的下標(biāo)以前的結(jié)點(diǎn)是已擴(kuò)展節(jié)點(diǎn),在它及它之后是待擴(kuò)展結(jié)點(diǎn)。

          五、下面是基本框架(只擴(kuò)展一個(gè)人物的可達(dá)范圍):

          int nodecount = 0; //AllPath數(shù)組中的點(diǎn)的個(gè)數(shù)(包含待擴(kuò)展節(jié)點(diǎn)和已經(jīng)擴(kuò)展的節(jié)點(diǎn)
                      int cutflag = 0; //用于劃分已經(jīng)擴(kuò)展的節(jié)點(diǎn)和待擴(kuò)展節(jié)點(diǎn)
                      NODE temp; //路徑中的一個(gè)點(diǎn)(臨時(shí))
                      temp.x = pRole[cur] - >x; //假設(shè)有一個(gè)關(guān)于人物的類,代表當(dāng)前的人物
                      temp.y = pRole[cur] - >y;
                      temp.curmp = pRole[cur] - >MP; //人物的最大MP
                      AllPath[nodecount] = temp; //起始點(diǎn)入AllPath,此時(shí)的起始點(diǎn)為等擴(kuò)展的節(jié)點(diǎn)
                       
                      while (curflag < nodecount) { //數(shù)組中還有待擴(kuò)展的節(jié)點(diǎn)
                      int n = nodecount; //記錄下當(dāng)前的數(shù)組節(jié)點(diǎn)的個(gè)數(shù)。
                      for (int i = cutflag; i < nodecount; i) { //遍歷待擴(kuò)展節(jié)點(diǎn)
                      for (int j = 0; j < 4; j) { //向待擴(kuò)展節(jié)點(diǎn)的四周各走一步
                      //取得相鄰點(diǎn)的數(shù)據(jù)
                      temp.x = AllPath[i].x offx[j];
                      temp.y = AllPath[i].y offy[j];
                      temp.curmp = AllPath[i].curmp -
                      pMap[AllPath[i].x][AllPath[i].y].decrease;
                      //以下為檢測是否為問題點(diǎn)的過程,如果是問題點(diǎn),不加入AllPath數(shù)組,繼續(xù)處理其它的點(diǎn)
                      if (pMap[temp.x][temp.y].block) {
                      continue; //有障礙,處理下一個(gè)節(jié)點(diǎn)
                      }
                      if (temp.curmp < 0) {
                      continue; //沒有移動力了
                      }
                      if (temp.x < 0 || temp.x >= MAP_MAX_WIDTH || temp.y < 0 ||
                      temp.y >= MAP_MAX_HEIGHT) {
                      continue; //出了地圖的范圍
                      }
                      if (pMap[temp.x][temp.y].flag) {
                      continue; //已經(jīng)擴(kuò)展了的結(jié)點(diǎn)
                      }
                      //經(jīng)過了上面幾層的檢測,沒有問題的節(jié)點(diǎn)過濾出來,可以加入AllPath
                      AllPath[nodecount] = temp;
                      }
                      pMap[AllPath[i].x][AllPath[i].y].flag = 1; //將已經(jīng)擴(kuò)展的節(jié)點(diǎn)標(biāo)記為已擴(kuò)展節(jié)點(diǎn)
                      }
                      cutflag = n; //將已擴(kuò)展節(jié)點(diǎn)和待擴(kuò)展節(jié)點(diǎn)的分界線下標(biāo)值移動到新的分界線
                      }
                      for (int i = 0; i < nodecount; i) {
                      pMap[AllPath[i].x][AllPath[i].y].bFlag = 0; //標(biāo)記為已擴(kuò)展節(jié)點(diǎn)的標(biāo)記設(shè)回為待擴(kuò)展節(jié)點(diǎn)。
                      }

           

           


          游戲算法整理 算法七 無限大地圖的實(shí)現(xiàn)

          這已經(jīng)不是什么新鮮的東西了,不過現(xiàn)在實(shí)在想不到什么好寫,而且版面上又異常冷清,我再不說幾句就想要倒閉了一樣。只好暫且拿這個(gè)東西來湊數(shù)吧。
          無限大的地圖,聽上去非常吸引人。本來人生活的空間就是十分廣闊的,人在這么廣闊的空間里活動才有一種自由的感覺。游戲中的虛擬世界由于受到計(jì)算機(jī)存儲空間的限制,要真實(shí)地反映這個(gè)無限的空間是不可能的。而對這個(gè)限制最大的,就是內(nèi)存的容量了。所以在游戲的空間里,我們一般只能在一個(gè)狹小的范圍里活動,在一般的RPG中,從一個(gè)場景走到另一個(gè)場景,即使兩個(gè)地方是緊緊相連的,也要有一個(gè)場景的切換過程,一般的表現(xiàn)就是畫面的淡入淡出。

          這樣的場景切換給人一種不連續(xù)的感覺(我不知道可不可以把這種稱作“蒙太奇”:o)),從城內(nèi)走到城外還有情可緣,因?yàn)橛械莱菈β铮莾蓚€(gè)地方明明沒有界限,卻偏偏在這一邊看不到另外一邊,就有點(diǎn)不現(xiàn)實(shí)了。當(dāng)然這并不是毛病,一直以來的RPG都是遵循這個(gè)原則,我們(至少是我)已經(jīng)習(xí)慣了這種走路的方式。我在這里說的僅僅是另外一種看起來更自然一點(diǎn)的走路方式,僅此而已。

          當(dāng)然要把整個(gè)城市的地圖一下子裝進(jìn)內(nèi)存,現(xiàn)在的確是不現(xiàn)實(shí)的,每一次只能放一部分,那么應(yīng)該怎么放才是我們要討論的問題。

          我們在以前提到Tile方法構(gòu)造地圖時(shí)就談到過Tile的好處之一就是節(jié)省內(nèi)存,這里仍然可以借鑒Tile的思想。我們把整個(gè)大地圖分成幾塊,把每一塊稱作一個(gè)區(qū)域,在同一時(shí)間里,內(nèi)存中只保存相鄰的四塊區(qū)域。這里每個(gè)區(qū)域的劃分都有一定的要求:每個(gè)區(qū)域大小應(yīng)該相等這是一定的了,不然判斷當(dāng)前屏幕在哪個(gè)區(qū)域中就成了一個(gè)非常令人撓頭的事;另外每個(gè)區(qū)域的大小都要大于屏幕的大小,也只有這樣才能保證屏幕(就是圖中那塊半透明的藍(lán)色矩形)在地圖上蕩來蕩去的時(shí)候,最多同時(shí)只能覆蓋四個(gè)區(qū)域(象左圖中所表示的),內(nèi)存里也只要保存四個(gè)區(qū)域就足夠了;還有一點(diǎn)要注意的,就是地圖上的建筑物(也包括樹啦,大石頭啦什么的)必須在一個(gè)區(qū)域內(nèi),這樣也是為了畫起來方便,當(dāng)然墻壁——就是那種連續(xù)的圍墻可以除外,因?yàn)閴Ρ诒緛砭褪且欢我欢纹雌饋淼摹?

          我們在程序中可以設(shè)定4個(gè)指針來分別指向這4個(gè)區(qū)域,當(dāng)每次主角移動時(shí),就判斷當(dāng)前滾動的屏幕是否移出了這四個(gè)區(qū)域,如果移出了這四個(gè)區(qū)域,那么就廢棄兩個(gè)(或三個(gè))已經(jīng)在目前的四個(gè)相鄰區(qū)域中被滾出去的區(qū)域(說得很別扭,各位見諒),讀入兩個(gè)(或三個(gè))新滾進(jìn)來的區(qū)域,并重新組織指針。這里并不涉及內(nèi)存區(qū)域的拷貝。

          這樣的區(qū)域劃分方法剛好適合我們以前提到的Tile排列方法,只要每個(gè)區(qū)域橫向Tile的個(gè)數(shù)是個(gè)偶數(shù)就行了,這樣相鄰的兩個(gè)區(qū)域拼接起來剛好嚴(yán)絲合縫,而且每個(gè)區(qū)域塊的結(jié)構(gòu)完全一致,沒有那些需要重復(fù)保存的Tile(這個(gè)我想我不需要再畫圖說明了,大家自己隨便畫個(gè)草圖就看得出來了)。在文件中的保存方法就是按一個(gè)個(gè)區(qū)域分別保存,這樣在讀取區(qū)域數(shù)據(jù)時(shí)就可以直接作為一整塊讀入,也簡化了程序。另外還有個(gè)細(xì)節(jié)就是,我們的整個(gè)地圖可能不是一個(gè)規(guī)則的矩形,可能有些地方是無法達(dá)到的,如右圖所示,背景是黑色的部分代表人物不能達(dá)到的地方。那么在整個(gè)地圖中,這一部分區(qū)域(在圖中藍(lán)色的3號區(qū)域)就可以省略,表現(xiàn)在文件存儲上就是實(shí)際上不存儲這一部分區(qū)域,這樣可以節(jié)省下不少存儲空間。對于這種地圖可以用一個(gè)稀疏矩陣來存儲,大家也可以發(fā)揮自己的才智用其他對于編程來說更方便的形式來存儲地圖。  

          這就是對無限大地圖實(shí)現(xiàn)的一種方法,歡迎大家提出更好的方法。也希望整個(gè)版面能夠活躍一點(diǎn)。

          posted @ 2009-11-07 13:18 小強(qiáng)摩羯座 閱讀(705) | 評論 (0)編輯 收藏

          天是09年1月1日,一個(gè)全新的開始,我要珍惜當(dāng)下每一分一秒!讓當(dāng)下每個(gè)呼吸都是嶄新的!

          新的一年,我希望自已對人生、對生命,能夠有一個(gè)全新的認(rèn)識。謝謝陳爺爺前幾天送我的那

          套《康熙起居注》,最近也越來越認(rèn)識到,要多讀書,讀好書的重要性,能夠天天在家讀書實(shí)

          在也是一種很大的幸福,我要珍惜!乾隆皇帝十九歲就寫下了《讀書以明理為先》的文章,說

          明讀書對明理、對做人的重要性,立身以至誠為本,讀書以明理為先!今年還有一個(gè)心愿就是

          用小楷臨摹一遍康熙皇帝抄寫的<金剛經(jīng)>,也終于明白了其實(shí)寫字這個(gè)過程本身也是在訓(xùn)練一

          種心境,所以寫字是寫的一種心境,而不在與字的美丑。一個(gè)有著強(qiáng)大心力的人,才可能真正

          意義的永無不勝,學(xué)佛學(xué)道,無論學(xué)什么,其實(shí)最大的敵人就是自已認(rèn)為的那顆狂燥不安的心,

          黑暗中總有惺惺相惜的敵人!純靜樸素的大道,無時(shí)無刻都存在于我們身邊,上達(dá)于天,下入于

          地,化育萬物。道法自然,或許真正獲得了心靈上的自由,才能達(dá)到至人的境界:游于物之所不

          得遁而皆存。遵循自然法則因緣和合,才是順天行事,緣份就像一針一眼,誰也逃不了,如果這

          輩子真有一個(gè)能把名和利都看的很淡的人,并且愿意和我過一種很簡單的生活,我一定要讓他成

          為這世上第二幸福的人,因?yàn)樽钚腋5娜耸俏遥∥乙惠呑硬浑x不棄,看日出看日落,看北

          斗星看流星雨,直到老!09年,讓我牽著莊子的手,在輪回的戰(zhàn)場中,做個(gè)勇敢的戰(zhàn)士!

          posted @ 2009-11-03 14:21 小強(qiáng)摩羯座 閱讀(554) | 評論 (0)編輯 收藏

          pku3233:Given a n × n matrix A and a positive integer k, find the sum S = A + A2 + A3 + … + Ak.

          分析:矩陣相乘O(n^3), 有k次,則復(fù)雜度為n^3*k。

          使用矩陣技巧,構(gòu)造:
              B=  |  A  A  |
                    |  O  I   |
          則B的乘方的結(jié)果其右上會是S,其他三個(gè)不變。此時(shí)化成了矩陣乘方問題,此時(shí)可以使用反復(fù)平方法,這樣復(fù)雜度為(2n)^3*logk


          反復(fù)平方法,迭代版的, 以整數(shù)a^m為例:
          int pow(int a, int m)
          {
            
          int y = 1;
            
          int z = a;
            
          while(m > 0)
            
          {  
               
          if( (n&1)==1 ) y = y*z;
               z 
          = z * z;
               n 
          = n >> 1;
            }

            
          return y;
          }
          遞歸的可以不用變量Z,要簡單些,但是要注意了遞歸的調(diào)用順序和結(jié)果的存儲。

            


          posted @ 2009-11-02 21:26 小強(qiáng)摩羯座 閱讀(298) | 評論 (0)編輯 收藏

          僅列出標(biāo)題
          共20頁: 上一頁 1 2 3 4 5 6 7 8 9 下一頁 Last 
          主站蜘蛛池模板: 九龙城区| 象山县| 游戏| 耒阳市| 墨竹工卡县| 吉木萨尔县| 开原市| 革吉县| 香港| 确山县| 禹州市| 利川市| 高密市| 额敏县| 阿克| 开封市| 金华市| 迭部县| 阳信县| 迁西县| 拜城县| 赣州市| 靖江市| 桃园县| 宜城市| 思南县| 文安县| 封开县| 科技| 农安县| 平凉市| 肇源县| 修武县| 南丹县| 宜君县| 罗平县| 温泉县| 台东县| 思茅市| 安顺市| 广州市|