數據結構 鏈表_單鏈表的實現與分析

来源:http://www.cnblogs.com/idreamo/archive/2017/11/18/7855826.html
-Advertisement-
Play Games

結構體List則表示鏈表這種數據結構(見示例1)。這個結構由5個成員組成:size表示鏈表中元素個數;match並不由鏈表本身使用,而是由鏈表數據結構派生而來的新類型所使用;destroy是封裝之後傳遞給list_init的析構函數;head是指向鏈表中頭結點元素的指針;tail則是指向鏈表中末尾結... ...


單鏈表的實現與分析

結構體ListElmt表示鏈表中的單個元素(見示例1),這個結構體擁有兩個成員,就是前面介紹的數據成員和指針成員。

結構體List則表示鏈表這種數據結構(見示例1)。這個結構由5個成員組成:size表示鏈表中元素個數;match並不由鏈表本身使用,而是由鏈表數據結構派生而來的新類型所使用;destroy是封裝之後傳遞給list_init的析構函數;head是指向鏈表中頭結點元素的指針;tail則是指向鏈表中末尾結點元素的指針。

示例1:鏈表抽象數據類型的頭文件

    /*list.h*/  
    #ifndef LIST_H  
    #define LIST_H  
      
    #include <stdio.h>  
    /*Define a structure for linked list elements.*/  
    typedef struct ListElmt_  
    {  
        void *data;  
        struct ListElmt_ *next;  
    } ListElmt;  
      
    /*Define a structure for linked lists.*/  
    typedef struct List_  
    {  
        int size;  
        int (*match)(const void *key1,const void *key2);  
        void (*destroy)(void *data);  
        ListElmt *head;  
        ListElmt *tail;  
    } List;  
      
    /*Public Interface*/  
    void list_init(List *list,void(*destroy)(void *data));  
    void list_destroy(List *list);  
    int list_ins_next(List *list,ListElmt *element,const void *data);  
    int list_rem_next(List *list,listElmt *element,void **data);  
    #define list_size(list)((list)->size)  
      
    #define list_head(list)((list)->head)  
    #define list_tail(list)((list)->tail)  
    #define list_is_head(list,element)(element==(list)->head ? 1 : 0)  
    #define list_is_tail(element)((element)->next==NULL ? 1 : 0)  
    #define list_data(element)((element)->data)  
    #define list_next(element)((element)->next)  
      
    #endif // LIST_H  

 

list_init

list_init用來初始化一個鏈表以便能夠執行其他操作(見示例2)。

初始化鏈表是一種簡單的操作,只要把鏈表的size成員設置為0,把函數指針成員destroy設置為定義的析構函數,head和tail指針全部設置為NULL即可。

list_init的複雜度為O(1),因為初始化過程中的所有步驟都能在一段恆定的時間內完成。

示例2:鏈表抽象數據類型的實現

    /*list.c*/  
    #include <stdio.h>  
    #include <string.h>  
      
    #include "lish.h"  
      
    /*list_init*/  
    void list_init(List *list,void(*destroy)(void *data))  
    {  
        list->size = 0;  
        list->destroy = destroy;  
        list->head = NULL;  
        list->tail = NULL;  
      
        return;  
    }  
      
    /*list_destroy*/  
    void list_destroy(List *list)  
    {  
        void *data;  
        /*Remove each element*/  
        while(list_size(list)>0)  
        {  
            if(list_rem_next(list,NULL,(void **)&data)==0 && list->destroy!=NULL)  
            {  
                /*Call a user-defined function to free dynamically allocated data.*/  
                list->destroy(data);  
            }  
        }  
        memset(list,0,sizeof(list));  
        return;  
    }  
      
    /*list_ins_next*/  
    int list_ins_next(List *list,ListElmt *element,const void *data)  
    {  
        ListElmt *new_element;  
          
        /*Allocate storage for the element*/  
        if((new_element=(ListElmt *)malloc(sizeof(ListElmt)))==NULL)  
            return -1;  
        /*insert the element into the list*/  
        new_element->data=(void *)data;  
        if(element == NULL)  
        {  
            /*Handle insertion at the head of the list. */  
            if(list_size(list)==0)  
                list_tail = new_element;  
                  
            new_element->next = list->head;  
            list->head = new_element  
        }  
        else   
        {  
            /*Handle insertion somewhere other than at the head*/  
            if(element->next==NULL)  
                list->tail = new_element;  
              
            new_element->next = element->next;  
            element->next = new_element;  
        }  
        /*Adjust the size of the list of account for the inserted element. */  
        list->size++;  
          
        return 0;  
    }  
      
    /*list_rem_next*/  
    int list_rem_next(List *list,ListElmt *element,void **data)  
    {  
        ListElmt *old_element;  
          
        /*Do not allow removal from an empty list. */  
        if(list_size(list) == 0)  
            return -1;  
          
        /*Remove the element from the list. */  
        if(element == NULL)  
        {  
            /*Handle removal from the head of the list. */  
            *data = list->head->data;  
            old_element = list->head;  
            list->head = list->head->next;  
              
            if(list_size(list) == 1)  
                list->tail = NULL;  
        }  
        else   
        {  
            /*Handle removal from somewhere other than the head. */  
            if(element->next == NULL)  
                return -1;  
              
            *data = element->next->data;  
            old_element = element->next;  
            element->next = element->next->next;  
              
            if(element->next == NULL)  
                list->tail = element;  
        }  
        /*Free the storage allocated by the abstract datatype.*/  
        free(old_element);  
        /*Adjust the size of the list account for the removed element. */  
        list->size--;  
        return 0;  
    }  

 

list_destroy

list_destroy用來銷毀鏈表(見示例2),其意義就是移除鏈表中的所有的元素。

如果調用list_init時destroy參數不為NULL,則當每個元素被移除時都將調用list_destroy一次。

list_destroy的運行時複雜度為O(n),n代錶鏈表中的元素個數,這是因為list_rem_next的複雜度為O,而移除每個元素時都將調用list_rem_next一次。

list_ins_next

list_ins_next將一個元素插入由element參數所指定的元素之後(見示例2)。該調用將新元素的數據指向由用戶傳遞進來的數據。向鏈表中插入新元素的處理步驟很簡單,但需要特別小心。有兩種情況需要考慮:插入鏈表頭部和插入其他位置。

一般來說,要把一個元素插入鏈表中,需要將新元素的next指針指向它之後的那個元素,然後將新元素位置之前的結點next指針指向新插入的元素(見圖3)。但是,當從鏈表頭部插入時,新元素之前沒有別的結點了。因此在這種情況下,將新元素的next指針指向鏈表的頭部,然後重置頭結點指針,使其指向新元素。回顧一下前面介面設計,當傳入element為null時代表新的元素將插入鏈表頭部。另外需要註意的是,無論何時當插入的元素位於鏈表末尾時,都必須重置鏈表數據結構的tail成員使其指向新的結點。最後,更新統計鏈表中結點個數的size成員。


list_rem_next

list_rem_next從鏈表中移除由element所指定的元素之後的那個結點(見示例2)。移除的是element之後的那個結點而不是移除element本身。這個調用也需要考慮兩個因素,移除的是頭結點以及移除其餘位置上的結點。

移除操作是很簡單的,但同樣需要註意一些細節問題(見圖4)。一般來說,從鏈表中移除一個元素,需要將移除的目標結點前一個元素的next結點指針指向目標結點的下一個元素。但是,當移除的目標結點是頭指針時,目標結點之前並沒有其他元素了。因此,在這種情況下,只需要將鏈接表的head成員指向目標結點的下一個元素。同插入操作一樣,當傳入的element為NULL時就代錶鏈表的頭結點需要移除。另外,無論何時當移除的目標結點是鏈表的尾部結點時,都必須更新鏈表數據結構中的tail成員,使其指向新的尾結點,或者當移除操作使得整個鏈表為空鏈表時,需要把tail設置為NULL。最後,更新鏈表的size成員,使其減1。當這個調用返回時,data將指向已經移除結點的數據域。


list_rem_next的複雜度為O(1),因為所有的移除步驟都在恆定的時間內完成。

list_size、list_head、list_tail、list_is_tail、list_data以及list_next

這些巨集實現了一些鏈表中的一些簡單操作(見示例1)。一般來說,它們提供了快速訪問和檢測List和ListElmt結構體中成員的能力。

這些操作的複雜度都是O(1),因為訪問和檢測結構體成員都可以在恆定的時間內完成。


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

-Advertisement-
Play Games
更多相關文章
  • 一、簡介 Topshelf可用於創建和管理Windows服務。其優勢在於不需要創建windows服務,創建控制台程式就可以。便於調試。 二、官方地址: 1、官網:http://topshelf-project.com/ 2、官方文檔:https://topshelf.readthedocs.io/e ...
  • 關於WCF即可以寄宿於IIS,也可以自我寄宿,本文采用的是自我寄宿方式。之所以採用自我寄宿方式,很大程度上,在一些特殊的場景,例如下載大文件(如幾百MB、1G等)、圖片、文檔等,如果以IIS為宿主,可能會產生記憶體不夠用。所以這裡採用自我寄宿的方式為例子。WCF是由微軟開發的一系列支持數據通信的應用程... ...
  • 問題描述 在發佈項目的時候,有一些文件是json文件,在網頁中進行載入,但是在IIS7發佈的時候,json文件居然是404,無法找到,在URL上輸入地址也一樣。 錯誤原因 IIS內部機制,不支持直接訪問json擴展名文件,沒有mime映射。因此IIS不認Json文件,如需要支持訪問json文件時,需 ...
  • 上一篇文章介紹了使用Authorize特性實現了ASP.NET MVC中針對Controller或者Action的授權功能,實際上這個特性是MVC功能的一部分,被稱為過濾器(Filter),它是一種面向切麵編程(AOP)的實現,本章將從以下幾個方面來介紹ASP.NET MVC中的過濾器。 ● ASP ...
  • 首先出個題: 如圖: 假設對成長速度顯示規定如下: 成長速度為5顯示1個箭頭; 成長速度為10顯示2個箭頭; 成長速度為12顯示3個箭頭; 成長速度為15顯示4個箭頭; 其他都顯示都顯示0各箭頭。 用代碼怎麼實現? 差一點的if,else: Js代碼 var add_level = 0; if(ad ...
  • 背水一戰 Windows 10 之 控制項(控制項基類 - UIElement ): 拖放的基本應用, 手動開啟 UIElement 的拖放操作 ...
  • 在我們開發工作流模塊的時候,有時候填寫申請單過程中,暫時不想提交審批,那麼可以暫存為草稿,以供下次繼續填寫或者提交處理,那麼這個草稿的功能是比較實用的,否則對於一些填寫內容比較多的申請單,每次要重填寫很多數據,那會被用戶罵的,從用戶的角度上來講,提供草稿保存的功能是比較友好的。本篇隨筆介紹在工作流模... ...
  • 簡介 排序演算法是我們編程中遇到的最多的演算法。目前主流的演算法有8種。 平均時間複雜度從高到低依次是: 冒泡排序(o(n2)),選擇排序(o(n2)),插入排序(o(n2)),堆排序(o(nlogn)), 歸併排序(o(nlogn)),快速排序(o(nlogn)), 希爾排序(o(n1.25)),基數排 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...