第一節 如何用Go實現單鏈表

来源:https://www.cnblogs.com/lanrenji/archive/2018/09/22/9691195.html
-Advertisement-
Play Games

一、概念介紹 下麵這副圖是我們單鏈表運煤車隊。 每節運煤車就是單鏈表裡的元素,每節車廂里的煤炭就是元素中保存的數據。前後車通過鎖鏈相連,作為單鏈表運煤車,從1號車廂開始,每節車廂都知道後面拉著哪一節車廂,卻不知道前面是哪節車廂拉的自己。第一節車廂沒有任何車廂拉它,我們就叫它車頭,第五節車廂後面拉其他 ...


一、概念介紹

下麵這副圖是我們單鏈表運煤車隊。

每節運煤車就是單鏈表裡的元素,每節車廂里的煤炭就是元素中保存的數據。前後車通過鎖鏈相連,作為單鏈表運煤車,從1號車廂開始,每節車廂都知道後面拉著哪一節車廂,卻不知道前面是哪節車廂拉的自己。第一節車廂沒有任何車廂拉它,我們就叫它車頭,第五節車廂後面拉其他車廂,我們稱為車尾。

作為單鏈表它最大的特點就是能隨意增加車隊的長度,也能隨意減少車隊的長度。這是比數組公交車最大的優點。

二、Go語言實現講解

1、節點

每節車廂都由車體、繩索和煤炭構成。在Go語言中表示這種自定義組合體的類型就是結構,當然為了通用性,我們這裡要把車廂轉換成節點也就是元素,煤炭轉換成數據,繩索轉換成指針。

    type Node struct {
        data Object
        next *Node
    }

用張圖來描述這種對應關係。

這裡結構體Node表示車廂,data表示煤炭用Object類型,next是牽著下節車廂的繩索,用指針表示。

對於車廂來說,除了放煤炭外,還能放瓜果、衣物、飯菜等等,所以這裡data的類型必須通用,當然Go里是沒有Java里的Object類型的,所以我們就自己定義了一個。

    type Object interface{}

2、鏈表

光有車廂還不夠,我們還要描述車廂組成的車隊。每個單鏈表車隊都有車頭、車尾和車廂數量,我們同樣以結構來表現。

    type List struct {
        size uint64 // 車輛數量
        head *Node  // 車頭
        tail *Node  // 車尾
    }

3、方法

(1)初始化

第一步就是組裝單鏈表車隊,這就是初始化。不過開始的時候車隊是個空殼,一個車廂沒有。

    func (list *List) Init() {
        (*list).size = 0    // 此時鏈表是空的
        (*list).head = nil  // 沒有車頭
        (*list).tail = nil  // 沒有車尾
    }

(2)添加元素

當前的單鏈表是空的,所以我們要向裡面添加元素。

    func (list *List) Append(node *Node) {
        (*list).head = node // 這是單鏈表的第一個元素,也是鏈表的頭部
        (*list).tail = node // 同時是單鏈表的尾部
        (*list).size = 1    // 單鏈表有了第一個元素
    }

現在單鏈表有了第一個元素,我還想再添加一個元素,當然是添加到單鏈表尾部。

    func (list *List) Append(node *Node) {
        if (*list).size == 0 { // 無元素的時候添加
            (*list).head = node // 這是單鏈表的第一個元素,也是鏈表的頭部 
            (*list).tail = node // 同時是單鏈表的尾部
            (*list).size = 1    // 單鏈表有了第一個元素
        } else { // 有元素了再添加
            oldTail := (*list).tail
            (*oldTail).next = node  // node放到尾部元素後面
            (*list).tail = node     // node成為新的尾部
            (*list).size++          // 元素數量增加
        }
    }

分析上面的代碼存在3處疑點,元芳你怎麼看?屬下認為
第一,node如果為空,則添加無任何意義;
第二,代碼中存在重覆的地方;
這第三麽,卑職如何才能知道新增結果?
下麵大衛哥順著元芳的思路改進下代碼。

    func (list *List) Append(node *Node) bool {
        if node == nil {
            return false
        }
        
        (*node).next = nil
        // 將新元素放入單鏈表中
        if (*list).size == 0 { 
            (*list).head = node  
        } else { 
            oldTail := (*list).tail
            (*oldTail).next = node  
        }
    
        // 調整尾部位置,及鏈表元素數量
        (*list).tail = node // node成為新的尾部  
        (*list).size++      // 元素數量增加
    
        return true
    }

(3)插入元素

一天領導讓大衛哥把他小舅子安排到第一個,於是大衛哥為了拍好領導馬屁。絞盡腦汁弄了一個方法。

   func (list *List) Insert(node *Node) bool {
      if node == nil {
          return false
      }
 
      (*node).next = (*list).head   // 領導小舅子排到之前第一名前面
      (*list).head = node           // 領導小舅子成為第一名
      (*list).size++
      return true
  }

來托關係插隊的人越來越多,領導的關係戶不能動,只能插後面人的隊了。於是大衛哥又修改了代碼,增加了位置參數。

    func (list *List) Insert(i uint,node *Node) bool {
        // 空的節點、索引超出範圍和空鏈表都無法做插入操作
        if node == nil || i > (*list).size || (*list).size == 0 {
            return false
        }
    
        if i == 0 { // 直接排第一,也就領導小舅子才可以
            (*node).next = (*list).head
            (*list).head = node
        } else {
            // 找到前一個元素
            preItem := (*list).head
            for j := 1 ; j < i; j++ { // 數前面i個元素
                preItem = (*preItem).next
            }
            // 原來元素放到新元素後面,新元素放到前一個元素後面
            (*node).next = (*preItem).next
            (*preItem).next = preItem
        }
    
            (*list).size++ 
    
            return true
    }

(4)刪除元素

插隊的關係戶太多,影響了正常排隊的人,被人投訴,大衛哥只好想辦法刪除一些。

    func (list *List) Remove(i uint, node *Node) bool {
        if i >= (*list).size {
            return false
        }
        
        if i == 0 { // 刪除頭部
            node = (*list).head
            (*list).head = (*node).next
            if (*list).size == 1 { // 如果只有一個元素,那尾部也要調整
                (*list).tail = nil
            }
        } else {
            preItem := (*list).head
            for j := 1; j < i; j++ {
                preItem = (*preItem).next
            }
        
            node = (*preItem).next
            (*preItem).next = (*node).next
    
            if i == ((*list).size - 1) { // 若刪除的尾部,尾部指針需要調整
                (*list).tail = preItem
            }
        }
        (*list).size--
        return true
    }

(5)獲取

為了獲取某個位置的元素,我們需要一個方法。

    func (list *List) Get(i uint) *Node {
        if i >= (*list).size {
            return nil
        }
    
        item := (*list).head
        for j := 0; j < i ; j++ {    // 從head數i個
            item = (*item).next
        }
    
        return item
    }

到這裡基本框架已經出來了,不過還有幾個介面還沒實現,作為課後作業。

三、小結

單鏈表就和列車類似,一個接著一個,所以本節從列車類比介紹了單鏈表的Go語言實現。在介面實現部分大衛哥以序號作為鏈表中每個節點的操作關鍵字。在實際應用中,我們往往以data中的某一個欄位作為操作關鍵字。所以這也衍生出鏈表的不同介面,大家可以參考大衛哥留的鏈接中的代碼實現作為理解。同時有些實現將表頭獨立出來並不存放數據,這在一定程度上簡化了代碼的實現。

代碼下載

四、習題

(1)補全GetSize,RemoveAll,GetHead和GetTail的定義和實現。
(2)以data作為參數,考慮單鏈表的實現。
(3)將單鏈表的head獨立出來,此時的head是獨立的,不存放data,如下圖,考慮單鏈表的實現,並比較這種實現。

(4)如果將head和tail都獨立出來,都不存放data,此時的單鏈表如何實現?這樣實現的代碼在插入刪除操作時候是不是更容易點?


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 5.object和Class的深入理解 屬性和方法 (視頻下載) (全部書籍) 抽象Abstract:【新手可忽略不影響繼續學習】 (視頻下載) (全部書籍)很多java 的書中都談到了抽象abstract的概念,到底什麼是抽象?馬克-to-win:抽取關鍵相關特性(屬性和方法)構成對象,用程式的方 ...
  • 最近的一個小項目是做一個簡單的數據倉庫,需要將其他資料庫的數據抽取出來,並通過而出抽取成頁面需要的數據,以空間換時間的方式,讓後端報表查詢更快。 因為在抽取的過程中,有一定的先後順序,需要做一個任務調度器,某一優先順序的會先執行,然後會進入下一個優先順序的隊列任務中。 先定義了一個Map的集合,key是 ...
  • collection模塊: 在內置數據類型(dict、list、set、tuple)的基礎上,collections模塊還提供了幾個額外的數據類型:Counter、deque、defaultdict、namedtuple和OrderedDict等。 1:namedtuple 生成可以使用名字來訪問元 ...
  • 簡介: 讀寫鎖與互斥量類似,但讀寫鎖允許更高的並行性。其特性為:寫獨占,讀共用。 讀寫鎖特性: 1. 讀寫鎖是“寫模式加鎖”時,解鎖前,所有對該鎖加鎖的線程都會被阻塞。 2. 讀寫鎖是“讀模式加鎖”時,如果線程以讀模式對其加鎖會成功。如果線程以寫模式加鎖會阻塞。 3. 讀寫鎖是“讀模式加鎖”時,如果 ...
  • 標準庫 map set 大鍋燉 一,關聯容器有哪些 | 按關鍵字有序保存元素 | | | | | | map | 保存key和value | | set | 只保存key | | mulutimap | key可以重覆出現 | | multiset | key可以重覆出現 | | 無序集合 | | ...
  • 一、什麼是集合 集合就是不同對象的無序聚集。那麼鏈表和集合有什麼關係呢?我們來變個魔術。如下圖是各種顏色組成的鏈表: 下麵我們一步步把鏈表變成集合。 第一步砍去鏈接 第二步去掉重覆 第三步放到一個框里搖一搖就成集合了 可以看出集合有這些特點: 無序:鏈表去掉鏈接,就是去掉元素間有序狀態。 不重覆:去 ...
  • 一、什麼是迴圈鏈表 迴圈鏈表的節點形成一個圈。把單鏈表的尾巴指向開頭形成單迴圈鏈表。把雙向鏈表的尾巴與開頭鏈接起來就形成雙向迴圈鏈表。使用迴圈鏈表可以不斷的繞圈尋找所需要的數據,而不需要像單鏈表那樣每次都從開頭開始尋找,可以提高查詢的效率。 今天大衛哥先實現一個單向迴圈鏈表,雙向迴圈鏈表的實現就交給 ...
  • kdTree相關原理及c++實現 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...