PHP算法

約瑟夫問題

故事

39個猶太人與Josephus以及他的朋友躲到一個洞里,決定寧愿死也不要被敵人抓到。于是決定了自殺方式,41個人圍成一圈,又第1個人開始報數,每報到第3個人就必須自殺。然后下一個重新報數,直到所有人都自殺身亡為止。然而Josephus和他的朋友并不像遵從,Josephus讓朋友想假裝遵從,他將朋友與自己安排在第16個與第31個位置,結果逃過了這場死亡游戲。

約瑟夫自殺問題

約瑟夫環

約瑟夫環
  • 第1輪 開始為0 循環為3 剔除為2
  • 第2輪 開始為3 循環為3 剔除為5
  • 第3輪 開始為6 循環為3 剔除為8
  • 第4輪 開始為9 循環為3 剔除為1
  • 第5輪 開始為3 循環為3 剔除為6
  • ...

數學推導

有n個數,編號從0到n-1,從0開始數到m則剔除,下一位繼續從0開始數,依此類推只剩下最后一個,求最后那個的編號。

每剔除一個就重新開始,相當于減少了問題的規模,解n個規模為n、n-1、n-2,n-3...3,2,1。

假如第2輪(n-1規模)中剔除編號為x,此編號是第1輪剔除后從新從0開始編排的,可推導出,此數在第1輪人數為n時中的編號為 (x+m)%n。

n-2中剔除的數在n-1中的編號為 (x+m)%(n-1)
n-3中剔除的數在n-1中的編號為(x+m)%(n-2)
...

$n = 100;
$m = 3;
$s = 0;//表示從第0個開始數
for($i=2; $i<=$n; $i++){
 $x = ($x + $m) % $i;
}
$result = ($x+$s)%n;

約瑟夫環應用:猴子大王

一群猴子排成一圈,按1,2,3,...,n依此編號,從第1只開始數,數到第m只將它踢出圈。再從它的后面開始數,再數到第m只,再把它踢出去...直到最后剩下的一直猴子,那只猴子就叫大王。

// 使用線性表求解約瑟夫
function josephus($n, $m){
  $result = 0;
  for($i=2; $i<=$n; $i++){
    $result = ($result + $m) % $i;
  }
  return $result + 1;
}

每只猴子出列后,剩下的猴子又組成另一個子問題。只是它們的編號變化了。第1個出列的猴子肯定是arr[1] = m%n

踢出第1只猴子后剩余的猴子是arr[1]+1arr[1]+2arr[1]+3,....,n,1,2,3,...,arr[1]-2arr[1]-1,對應新的編號為1,2,3,...,n-1

假設此時某只猴子的新編號為i,原來的編號就是(i+a[1])%n。于是,便形成了一個遞歸問題。

假設知道子問題(n-1只個猴子)的解為x,那么原問題(n只猴子)的解就是(x+m%n)%N=(x+m)%n。問題的起始條件,若n=1那么結果就是1。

約瑟夫環:丟手帕

設編號為自然序號的1,2,3...n的n個人圍坐一圈,約定編號為k(1<=k<=n)的人從1開始報數,數到m的那個人出列,他的下一位又從1開始報數,數到m的那個人又出列,以此類推,直到所有人出列為止,由此產生一個出隊編號的序列。

丟手帕問題

使用環形鏈表解決約瑟夫問題

可使用一個不帶頭節點的循環鏈表來處理約瑟夫問題,首先構造一個含有n個節點的單循環鏈表,然后由k節點起從1開始計數,計數到m時,對應節點從鏈表中刪除,然后再從被刪除節點的下一個節點從1開始計數,直到最后一個節點從鏈表中刪除。

鏈表

鏈表,最靈活的數據結構。鏈表是有序的列表,而在內存中是分散存儲的。

鏈表是一種在邏輯上連續且有序的數據存儲結構,而在物理存儲單元上是非連續且非有序的。

使用鏈表可解決類似約瑟夫問題、排序問題、搜索索引、廣義表...

鏈表的類型可分為

  • 單向鏈表
  • 雙向鏈表
  • 單向循環鏈表
  • 雙向循環鏈表
  • 十字鏈表/有向圖

單向鏈表

單向鏈表又稱為單鏈表,是一個鏈式存儲結構,擁有一個表頭head,除了最后一個節點外,所有節點都有后繼節點。

單向鏈表

單鏈表的每個節點都保存其數據域和后繼指針。

單向鏈表

案例:排行榜

內存分區

一個程序運行時,內存分成5個區域:棧區、堆區、全局區/靜態存儲區、常量存儲區、代碼區/自由存儲區。

  • 棧區
    由編譯器需要時分配,不需要時自動清除的變量存儲區,其中存儲的變量通常是局變量、函數參數等。
  • 堆區
    使用new分配的內存塊,它們的釋放是由應用程序控制,而非編譯器控制。一般而言,一個new就會對應一個delete。如果程序員沒有釋放掉,在程序結束后,會由操作系統自動回收。
  • 自由存儲區
    malloc等分配的內存塊,與堆區十分類似,不過它是用free來結束自己的生命的。
  • 全局/靜態存儲區
    全局變量和靜態變量被分配到同一塊內存中,C語言中全局變量又分為初始化和未初始化的,在C++中沒有這個區分,他們共同占用同一塊內存區域。
  • 常量存儲區
    比較特殊的存儲區,存放著不允許修改的常量。

PHP的內存模型

內存從邏輯上可分為4段,程序中不同的聲明存放在不同的內存段中。

內存段
  • 數據段data segment
    又稱為初始化靜態段,用來存放程序中已被初始化且不為0的全局變量,如靜態變量和常量。
  • 代碼段/文本段 code segment/text segment
    用于存放程序執行代碼的內存區域,如函數和方法。
  • 棧空間段
    存儲占用相同空間長度,且占用空間小的數據類型。如整形在內存中占用的空間是等長的,都是64bit(4byte)。常有intfloatbool...
  • 堆內存
    數據長度不定長,且占用空間很大的數據類型的存儲區域。如 stringarrayclass...

棧空間段是可以直接存取的,而堆內存不可以直接存取。對于對象而言,是一種很大且占用不定長的數據類型,所以對象是存放在堆中。但對象名是存放在棧中的,因此通過對象名就可以訪問使用對象。

PHP的內存管理

PHP的內存管理是分層的,由上到下分為接口層、堆層、存儲層

PHP的內存管理
  • 接口層

接口層是PHP對外提供的可調用的方法,通過宏封裝了內部實現。這些宏定義了一個高層次的接口,使得調用更加容易,它隔離了外部調用和PHP內存管理的內部實現,實現了一種松耦合關系。

  • 堆層

在接口層下面是PHP內存管理的核心實現,稱之為heap層。堆層控制整個PHP內存管理的過程。

PHP中的內存管理主要工作就是維護3個列表:小塊內存列表free_buckets、大塊內存列表large_free_buckets、剩余內存列表rest_buckets。這里每個bucket也對應一定大小的內存塊列表,這些列表都包含雙向鏈表的實現。

對于小塊內存是最常用的,所以追求高性能。對于大塊內存則追求的是穩妥,盡量避免內存浪費。對于小塊內存,PHP引入了cache機制,ZendMM希望通過cache盡量做到一次定位就能查找分配。

  • 存儲層

存儲層的作用是將內存分配的方式對堆層透明化。

存儲層通常申請的內存都比較大,這里申請的內存大小并不是指storage層結構所需要的大小,只是堆層通過調用存儲層的分配方法時,以大塊大塊的方式申請的內存。

存儲層通過malloc()mmap()等函數向系統真正的申請內存,并通過free()函數釋放掉所申請的內存。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容