<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Data Structures on Intellectual Guides</title><link>https://ckw19810413.github.io/tags/data-structures/</link><description>Recent content in Data Structures on Intellectual Guides</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><lastBuildDate>Wed, 07 Oct 2026 08:00:00 +0800</lastBuildDate><atom:link href="https://ckw19810413.github.io/tags/data-structures/index.xml" rel="self" type="application/rss+xml"/><item><title>每日研究生知識寓言故事：星辰城的點名者</title><link>https://ckw19810413.github.io/blog/2026-10-07-hyperloglog-fable/</link><pubDate>Wed, 07 Oct 2026 08:00:00 +0800</pubDate><guid>https://ckw19810413.github.io/blog/2026-10-07-hyperloglog-fable/</guid><description>&lt;p&gt;在古老而繁華的星辰城，每天都有成千上萬的旅人、商人和朝聖者從四面八方湧入。星辰城的城主提出了一個看似簡單，卻讓所有書記官頭痛不已的要求：「我需要知道過去一年中，究竟有多少個『不同』的人來過我們城市。」&lt;/p&gt;
&lt;p&gt;傳統的做法是：在城門口放一本巨大的羊皮帳冊，每來一個人，就記下他的名字。每天晚上，書記官必須核對新來的名字是否已經在帳冊中出現過。如果來過，就不重複計算。&lt;/p&gt;
&lt;p&gt;剛開始幾天這還行得通。但幾個月後，帳冊變得像小山一樣高。每核對一個名字，書記官都要翻閱幾十萬頁的記錄，耗費大量的人力和時間。這座圖書館般的帳冊，即將壓垮整個城市的運作。&lt;/p&gt;
&lt;p&gt;絕望的首席書記官貼出告示尋求幫助。一天，一位雲遊的數學家揭下了告示，他手裡只拿著一塊小小的石板。&lt;/p&gt;
&lt;p&gt;「你只需要這塊石板，就能算出上百萬名獨立訪客的數量，而且不需要記下任何人的名字。」數學家說。城主和書記官們都覺得他在說大話。&lt;/p&gt;
&lt;p&gt;數學家解釋了他的方法：「星辰城的每個旅人，身上都佩戴著一條代表他們家族血脈的護身符串珠。這些串珠由黑白兩色隨機組成，且每個人的排列都是獨一無二的。」&lt;/p&gt;
&lt;p&gt;「從今天起，守衛不需要問旅人的名字。當旅人入城時，守衛只需看他護身符上，『從第一顆開始算，有連續多少顆白珠子』。如果第一顆是黑的，那就是 0 顆；如果是白、白、黑，那就是 2 顆。」&lt;/p&gt;
&lt;p&gt;「書記官不需要記下任何人的串珠圖案，你們石板上只需要保留『一個數字』——也就是你們目前為止見過『最長的連續白珠子數量』。」&lt;/p&gt;
&lt;p&gt;城主皺起眉頭：「這有什麼用？如果有人來了兩次呢？」&lt;/p&gt;
&lt;p&gt;數學家笑著說：「如果同一個人來了兩次，他的串珠是一模一樣的，所以他不會改變你們石板上那個『歷史最大值』。這天然就過濾掉了重複的訪客！」&lt;/p&gt;
&lt;p&gt;城主又問：「那怎麼知道總人數呢？」&lt;/p&gt;
&lt;p&gt;數學家答道：「這是一個機率的遊戲。如果我拋硬幣，要連續拋出一次正面很容易（一半機率），要連續兩次正面較難（四分之一機率）。如果要連續看到十次正面，那我大概得拋 $2^{10}$ 也就是 1024 次硬幣才可能撞見一次。所以，如果你的石板上寫著『最大連續白珠數是 10』，那麼我們就可以大膽推測，大約有 1024 個不同的旅人來過。」&lt;/p&gt;
&lt;p&gt;首席書記官敏銳地發現了破綻：「等等！如果今天運氣不好，早上第一個進城的旅人，他剛好就帶著連續 20 顆白珠的護身符呢？那我們豈不是第一天就會誤以為城裡來了一百萬人？」&lt;/p&gt;
&lt;p&gt;數學家拍手稱讚：「非常聰明！這就是這個方法最大的弱點：單一記錄容易受到極端運氣的干擾。為了解決這個問題，我們不只用一個守衛。」&lt;/p&gt;
&lt;p&gt;「我們把城門分成 1024 個不同的通道。當旅人來時，我們根據他們串珠最後十顆的排列組合，決定他們必須走哪個通道。每個通道的守衛，都拿著一塊小石板，記錄自己通道裡看到的最大連續白珠數。」&lt;/p&gt;
&lt;p&gt;「到了年底，我們把這 1024 個通道的最大值收集起來，用一種特殊的平均法（調和平均數）來計算。這樣一來，極端的壞運氣就會被其他通道的正常情況給互相抵消。最終，你們只需要這 1024 個小數字，就能極度精準地估算出來過星辰城的人數。」&lt;/p&gt;
&lt;p&gt;城主半信半疑地實施了這個計畫。一年後，當他們用數學家的方法估算，並與耗費千辛萬苦整理出來的巨型帳冊比對時，驚訝地發現，兩者的數字竟然只有不到百分之二的誤差，而書記官們的辦公室裡，再也沒有堆積如山的羊皮紙了。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="概念解析hyperloglog-hll-演算法"&gt;概念解析：HyperLogLog (HLL) 演算法
&lt;/h3&gt;&lt;p&gt;這個故事所描述的，是計算機科學與大數據領域中非常著名的基數估算演算法——&lt;strong&gt;HyperLogLog (HLL)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在大數據場景下（例如統計一個大型網站每天的獨立訪客數 UV、或者資料庫中的 Unique 查詢），如果使用傳統的 Set 或 Hash Table 來精確記錄每一個出現過的元素，當數據量達到數億級別時，會消耗極其龐大的記憶體（這對應故事中「堆積如山的羊皮帳冊」）。&lt;/p&gt;
&lt;p&gt;HyperLogLog 是一種機率資料結構，它犧牲了一點點精確度，換來了極致的空間效率。在 Redis 等現代資料庫中，HLL 只需要 12 KB 的記憶體，就能估算多達 $2^{64}$ 個不同元素的數量，標準誤差僅約為 0.81%。&lt;/p&gt;
&lt;h4 id="故事中的隱喻對應"&gt;故事中的隱喻對應：
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;巨大羊皮帳冊與核對過程&lt;/strong&gt;：傳統的 Set 或 Hash Table 資料結構。空間複雜度為 O(N)，且隨著資料量變大，操作成本急劇上升。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;旅人獨一無二的黑白串珠&lt;/strong&gt;：將大數據元素（如 IP 位址、用戶 ID）通過「雜湊函數 (Hash Function)」轉換成的二進位字串（0 和 1）。雜湊保證了相同的元素會有相同的二進位字串，且 0 和 1 呈現均勻的隨機分佈。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;計算開頭連續的白珠子數量&lt;/strong&gt;：觀察二進位字串從最低位（或最高位）開始，連續出現「0」的個數（Leading zeros）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;石板上只記錄「歷史最大值」&lt;/strong&gt;：如果連續出現 $k$ 個 0，意味著大約進行了 $2^k$ 次隨機試驗。HLL 只保留見過最大的 $k$ 值，藉此反推總基數 $2^k$。而且相同的輸入會產生相同的 Hash，不會改變最大值，自然達到了「去重（Deduplication）」的效果。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第一個旅人帶 20 顆白珠（運氣不好）&lt;/strong&gt;：在機率統計中單次取樣的高變異數（High Variance）問題。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1024 個通道與調和平均數&lt;/strong&gt;：HyperLogLog 的核心改良——「分桶（Bucketing）」。利用 Hash 字串的某幾個位元（例如前 10 位元）作為桶（Bucket）的索引，將數據分散到多個桶中，每個桶獨立維護自己的最大值。最後將所有桶的估算值利用調和平均數（Harmonic Mean）聚合起來，大幅降低變異數與誤差，達到驚人的估算精度。&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>每日研究生知識寓言故事：亞歷山大圖書館的快筆書記官與夜巡學徒</title><link>https://ckw19810413.github.io/blog/2026-10-04-lsm-tree-fable/</link><pubDate>Sun, 04 Oct 2026 08:00:00 +0800</pubDate><guid>https://ckw19810413.github.io/blog/2026-10-04-lsm-tree-fable/</guid><description>&lt;p&gt;在古老的知識之都，有一座號稱要收納世間所有智慧的「無盡圖書館」。每天，來自世界各地的旅人、學者和商隊，都會湧入圖書館，帶來成千上萬條新的見聞、歷史與卷宗。&lt;/p&gt;
&lt;p&gt;最初，圖書館採用的是「完美書架法則」。每當有人帶來一份新卷宗，老館長就必須提著燈籠，穿過宛如迷宮般的幽暗長廊，找到與該卷宗字母排序完全對應的書架。有時候為了塞進這份新卷宗，老館長還得把書架上後面的幾百卷書全部往右挪動一個位置。
如果一天只有十幾份卷宗，這套法則還行得通。但隨著帝國版圖擴張，每天都有數萬份卷宗如雪片般飛來，老館長在書架間來回狂奔，累得氣喘吁吁，甚至在門口排起了長達數公里的等待隊伍。圖書館面臨了崩潰的危機。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="快筆書記官的誕生"&gt;快筆書記官的誕生
&lt;/h3&gt;&lt;p&gt;為了解決這個問題，新任的大書記官「埃爾斯」發明了一套全新的《雙層流動法則》。&lt;/p&gt;
&lt;p&gt;埃爾斯在圖書館的大門口擺了一張靈巧的&lt;strong&gt;紅木書桌&lt;/strong&gt;。現在，當新的卷宗送達時，他不再跑進深邃的書庫，而是直接將內容快速地抄寫在紅木書桌上的一本**《當日記事本》**裡。因為只是按順序抄寫，速度快若閃電，門口的隊伍瞬間消失了。&lt;/p&gt;
&lt;p&gt;然而，《當日記事本》的容量是有限的。一旦寫滿，埃爾斯就會用蜂蠟將這本記事本封死，貼上「不可修改」的封條，然後隨手往身後的**「昨日書堆」**一扔，接著再拿出一本新的空白記事本繼續抄寫。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="尋書人的困境與夜巡學徒"&gt;尋書人的困境與夜巡學徒
&lt;/h3&gt;&lt;p&gt;這個方法讓「寫入」的速度達到了極致，但卻苦了那些來「尋書」的人。
當有學者來查詢某段歷史時，尋書人必須先看門口的《當日記事本》有沒有記錄；如果沒有，就得到後方雜亂無章的數十個「昨日書堆」中，一個一個去翻找。有時候為了找一條記錄，要翻遍幾十個書堆，找書的速度變得異常緩慢。&lt;/p&gt;
&lt;p&gt;為了解決找書慢的問題，埃爾斯招募了一群&lt;strong&gt;夜巡學徒&lt;/strong&gt;。
每當夜深人靜，這群學徒就會在後方默默工作。他們會把好幾個小型的「昨日書堆」搬出來，將裡面的卷宗重新按照字母順序排列、合併，然後整齊地裝訂成一本厚厚的**《月度大典》**，放進更深處的書架上。&lt;/p&gt;
&lt;p&gt;如果學徒在合併的過程中，發現了兩份關於同一個事件的記錄（例如：早上有人記錄「國王生病了」，下午又有人記錄「國王康復了」），學徒只會保留&lt;strong&gt;時間最新&lt;/strong&gt;的那一份。如果有人特別交代「把某份卷宗銷毀」，這份指令就像一塊&lt;strong&gt;黑色墓碑&lt;/strong&gt;，當學徒在合併時看到這塊墓碑，就會順手把對應的舊卷宗丟進火爐裡燒掉。&lt;/p&gt;
&lt;p&gt;就這樣，小書堆合併成大書堆，大書堆再合併成巨大的書架。前端的抄寫員始終保持著閃電般的速度，而後端的夜巡學徒則透過不斷地合併與整理，讓尋書人在深處也能快速找到想要的知識。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="揭曉時刻這到底是什麼"&gt;揭曉時刻：這到底是什麼？
&lt;/h3&gt;&lt;p&gt;這個故事所描述的，正是現代高效能資料庫背後的核心資料結構：&lt;strong&gt;LSM-Tree (Log-Structured Merge-Tree，日誌結構合併樹)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如果你用過 Cassandra、RocksDB、LevelDB，或者是 Kafka 的底層存儲，它們都深受 LSM-Tree 思想的影響。&lt;/p&gt;
&lt;p&gt;在傳統的 B-Tree 或 B+Tree 資料庫中，每次寫入一筆新資料，都需要在磁碟中尋找特定的位置並更新，這會產生大量的「隨機寫入 (Random Write)」。傳統硬碟最怕隨機寫入，這成了效能的巨大瓶頸。&lt;/p&gt;
&lt;p&gt;LSM-Tree 則反其道而行：它將所有的「隨機寫入」轉化為了「循序寫入 (Sequential Write)」。這極大地提升了資料庫的寫入吞吐量，特別適合寫入極其頻繁的現代應用場景。&lt;/p&gt;
&lt;h3 id="故事中的隱喻對應"&gt;故事中的隱喻對應：
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;完美書架法則&lt;/strong&gt;：傳統的 B-Tree/B+Tree 結構。每次插入資料都可能需要拆分節點、移動資料，雖然讀取很快，但寫入時會產生大量的隨機 I/O。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;紅木書桌上的《當日記事本》&lt;/strong&gt;：LSM-Tree 的 &lt;strong&gt;MemTable&lt;/strong&gt;（通常位於記憶體中）。所有的新增、修改、刪除操作，都會先快速寫入記憶體中的這棵小樹裡。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不可修改的「昨日書堆」&lt;/strong&gt;：&lt;strong&gt;SSTable (Sorted String Table)&lt;/strong&gt;。當記憶體中的 MemTable 滿了，就會被「凍結」並 Flush 到磁碟上，成為一個不可變的檔案。因為是直接循序寫入磁碟，速度極快。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;尋書人的困境 (找書慢)&lt;/strong&gt;：LSM-Tree 的 &lt;strong&gt;Read Amplification (讀取放大)&lt;/strong&gt; 效應。讀取資料時，如果在 MemTable 找不到，就必須依序去磁碟上的多個 SSTable 中查詢，導致讀取效能下降。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;夜巡學徒的整理工作&lt;/strong&gt;：&lt;strong&gt;Compaction (合併壓縮)&lt;/strong&gt; 機制。背景程式會定期將多個小型的 SSTable 合併成一個較大的 SSTable，以此減少檔案數量，提升未來的讀取速度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;黑色墓碑&lt;/strong&gt;：&lt;strong&gt;Tombstone (墓碑標記)&lt;/strong&gt;。在 LSM-Tree 中，刪除資料並不會立刻去磁碟裡把舊資料抹除（因為那又變成了隨機寫入）。相反地，它會寫入一筆帶有「墓碑」標記的刪除記錄。直到夜間 Compaction 合併時，系統才會真正把舊資料與墓碑一起清理掉。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;LSM-Tree 用「犧牲一部分讀取效能，並依賴背景合併」的代價，換取了極致的寫入速度。這就是電腦科學中經典的 Trade-off（權衡）藝術，也是大數據時代能支撐海量資料寫入的幕後英雄。&lt;/p&gt;</description></item></channel></rss>