[編譯原理讀書筆記][第3章 詞法分析]
標簽(空格分隔): 未分類
-
本章我們主要討論如何構建一個詞法分析器
- 首先建立起每個詞法單元的詞法結構圖或其他描述.
- 編寫代碼識別輸入中出現的每個詞素,並返回識別到詞法單元的有關信息
-
詞法分析器生成工具(lexical-analyzer generator)
- 描述出詞素模式,然后將這些模式編譯為具有詞法分析功能的代碼.
- 程序員只要在抽象很高的層次上描述軟件,就能生成代碼.
- 3.5節將介紹一個名為
Lex
分析器生成工具
-
正則表達式
-
正則表達式是一種很方便描述詞素模式的方法.
-
我們將介紹正則表達式進行轉換:
- 首先轉換為不確定有窮自動機.
- 然后轉換為確定又窮自動機.
-
驅動程序
就是模擬這些自動機的代碼,使用自動機確定下一個詞法單元.
-
-
驅動程序和自動機的規約形成詞法分析器的核心部分.
3.1 詞法分析器的作用
詞法分析是編譯的第一階段.
-
詞法分析器的主要任務是
- 1.讀入源程序的輸入字符
- 2.將他們組成詞素,生成並輸出一個詞法單元序列,每個詞法單元對應一個詞素.
-
詞法分析,語法分析,符號表的交互
getNextToken
所指示的調用使得詞法單元從它的輸入不斷讀取字符,直到識別到下一個詞素為止.- 詞法單元根據詞生成一個詞法單元返回給語法分析.
-
詞法分析其余任務
- 過濾注釋和空白
- 編譯器生成的錯誤信息,和源代碼的位置練習起來
-
有時候,詞法分析分成兩個級聯處理
- 掃描階段: 不生成詞法的單元的簡單處理:刪除注釋和將多個空白壓縮成一個
- 分析極端:處理掃描階段的輸出,返回詞法單元
3.1.1 詞法分析及語法分析
把編譯階段的分析部分化為詞法分析和語法分析階段有如下幾個原因:
- 最重要的考慮是簡化編譯器設計
- 提高編譯器效率(因為能專精)
- 增強編譯器的可移植性.
3.1.2 詞法單元,模式和詞素
三個相關但有區別的術語.
- 詞法單元:由一個詞法單元名和一個可選屬性組成.
- 詞法單元名:是一種表示某種詞法單元的抽象符號.
- 比如一個關鍵詞,標識符的輸入字符序列.
- 詞法單元名是由語法分析處理的輸入符號.
- 通常用黑體字表示詞法單元名
- 詞法單元名:是一種表示某種詞法單元的抽象符號.
- 模式:描述一個詞法單元的詞素可能具有的形式.
- 詞法單元是關鍵詞時:
- 模式是組成這個關鍵詞的字符序列
- 對於標識符和其他詞法單元:
- 模式是一個更加復雜的結構,可以和很多符號串匹配.
- 正則表達式
- 詞法單元是關鍵詞時:
- 詞素:是源程序的一個字符序列.
- 和某個詞法單元的模式匹配
- 被詞法分析器識別為該詞法單元的一個實例.
在很多程序設計語言中,下面類別覆蓋了大多數詞法單元
- 每個關鍵詞有一個詞法單元.
- 一個關鍵詞的模式是他本身.
- 表示運算符的詞法單元
- 表示所有標識符的詞法單元
- 一個或多個表示常量的詞法單元
- 每一個標點符號 有一個詞法單元
3.1.3 詞法單元的屬性
- 詞法單元的屬性:如果多個詞素可以和一個模式匹配,那么詞法分析器必須向編譯器的后續階段提供被匹配詞素的附加信息.
- 一個
標識符
的屬性值是一個指向符號表中該標識符條目的指針.
- 一個
3.1.4 詞法錯誤
-
如果沒有其他組件幫助,詞法分析器很難發現源代碼的錯誤.
比如:
fi(a==f(x))
- 詞法分析器會當做一個標識符作為詞法單元傳給語法分析器
- 這個錯誤將由語法分析器處理.
-
恐慌模式
:所有詞法單元都無法和剩余輸入的某個前綴相匹配時的策略- 我們從剩余的輸入不斷刪除字符,直到詞法分析器能夠在剩余的開頭發現一個正確的詞法單元為止.
3.1.5 練習
<float> <id, limitedSquaare> <(> <id, x> <)> <{>
<float> <id, x>
<return> <(> <id, x> <op,"<="> <num, -10.0> <op, "||"> <id, x> <op, ">="> <num, 10.0> <)> <op, "?"> <num, 100> <op, ":"> <id, x> <op, "*"> <id, x>
<}>
<text, "Here is a photo of"> <nodestart, b> <text, "my house"> <nodeend, b>
<nodestart, p> <selfendnode, img> <selfendnode, br>
<text, "see"> <nodestart, a> <text, "More Picture"> <nodeend, a>
<text, "if you liked that one."> <nodeend, p>
3.2 輸入緩沖
討論幾種可以加快源程序讀入速度的方法.
- 我們將介紹一種雙緩沖區方案
- 這種方案能安全處理向前看多個符號問題.
-
,=
,<
可能是->
,==
,<=
這樣雙字符運算符的開始.
- 我們將考慮一種改進方法,這種方法用
哨兵標記
來節約檢查緩沖區末端的時間.
3.2.1 緩沖區對
何為緩沖區對?
由於在編譯一個大型程序需要處理大量的字符,處理這些字符需要很多時間,由此開發了一些特殊的緩沖技術來減少用於處理單個輸入字符的時間開銷.一種重要機制是利用兩個交替讀入的緩沖區.
緩沖區具體
-
每個緩沖區容量都是N個字符,通常N是一塊磁盤塊大小,如4096字節
-
程序為輸入維護了兩個指針
lexemeBegin指針
:該指針指向當前詞素的開始處.當前我們正試圖確定這個詞素的結尾.forward指針
:它一直向前掃,直到發現某個模式被匹配到為止- 做出這個決定的策略在之后描述.
-
一旦確定下一個詞素,
forward
指針將指向該詞素結尾的字符.- 詞法分析器將這個詞素作為某個詞法單元記錄下來並返回.
- 然后使
lexemeBegin
指針指向剛找到詞素后的第一個位置 - 處理完后,
forward
指針也會前移一個位置
-
如果
forward
指針前移超過緩沖區末尾(哨兵標記優化的地方)- 將N個新字符讀到另一個緩沖區末尾,且將
forward
指針指向這個新載入符的頭部.
- 將N個新字符讀到另一個緩沖區末尾,且將
3.2.2 哨兵標記
如何優化檢測緩沖區末尾呢?
思考之前的有兩步操作
- 檢查是否到了末尾
- 確定讀入的字符
將兩步合二為一
在緩沖區末尾擴展一個絕對不會使用的符號,叫做哨兵(sentinel)
字符,一個自然的選擇是eof
.
3.3 詞法單元的規約
-
正則表達式是一種用來描述詞素模式的重要表示方法.
- 雖然不能表達所有可能的模式,但能高效描述在處理詞法單元時要用到的模式類型.
-
這一節我們將研究正則表達式的形式化表示方法
-
在3.5節中 我們將看到如何將正則表達式用到詞法分析生成工具中.
-
在3.7節中 我們將學到如何能將正則表達式轉換為能夠識別詞法單元的自動機,並由此建立一個詞法分析樹.
3.3.1 串和語言
-
字母表(alphabet)
是一個有限的符號集合.- 如
{0,1}
,ASCII
,Unicode
.
- 如
-
某個字母表的
串(string)
是該字母表符號的有窮序列.
-
語言(language)
是某個給定字符表上任意的可數的串的集合. -
字符串的指數運算
3.3.2 語言上的運算
在詞法分析,最重要的語言上的運算是並
,連接
,和閉包運算
.
3.3.3 正則表達式
-
正則表達式可以由較小的正則表達式按照如下規則遞歸地構建.
- 每個正則表達式r表示一個語言L(r),也是根據r的子表達式所構建的語言遞歸構造.
-
某個字母表Σ上的正則表達式以及這些表達式所表示的語言
-
根據優先級丟掉括號
- 一元運算符
*
是最高級,並且是左結合. - 鏈接運算符次高的優先級,也是左結合
|
的游戲級最低,也是左結合.- 將
(a)|((b)*(c))
改寫為a|b*c
- 一元運算符
-
例子:
正則集合
-
可以用同一個正則表達式定義的語言叫做
正則集合(regular set)
-
如果兩個正則表達式
r
和s
表示的語言相同的語言,則稱兩者等價,記做r=s
. -
正則表達式遵守一定的代數定律
3.3.4 正則定義
- 為方便表示,我們可能希望給某些正則表達式命名,並在之后像使用符號一樣使用這些名字,這叫做
正則定義(regular definition)
是具有如下形式的定義序列:
- 每個
di
都是一個新符號,不在Σ中,並且各不相同. - 每個
ri
是字母表`Σ U {d1,d2,...di-1}上的正則表達式.
- 每個
- 可以看出 遞歸定義是正則表達式的很重要的性質
3.3.5 正則表達式的擴展
除了以上的運算,在現代像Lex
這樣的實用Unix程序都有對正則的擴展
- 一個或多個實例:
+
- 單目后綴運算符
+
表示一個正則表達式及其語言的正閉包. (r)+
意思為(L(r))+
+
與*
有相同的優先級
- 單目后綴運算符
- 零個或一個實例:
?
- 單目后綴運算符
?
的意思是零個和一個出現. r?
等價於r|空集
?
與+
與*
有相同的運算集
- 單目后綴運算符
- 字符類:
[]
- 一個正則表達式
a1 | a2 |...|an
可以縮寫為[a1a2...an]
- 如果a1,a2,a2還具有邏輯關系,可以縮寫為
[a1-an]
- 例如
[1-9]
,[a-z]
- 例如
- 一個正則表達式
3.3.6 3.3練習
第三題
\/\*([^*"]*|".*"|\*+[^/])*\*\/
第四題
先解決比較簡單的{0,1,2}
SB解法
0?1?2?|0?2?1?|1?0?2?|1?2?0?|2?0?1?|2?1?0?|
正確解法
want -> 0|A?0?1(A0?1|01)*A?0?|A0?
A -> 0?2(02)*
證明如下面的圖
step3
5-7太難
第八題:
b*(a+b?)*
第九題:
b* | b*a+ | b*a+ba*
Lex的擴展方法:
如何引用這些被使用的符號
3.4 詞法單元的識別
- 我們學習如何根據各個需要識別的詞法單元的模式來構造一段代碼.
- 識別檢查輸入的串,並找到匹配的詞素.
3.4.1 狀態轉換圖
介紹
- 詞法分析器的中間步驟,將模式轉換為
狀態轉換圖
- 本節用人工方式,將正則表示的模式轉換為狀態圖
- 3.6節介紹一種使用自動化方法來進行
狀態轉換圖
有一組被稱為狀態
的結點和圓圈.
例子
3.4.2 保留字和標識符的識別
我們可以用兩種方法處理像標識符的保留字.
-
初始化時將保留字填入符號表
-
為每個關鍵字建立單獨的狀態轉換圖
3.4.3 完成我們的例子
3.4.4 基於狀態轉換圖的詞法分析器的體系結構
有幾種方法根據一組狀態圖構造出詞法分析器.
例子
根據這個狀態圖,寫出getRelop()
函數
-
函數
fail()
具體操作依賴於全局恢復策略- 將
forward
指針重置為lexemeBegin
的值 - 使用另一個狀態圖從尚未處理的輸入部分的真實位置開始識別.
- 將state改為另一狀態圖的初始狀態,將尋找別的詞法單元
- 當所有狀態圖都已經試過,
fail()
啟動一個錯誤糾正步驟.
- 將
-
狀態8,帶有
*
,輸入指針會回退,c放回輸入流- 由
retract()
完成
- 由
-
狀態圖的執行
- 順序
- 並行
- 取最長的
- 合並(麻煩)
3.4.5 3.4的練習
3.5 詞法分析器生成工具 Lex
介紹一個名為Lex
的工具,在最近的實現中也稱為Flex
.
- 支持使用正則表達式來描述詞法單元的模式,給出一個詞法分析器的規約
- 輸入表示方法叫做
Lex語言
,工具本身是Lex編譯器
. - 核心部分: 根據模式,生成轉換圖,生成相應代碼,存放到lex.yy.c中
3.5.1 Lex的使用
a.out
通常是語法分析器調用的子例程- 子例程通常返回一個整數值,代表詞法單元的編碼
- 詞法單元的屬性值,保存在全局變量
yylval
中- 這個變量由語法分析器,詞法分析器共享.
3.5.2 Lex 程序的結構
-
聲明部分包括變量和明示常量(manifest con-stant)和正則定義
- 明示常量,表示一個常數的標識符
-
Lex的轉換規則有如下形式
模式 {動作}
- 每個模式是一個正則表達式,可以使用聲明部分的正則定義.
- 動作部分是代碼片段,一般是C語言
-
Lex第三個部分包含各個動作要的輔助函數.
- 還有一種方法將這些代碼單獨編譯,一起裝載
lex詞法分析器和語法分析器的協同
- 當詞法分析器被語法分析器調用時,詞法分析從余下輸入逐個讀取字符.
- 直到發現最長的與某個模式
Pi
匹配的前綴.
- 直到發現最長的與某個模式
- 然后詞法分析器執行動作
Ai
.- 通常
Ai
會返回語法分析器 - 如果不返回控制,繼續尋找其他詞素,直到某個動作返回
- 通常
- 詞法分析器只返回一個值,即詞法單元名
- 在需要時,通過
yylval
傳遞詞素附加信息
- 在需要時,通過
例子
-
%{ %}
-
處理名為
ID
的詞法單元的時候
3.5.3 Lex中的沖突解決
- 總是選擇最長的前綴
- 如果最長的前綴與多個模式匹配,選擇聲明靠前的那個.
3.5.4 向前看運算符
某些時候,我們希望僅僅詞素后面跟隨特定字符,才能和模式匹配.
- 這種情況,我們使用
/
之后跟隨表示一個附加的模式. - 附加部分能匹配,但最后不屬於詞素
3.6 有窮自動機
揭示Lex如何將輸入程序轉換為詞法分析器,核心在於有窮自動機
這些自動機本質和轉換狀態圖類似,但也有以下不同
- 有窮自動機是識別器(recognizer).
- 只能對每個輸入串簡單的回答 是,否.
- 有窮自動機分為兩類
- 不確定的有窮自動機(NFA):對其邊上的標號沒有任何限制,一個符號可以標記離開同一個狀態的多條邊.
- 確定的有窮自動機(DFA):對於每個符號,有且一條離開該狀態的邊.
確定和不確定的能識別的語言的集合是相同的,恰好也是正則表達式能識別的集合,這個集合的語言叫做正則語言
3.6.1 不確定的有窮自動機
一個NFA由以下部分組成
- 一個有窮狀態集合
S
- 一個輸入符號集合
Σ
,即輸入字母表.- 假設空串不在這個集合.
- 一個轉換函數,他為每個狀態給出了相應的后繼狀態.
S
中的s0
被稱為開始狀態S
的一個子集F
被稱為接受狀態
跟轉換圖有以下區別
- 同一個符號可以標記同一狀態到達多個目標狀態的多條邊.
- 一個邊的標號不僅可以是輸入字母表的字符,也可是空串
例子
(a|b)*abb
的NFA轉換圖
3.6.2 轉換表
3.6.3 自動機輸入字符串的接受
一個NFA接受輸入字符串x,**當且僅當對應的轉換圖中存在一條開始狀態到某個接受狀態的路徑,使得路徑中各條變上的標號組成字符串x.
-
注意:路徑的
ε
被忽略 -
我們可以用
L(A)
表示自動機A接受的語言
3.6.4 確定的有窮自動機
- 在構造詞法分析器時,我們真正實現和模擬的是DFA.
- ?幸運的是,每個正則表達式和每個DFA都可以轉變為接受相同語言的DFA.
如何用DFA進行串的識別(十分簡單)
3.7 從正則表達式到自動機
-
首先介紹如何把NFA轉換為DFA.
- 利用
子集構造法
的技術給出一個直接模擬NFA的算法- 這個算法可用於那些將NFA轉化為DFA比直接模擬NFA更加耗時的(非詞法分析)情形
- 利用
-
接着,我們介紹正則表達式轉為NFA
- 在必要時刻,根據這個NFA構造DFA
-
最后討論不同正則表達式實現技術的時間-空間權衡,並說明如何選擇正確的方法.
3.7.1 從NFA到DFA的自動轉換
子集構造法的基本思想:是讓構造得到的DFA的每個狀態對應於NFA的一個狀態集合.
-
DFA在讀入輸入
a1a2...an
之后到達的狀態對應於相應NFA從開始狀態出發,沿着以a1a2...an
為標號走到的路徑能夠到達狀態的集合. -
DFA
狀態數可能是NFA
狀態數的指數,此時試圖實現這個DNA有點困難. -
然而,基於自動機的詞法分析方法的處理能力部分基於這個事實:
- 對於一個真實的語言,它的NFA和DFA的狀態數量大致相同,狀態數量呈指數尚未在實踐中出現
子集構造算法
輸入: 一個NFA N
輸出: 一個接受同樣語言的 DFA D
方法: 我們的算法為D
構造一個轉換表Dtran
.
-
D的每個狀態是一個
NFA
狀態集合,我們將構造Dtran
,使得D "並行的" 模擬N在遇到一個給定輸入串可能執行的所有動作. -
第一個問題:正確處理N的`轉換.
-
給出一些基本操作
s
表示N的單個狀態,T表示一個狀態集. -
算法代碼(有ACM功底很容易懂):
-
D的開始狀態是
ε-closure(s0)
,D的接受狀態是所有至少包含N
的一個接受狀態的狀態集合, -
ε-closure(T)
代碼,一個簡單的深搜而已
例子
- A和C可以合並,將在后面介紹DFA的最小化問題.
3.7.2 NFA的模擬
許多的文本編輯器使用的策略是根據一個正則表達式構造出相應的NFA
,然后使用類似於on the fly
(邊構造邊)的子集構造法來模擬這個NFA的執行.
算法:模擬一個NFA的執行
也類似廣搜,一步一步把所有情況跑一遍.
3.7.3 NFA模擬的效率
更詳細的介紹這個算法
需要以下數據結構
-
兩個堆棧,其中每個堆棧都存放了一個NFA狀態集合.
- 堆棧
oldStates
存放 "當前狀態集合" - 堆棧
newStates
存放 "下一個狀態集合"
- 堆棧
-
一個以NFA狀態為下標的布爾數組
alreadyOn
,指示那個狀態已經在newStates
. -
一個二維數組
move[s,a]
,保存了這個NFA的轉換表,是一個鄰接表(因為轉換表單元格有多個元素).
- 既然書說他是
O(k(m+n))
復雜度...那就是吧
3.7.4 從正則表達式構造NFA
現在給出一個算法,它可以將任何正則表達式轉為接受相同語言的NFA
- 這個算法是語法制導的,沿着正則表達式的語法分析樹自底向上遞歸處理.
- 對於每個子表達式,該算法構造一個只有一個
算法 3.23 將正則表達式 轉換為一個NFA的McMaughton-Yamada-Thompson算法
-
基本規則
-
r=s|t
-
r=st
-
r=s*
-
幾個有趣的性質
- N(r)的狀態數最多是 r中出現的運算符和運算分量的總數的2倍.
- N(r)有且只有一個開始狀態,接收狀態沒有出邊,開始狀態沒有入邊.
- 除接受狀態,都最多有一條帶字符的出邊,最多有兩條空邊
例子
3.7.5 字符串處理算法的效率
-
子集構造法 :
O(|r|^2 * DFA狀態數)
DFA狀態數一般是|r|
-
模擬算法:
O(|r|*|x|)
3.8 詞法分析器生成工具的設計
- 應用3.7中介紹的技術,討論
Lex
這樣生成工具的體系 - 將討論基於
NFA
和DFA
的方法,后者實質上就是Lex
的實現方法
3.8.1 生成的詞法分析器的結構
3.8.2 基於NFA的模式匹配
- 沿着狀態集回頭尋找直到有一個完成狀態
3.8.3 詞法分析器使用的DFA
3.8.4 實現向前看運算符/
3.9 基於DFA 的模式匹配器的優化
本節給出三個算法,用於實現和優化根據正則表達式構建的模式匹配器
- 第一個算法可以用於Lex編譯器
- 可以不用經過構造中間NFA就能構建DFA,
- 得到的DFA狀態數也比經過中間NFA得到的DFA狀態數少
- 第二個算法,可以將任何DFA未來具有相同行為的狀態合並.
- 算法復雜度
O(nlogn)
- 算法復雜度
- 第三個算法,可以生成比標准二維表更加緊湊的轉換表方式.
3.9.1 NFA的重要狀態
-
如果一個NFA狀態有一個標號非
ε
的離開轉換,則稱這個狀態是重要狀態(important state)
ε-closure(move(T,a))
,它只使用了集合T中的重要狀態.- 只要當集合
s
是重要狀態時,move(s,a)
才可能是非空的.
-
在子集構造法中,兩個NFA狀態可以被認為是一致的的條件是:
- 1.具有相同的重要狀態
- 2.要么包含接受狀態,要么都不包含接受狀態.
- 一致的意思是把它們當做同一個集合來處理.
正則轉換的NFA
-
如果NFA由正則表達式生成,還有更多關於重要狀態的性質
- 重要狀態只包括在基礎規則部分為正則表達式中某個特定符號位置引入的初始狀態.
- 也就是說,每個重要狀態對應於正則表達式某個運算分量.
- 在正則表達式后面加一個#變為
(r)#
,使得原本的接受狀態變為重要狀態,使得在構造過程中,不要考慮接受狀態.(r)#
又叫擴展的正則變道時
-
使用抽象語法樹表示擴展的正則表達式.
- 葉子節點代表運算分量
- 內部節點表示運算符號
- 整數叫做葉子節點的位置,也表示他對應符號的位置
- 一個符號可以有兩個位置:比如
a
有1和3
- 一個符號可以有兩個位置:比如
3.9.2 根據抽象語法樹計算的到的函數
- 要做一個正則表達式直接構造出DFA,我們首先要構造它的抽象語法樹
- 然后計算如下四個函數:
nullable
,firstpos
,lastpos
和followpos
.- 都是在
(r)#
下進行的. - nullable(n):對於一個節點n當且僅當此節點代表的子表達式中包含空串
ε
返回真- 子表達式可以生成空串或者自己就是空串,即使能生成一些其他串
- firstpos(n):以節點n為根的子樹中的位置集合.
- 這些位置對應於以n為根的子表達式的語言中某個串的第一個符號.
- lastpos(n):以節點n為根的子樹中的位置集合.
- 這些位置對應於以n為根的子表達式的語言中某個串的最后一個符號.
- followpos(p):定義了一個和位置p相關的,某些位置的集合.
- 一個位置
q
在followpos(p)
中當且僅當存在L((r)#)
中的某個串x=a1a2...an
,使得我們在解釋為什么x屬於L((r)#)
時,可以將x中某個ai
可以和位置p匹配,且將位置ai+1和位置q匹配
- 一個位置
- 都是在
3.9.3 計算nullable,firstpos和lastpos
- 直接樹形遞歸就行
3.9.4 計算followpos
只有兩種情況,正則表達式某個位置會跟在另一個位置之后
- 如果n是一個
cat
節點,且左右節點是c1
,c2
.- 那么對於
lastpos(c1)
中的每個位置i
,firstpos(c2)
中的所有位置都在followpos(i)
中. - 如果 n 是 star節點,並且i是
lastpos(n)
中的一個位置,那么firstpos(n)
中的所有位置都在followpos(i)
中.
- 那么對於
例子
3.9.5 根據正則表達式構建DFA
算法3.3.6 從一個正則表達式r構造DNF
- 根據擴展表達式
(r)#
構造出一顆抽象語法樹T
- 計算T的函數
nullable
,firstpos
,lastpos
,followpos
- 使用下列程序,構造出
D
的狀態集Dstates
和D
的轉換函數Dtran
3.9.6 最小化一個DFA的狀態數
(數電中有提及)
任何正則語言都一個唯一的(不計同構)狀態數目最少的DFA.,從任意一個接受相同語言的DFA出發,通過分組合並等價的狀態,我們總是構建這個狀態數最少的DFA.
算法
-
該算法首先創建輸入DFA的狀態的集合的划分.
-
輸入串如何區分各個狀態:
- 如果分別從狀態s和t出發,沿着標號為x的路徑到達的兩個狀態中只有一個是接受狀態,我們說串x區分狀態s和狀態t的串
- 如果存在一個串能區分s和t,那么他們是可區分的.
-
DFA狀態最小化的工作原理是將一個DFA的狀態集合分划成多個組,每個組中的各個狀態之間相互不可區分.
算法3.39
之后就類似於縮點的算法.
例子版本:
3.9.8 DFA模擬中的時間和空間權衡
-
轉換鏈表壓縮
-
一種方式,即有數組的便攜,又有鏈表壓縮的默認狀態.
-
如何保存base值比較好
-