簡體   English   中英

什么是應該使用鏈接列表的真實世界示例?

[英]What are real world examples of when Linked Lists should be used?

另一位程序員提到,在他的職業生涯中,他們還沒有找到在任何專業軟件中使用鏈表數據結構的用例。 我想不出任何好的例子。 他主要是 C# 和 Java 開發人員

任何人都可以舉一些例子,說明這是解決特定現實世界問題的正確數據結構嗎?

相關:鏈接列表的實際示例是什么?

與類似的數據結構(例如 static 或動態擴展 arrays)相比,鏈表提供了幾個優勢。

  1. LinkedLists 不需要 memory 的連續塊,因此可以幫助減少 memory 碎片
  2. LinkedLists 支持有效刪除元素(動態 arrays 通常會強制所有元素移動)。
  3. LinkedLists 支持元素的高效添加(如果特定添加超出當前容量,動態 arrays 會導致重新分配 + 復制)

任何這些優點對程序非常有價值的地方(並且 LinkedList 的缺點可以忽略不計)都將是使用 LinkedList 的地方。

一個真實的例子是 FIFO 隊列。 一個簡單的基於數組的列表對此非常不利,因為您需要在一端添加並在另一端刪除,其中一個操作將是 O(n) 與基於數組的列表(除非您添加額外的邏輯使用開始和結束索引),而兩者都是 O(1) 使用鏈表而不需要額外的努力。

侵入式鏈表是游戲開發的有趣野獸。 例如,有一個侵入性的單鏈接或雙鏈接“渲染”列表是一種常見的做法:

class Renderable /* or class Object, whatever */
{
  // ...
  Renderable * m_pNext;
  Renderable * m_pPrev; // or not, if singly-linked
  // ...
}

隨着可渲染對象的出現和消失,它們可以在此列表中注冊自己——而不會導致任何 memory 分配。 如果他們的渲染深度或優先級被改變,他們可以移除並重新插入自己,等等。

到了渲染的時候,你需要做的就是找到列表的頭部和zip,調用相應的方法!

(當然,這個主題有很多變體,有多個單獨的列表等。而且你不需要有一個侵入性的列表來完成這項工作,我只是覺得侵入性的列表很有趣。)

鏈表( 與散列表配對)對於LRU 緩存非常有用。

每個 Get 都需要將一個節點撞到列表的前面,這個操作對於鏈表來說真的很便宜。

堆棧和隊列是鏈表的非常清楚的例子,但正如其他人已經提到的那樣,我想添加一些其他例子:

DOM 將節點存儲為鏈表。 一個簡單的 javascript 示例,在任何語言中都完全相同:

for (var node = parent.firstChild; node != null; node = node.nextSibling) {
    // ..
}

我想 java 開發人員在某個時候遇到了 XML。

樹是鏈表的另一個很好的例子,盡管它們不是簡單的一維的。 做過很多 java 開發的人可能遇到過 TreeMaps 和 TreeSets。

整個討論對我來說似乎有點愚蠢。 鏈表是一種無處不在的基本數據結構。 人們可能認為他/她沒有遇到過它們的唯一原因是您實際上不必擔心當今高級語言中數據結構的實現,但它們當然仍然存在。

單鏈表的一些例子。

  1. Microsoft Word、Paint 等任何應用程序的撤消按鈕:狀態鏈接列表。
  2. GPS 導航:map 數據的鏈表。 從起點到終點的旅行是遍歷所有節點的示例。 通過 GPS 重新路由是 map 數據的添加和刪除操作的示例。

雙鏈表的一些例子。

  1. 瀏覽器的下一個和上一個按鈕:一個鏈接的 URL 列表。
  2. 圖像查看器的下一個和上一個按鈕:圖像的鏈接列表
  3. Photoshop 的撤消和重做按鈕,狀態鏈接列表。

它們一直在發生,只要 object 具有指向同一類型的另一個 object 的屬性。 在 CLR 中,由於InnerException屬性,異常形成了一個鏈表。

不可變鏈表是一種非常有價值的結構,因為您可以與其他具有相同尾部的列表“共享結構”。 大多數函數式語言都包含一個不可變的鏈表類型作為它們的基本數據結構之一,並且這些類型在所有地方都被使用。

我為 USB 主機 controller 編寫了 BIOS 擴展(基本上是 BIOS 的設備驅動程序,用匯編語言編寫)。 -- 在匯編中實現像鏈表這樣的高級看似抽象的數據結構並不像聽起來那么難。 -- controller 使用主 memory 中的數據包鏈接列表為鍵盤和磁盤的 I/O 請求提供服務。 我在另一個鏈表中維護了一個免費數據包池。 執行 I/O 基本上包括從空閑列表的開頭抓取一個空閑數據包,配置數據包,將數據包添加到設備列表中,並在 I/O 完成時將數據包重新添加到空閑池的開頭。 鏈表對於像這樣在對象周圍移動非常快,尤其是當對象很大時,因為對象實際上不必移動。 只有它們的指針需要更新。

基於數組的隊列需要:

  • 使用開始/結束索引指針,這很快但需要固定隊列的大小,以便生產者必須在消費者隊列已滿時等待,如果有多個生產者,則在整個數據包填滿時鎖定隊列
  • 每當您插入/刪除時移動隊列中的所有元素,這對於大型對象來說很慢

因此,鏈表是實現任意長的大對象先進先出隊列的好方法。

另一件需要注意的事情是,對於小對象或從常規堆而不是自定義空閑池分配對象的地方,arrays 可以更快,因為如果不經常進行復制並不會那么慢,並且重復分配每次添加新元素時,鏈表都需要從堆中獲取速度很慢。

當然,您可能希望使用一些一次性測試代碼來模擬和測量您的特定場景以找到答案。 我喜歡用鏈表和 arrays 運行幾百萬次循環,用小對象和大對象,以及每個實時需要多長時間。 有時你會感到驚訝。

作為.Net 中最好的現實世界示例,請考慮MultiCastDelegate

以這種方式實現的鏈接列表,其中列表鏈接方面直接支持到類型中而不是作為單獨的容器,可以非常強大和高效。 然而,它們伴隨着各種權衡。
一個明顯的問題是代碼重復,您必須在每種類型中烘焙此邏輯。 諸如擴展基礎 class 提供指針的技術是混亂的(如果沒有泛型,您會失去強類型)並且從性能的角度來看通常是不可接受的。
另一個是您僅限於每種類型的一個實現。 如果不在每個實例中插入一個額外的字段並更新所有相關代碼,就無法將單鏈接結構變成雙鏈接結構。

鏈表是Dancing Links的基本數據結構,它是用於有效實現算法 X的技術,它是一種回溯算法,用於找到 NP 完全精確覆蓋問題的所有解決方案,許多難題自然可以簡化為。

如前所述,當您需要插入和刪除元素時,鏈表非常有用。

例如,為了幫助在我的代碼中調試 memory 管理,我最近在我的所有參考計數對象之間實現了一個鏈接列表,以便在程序結束時(當參考應該全部達到零並刪除對象時)我可以確切地看到是什么仍然存在,通常導致我能夠在問題的根源上找到單個 object。

CPython 實現了類似的東西,它幾乎在每一個構建時都帶有一個龐大的鏈表,並帶有調試功能。

響應標簽“數據結構”,在匯編等低級別,鏈表是存儲其他數據結構的可變長度列表的理想方式。 維護列表長度或結尾沒有開銷,也不需要固定大小的列表項。 最后一個原因也適用於高級語言。

如果我可以以與其他人略有不同的方式回答這個問題,那么最近我一直在使用鏈接列表來組織音樂列表。 這些列表提供了一種按藝術家、歌曲、專輯甚至收視率或歌曲長度存儲和排序音樂的絕佳方式。 我的是一個雙向鏈表,但我可以看到它也適用於單鏈表。 購物清單也很合適,如果你像我媽媽一樣有強迫症,你可以很容易地把它整理成過道和最后的冷凍食品!

如果我們包括堆棧和隊列,那么隊列顯然是打印隊列或音樂播放列表(顯然任何意義上的列表都很好)。

正如 daustin777 指出的那樣,鏈表就在你身邊,你甚至可能都不知道。 但問題的實用性在於它們允許以相對容易和靈活的方式執行一些非常基本的操作。 例如,不排序,只是交換指針。 需要在任意位置插入或移除? 在列表中插入或刪除您的指針。 需要向前和向后迭代...鏈表。 雖然不完全是一個商業示例,但我希望這足以澄清它,以便您將其應用到您自己的真實世界商業案例中。

堆棧和隊列是鏈表的示例。

對於最大節點數限制在 100 或以下的問題,鏈表是一個合理的解決方案。 冒泡排序和其他被認為是次優的事情也是如此。

在野外發現的鏈表實現:

Python 的 LRU 緩存

...
...
    cache_get = cache.get    # bound method to lookup a key or return None
    cache_len = cache.__len__  # get cache size without calling len()
    lock = RLock()           # because linkedlist updates aren't threadsafe
    root = []                # root of the circular doubly linked list
...
...

但請注意, _lru_cache_wrapper function 實際上是從C module 實現導入的:

...
...
typedef struct lru_list_elem {
    PyObject_HEAD
    struct lru_list_elem *prev, *next;  /* borrowed links */
    Py_hash_t hash;
    PyObject *key, *result;
} lru_list_elem;
...
...

鏈表優點:

  • 動態存儲消除碎片
  • 快速插入/移除
  • 快速訪問第一個元素(如果雙向實現則結束)
  • 高效的 memory 使用

鏈表缺點:

  • 慢速穿越

在我的腦海中,我會使用鏈表來實現堆棧、隊列和消息泵。 我還將使用多引用鏈接列表實現數據樹,以實現快速插入/刪除。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM