• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            C++ Programmer's Cookbook

            {C++ 基礎} {C++ 高級} {C#界面,C++核心算法} {設計模式} {C#基礎}

            數據結構簡介(一)


             

            第一部分:數據結構簡介

             

            原文鏈接:Part 1: An Introduction to Data Structures

             

            介紹:
            本文是介紹在.Net平臺下使用數據結構的系列文章,共分為六部分,這是本文的第一部分.本文試圖考察幾種數據結構,其中有的包含在.Net Framework的基類庫中,有的是我們自己創建的.如果你對這些名詞不太熟悉,那么我們可以把數據結構看作是一種抽象結構或是類,它通常用來組織數據,并提供對數據的操作.最常見并為我們所熟知的數據結構就是數組array,它包含了一組連續的數據,并通過索引進行訪問.

            在閱讀本文內容之前,讓我們先看看這六部分的主要內容.如果你有什么想法,或覺得本文有什么遺漏之處,希望你通過e-mail(mitchell@4guysfromrolla.com)和我聯系,共同分享你的思想.假如有時間的話,我很高興將你的建議放到合適的部分,如有必要,可以在這篇系列文章中加上第七部分.

            第一部分:首先介紹數據結構在算法設計中的重要性.決定數據結構的優劣在于其性能.我們將經過嚴格分析數據結構的各種性能.此部分還將介紹.Net Frameword下兩種常用的數據機構:Array 和ArrayList.我們將考察其結構的操作方式及其效率.

            第二部分:我們將繼續從更多細節上分析ArrayList結構,同時還將介紹Queue類和Stack類.和ArrayList一樣,Queue和Stack存放的都是一組連續的數據集合,都屬于.Net Framework基類庫.與ArrayList不同的是,Stack和Queue只能以預先規定的序列順序讀取其數據(先進先出和先進后出),而ArrayList可以任意獲取數據項.我們將通過示例程序來考察Queue,Stack,并通過擴展ArrayList類來實現它們.之后,我們還要分析哈希表HashTable,它象ArrayList一樣可以直接訪問數據,不同的是它以key(字符串)為索引.

            ArrayList對數據直接讀取和存儲是一種理想的數據結構,同時,它也是支持數據搜索的候選方案.在第三部分,我們將考察二叉樹結構,對于數據搜索而言,它比ArrayList更加有效. .Net Framework并不包含此種內置數據結構,因此需要我們自己創建.

            二叉樹搜索的效率受制于插入到樹中的數據的順序.如果我們插入的是有序或近似有序的數據,實際上,它的效率不如ArrayList.為了將這兩種的優勢結合起來,在第四部分,我門將考察一種有趣的隨機數據結構——SkipList. SkipList既保留了二叉樹搜索的高效率,同時輸入數據的順序對其效率影響甚微.

            第五部分我們將注意力轉向通常用來表現圖形的數據結構.圖(graph)是眾多節點以及節點之間邊的集合.舉例來說,地圖就可以圖的形式來表現.城市是節點,公路則是連接節點之間的邊.許多現實問題都可以抽象成圖的形式,因此,圖也是我們經常要用到的數據結構.

            最后,第六部分我們將談到reprisent sets(表示集?)和disjoint sets(非關聯集,即交集為空?)集合是一種無序數據的集中.非關聯集是指它和另外一個集合沒有共同的元素.我們在程序編寫時會經常用到集合和非關聯集.我們將在這一部分中詳細描述它.


            數據結構性能分析

            當我們在思考一個特別的應用程序或者程序的問題時,多數開發人員(包括我自己)都將興趣集中到算法上以解決手頭的難題,或者為應用程序加上一個很酷的特色以豐富用戶的經驗.我們似乎很少聽到有人會為他所使用的數據結構而激動不已,嘖嘖贊嘆. 然而,用在一個特定算法中的數據結構能夠很大程度上影響其性能.最常見的例子就是在數據結構中查找一個元素.在數組中,查找過程所耗時間是與這個數組中元素的個數是成正比的.采用二叉數或者SkipLists(我找不到合適的翻譯,按前所述,它包含了隨機數的集合,也許看了后面的部分會想到合適的中文),耗時與數據個數比例成線型下降(sub-linear,我又黔驢詞窮了).當我們要搜索大量的數據時,數據結構的選擇對程序的性能尤其重要,其差別甚至達到數秒,乃至于數分鐘.

            既然在算法中使用的數據結構影響了算法的效率,因此比較各種數據結構的效率并從中選擇一種更佳的方法就顯得尤為重要.作為開發者而言,我們首先要關注的是隨著存儲的數據量的增長,數據結構性能是怎樣隨之改變的的?也就是說,每當數據結構中添加一個新元素時,它將怎樣影響數據結構的運行時間?

            考慮這樣一種情形,我們在程序中使用了System.IO.Directory.GetFiles(路徑)方法以返回文件的列表,存放到一個特定的字符串數組directory中.假設你需要搜索這個數組以判斷在文件列表中是否存在XML文件(即擴展名為.xml的文件),一種方法是掃描(scan,或者是遍歷)整個數組,當找到XML文件時,就設置一個標識.代碼可能是這樣:

            using System;
            using System.Collections;
            using System.IO;

            public class MyClass
            {
               public static void Main()
               {
                  string [] fs = Directory.GetFiles(@"C:\Inetpub\wwwroot");
                  bool foundXML = false;
                  int i = 0;
                  for (i = 0; i < fs.Length; i++)
                     if (String.Compare(Path.GetExtension(fs[i]), ".xml", true) == 0)
                     {
                        foundXML = true;
                        break;
                     }
              
                 if (foundXML)
                    Console.WriteLine("XML file found - " + fs[i]);
                 else
                    Console.WriteLine("No XML files found.");
                 
               }
            }


            現在我們來看看最糟糕的一種情況,當這個列表中不存在XML文件或者XML文件是在列表的最后,我們將會搜索完這個數組的所有元素.再來分析一下數組的效率,我們必須問問自己,"假設數組中現有n個元素,如果我添加一個新元素,增長為n+1個元素,那么新的運行時間是多少?(術語"運行時間"--running time,不能顧名思義地認為是程序運行所消耗的絕對時間,而指的是程序完成該任務所必須執行的步驟數.以數組而言,運行時間特定被認為是訪問數組元素所需執行的步驟數。)要搜索數組中的一個值,潛在的可能是訪問數組的每一個元素,如果數組中有n+1個元素,就將執行n+1次檢查。那就是說,搜索數組耗費的時間與數組元素個數成幾何線形比。

            當數據結構的長度趨于無窮大時,分析其結構的效率,我們把這種分析方法稱為漸進分析(asymptotic analysis)。漸進分析中常用的符號是大寫的O(big-Oh),以O(n)的形式描述遍歷數組的性能。O是術語學中big-Oh符號的表示,n則代表遍歷數組時隨長度增長而與之線形增長的程序執行步數。

            計算代碼塊中算法的運行時間的一種系統方法應遵循以下步驟:

            1、判斷組成算法運行時間的步驟。如前所述,對于數組而言,典型的步驟應是對數組進行讀寫訪問的操作。而對于其他數據結構則不盡然。特別地,你應該考慮的是數據結構自身的步驟,而與計算機內部的操作無關。以上面的代碼塊為例,運行時間應該只計算訪問數組的次數,而不用考慮創建和初始化變量以及比較兩個字符串是否相等的時間。
            2、找到符合計算運行時間條件的代碼行。在這些行上面置1。
            3、判斷這些置1的行是否包含在循環中,如果是,則將1改為1乘上循環執行的最大次數。如果嵌套兩重或多重循環,繼續對循環做相同的乘法。
            4、找到對每行寫下的最大值,它就是運行時間。

            現在我們按照這種步驟來標記上面的代碼塊。首先我們已經能夠確定與計算運行時間有關的代碼行,再根據步驟2,在數組fs被訪問的兩行代碼作上標記,一行是數組元素作為String.Compare()方法的參數,一行是在Console.WriteLine()方法中。我們將這兩行標記為1。然后根據步驟3,String.Compare()方法是在循環中,最大循環次數為n(因為數組長度為n)。因此將該行的標記1改為n。最后,我們得到的運行時間就是標記的最大值n,記為O(n)。(譯注:即為數據結構中通常所說的時間復雜度)

            O(n),或者說線形時間(linear-time),表示了多種算法運行時間中的一種。其他還有O(log2 n),O(n log 2 n),O(n2),O(2n)等等。我們無須關心這些繁雜的big-Oh記號,只需要知道在括號中的值越小,則代表數據結構的性能越好。舉例來說,時間復雜度(在這里我還是覺得用時間復雜度比運行時間更能理解)為O(log n)的算法遠比O(n)更有效率,因為log n


            注:

            我們需要溫習以下數學知識。在這里,log a b另外一種表示方法為ay=b。因此,log24=2,因為22=4Log2n增長速度比單個的n要慢得多,在第三部分我們將考察時間復雜度為O(log2n)的二叉樹結構。(這個注釋沒多大意思啊!)

            在這篇系列文章中,我們將計算每一種新的數據結構和它們的漸進操作運行時間,并通過相似的操作比較其他數據結構在運行時間上的區別。

            數組:一種線形的,可以直接訪問的,單一數據結構

            在程序編寫中,數組是最簡單也是最廣泛使用的數據結構。在所有的程序語言中數組都具備以下共同的屬性:
            1.數組的數據存儲在一段連續的內存之中;
            2.數組的所有元素都必須是同一種數據類型,因此數組又被認為是單一數據結構(homogeneous data structures);
            3.數組元素可以直接訪問。(在很多數據結構中,這一特點是不必要的。例如,文章第四部分介紹的數據結構SkipList。要訪問SkipList中的特定元素,你必須根據搜索其他元素直到找到搜索對象為止。然而對于數組而言,如果你知道你要查找第i個元素,就可以通過arrayName[i]來訪問它。)(譯注:很多語言都規定數組的下標從0開始,因此訪問第i個元素,應為arrayName[i-1])

            以下是數組常用的操作:
            1.分配空間
            2.數據訪問
            3.數組空間重分配(Redimensioning)

            在C#里聲明數組時,數組為空值(null)。下面的代碼創建了一個名為booleanArray的數組變量,其值為空(null):

            Bool [] boolleanArray;

            在使用該數組時,必須用一個特定數字給它分配空間,如下所示:

            booleanArray = new bool[10];

            通用的表述為:

            arrayName = new arrayType[allocationSize];

            它將在CLR托管堆里分配一塊連續的內存空間,足以容納數據類型為arrayTypes、個數為allocationSize的數組元素。如果arrayType為值類型(譯注:如int類型),則有allocationSize個未封箱(unboxed)的arrayType值被創建。如果arrayType為引用類型(譯注:如string類型),則有allocationSize個arrayType引用類型值被創建。(如果你對值類型和引用類型、托管堆和棧之間的區別不熟悉,請查閱“理解.Net公共類型系統Common Type System”)

            為幫助理解.Net Framework中數組的內部存儲機制,請看下面的例子:

            arrayName = new arrayType[allocationSize];

            This allocates a contiguous block of memory in the CLR-managed heap large enough to hold the allocationSize number of arrayTypes. If arrayType is a value type, then allocationSize number of unboxed arrayType values are created. If arrayType is a reference type, then allocationSize number of arrayType references are created. (If you are unfamiliar with the difference between reference and value types and the managed heap versus the stack, check out Understanding .NET's Common Type System.)

            To help hammer home how the .NET Framework stores the internals of an array, consider the following example:

            bool [] booleanArray;
            FileInfo [] files;

            booleanArray = new bool[10];
            files = new FileInfo[10];

            這里,booleanArray是值類型System.Boolean數組,而files數組則是引用類型System.IO.FileInfo數組。圖一顯示了執行這四行代碼后CLR托管堆的情況。

             
             
            圖一:在托管堆中順序存放數組元素

            請記住在files數組中存放的十個元素指向的是FileInfo實例。圖二強調了這一點(hammers home this point,有些俚語的感覺,不知道怎么翻譯),顯示了如果我們為files數組中的FileInfo實例分配一些值后內存的分布情況。
             


            圖二:在托管堆中順序存放數組元素


            .Net中所有數組都支持對元素的讀寫操作。訪問數組元素的語法格式如下:

            // 讀一個數組元素
            bool b = booleanArray[7];

            // 寫一個數組元素,即賦值
            booleanArray[0] = false;

            訪問一個數組元素的運行時間表示為O(1),因為對它的訪問時間是不變的。那就是說,不管數組存儲了多少元素,查找一個元素所花的時間都是相同的。運行時間之所以不變,是因為數組元素是連續存放的,查找定位的時候只需要知道數組在內存中的起始位置,每個元素的大小,以及元素的索引值。

            在托管代碼中,數組的查找比實際的實現稍微復雜一些,因為在CLR中訪問每個數組,都要確保索引值在其邊界之內。如果數組索引超出邊界,會拋出IndexOutOfRangeException異常。這種邊界檢查有助于確保我們在訪問數組不至于意外地超出數組邊界而進入另外一塊內存區。而且它不會影響數組訪問的時間,因為執行邊界檢查所需時間并不隨數組元素的增加而增加。

            注:如果數組元素特別多,索引邊界檢查會對應用程序的執行性能有稍許影響。而對于非托管代碼,這種邊界檢查就被忽略了。要了解更多信息,請參考Jeffrey Richter所著的Applied Microsoft .NET Framework Programming第14章。

            使用數組時,你也許需要改變數組大小。可以通過根據特定的長度大小創建一個新數組實例,并將舊數組的內容拷貝到新數組,來實現該操作。我們稱這一過程為數組空間重分配(redimensioning),如下代碼:

            using System;
            using System.Collections;

            public class MyClass
            {
               public static void Main()
               {
                  // 創建包含3個元素的int類型數組
                  int [] fib = new int[3];
                  fib[0] = 1;
                  fib[1] = 1;
                  fib[2] = 2;
                 
                  // 重新分配數組,長度為10
                  int [] temp = new int[10];

            // 將fib數組內容拷貝到臨時數組
                  fib.CopyTo(temp, 0);
                 
                  // 將臨時數組賦給fib
                  fib = temp;  
               }
            }

            在代碼的最后一行,fib指向包含10個元素的Int32類型數組。Fib數組中3到9(譯注:注意下標從0開始)的元素值默認為0(Int32類型)。

            當我們要存儲同種類型的數據(原文為heterogeneous types——異類數據類型,我懷疑有誤)并僅需要直接訪問數據時,數組是較好的數據結構。搜索未排序的數組時間復雜度是線形的。當我們對小型數組進行操作,或很少對它進行查詢操作時,數組這種結構是可以接受的。但當你的應用程序需要存儲大量數據,且頻繁進行查詢操作時,有很多其他數據結構更能適應你的工作。我們來看看本文接下來將要介紹的一些數據結構。(如果你要根據某個屬性查找數組,且數組是根據該屬性進行排序的,你可以使用二叉法(binary search)對其搜索,它的時間復雜度為O(log n),與在二叉樹中搜索的時間復雜度相同。事實上,數組類中包含了一個靜態方法BinarySearch()。如要了解該方法的更多信息,請參考我早期的一篇文章“有效地搜索有序數組”。

            注:.Net Framework同樣支持多維數組。與一維數組一樣,多維數組對數據元素的訪問運行時間仍然是不變的。回想一下我們前面介紹的在n個元素的一維數組中查詢操作的時間復雜度為O(n)。對于一個nxn的二維數組,時間復雜度為O(n2),因為每次搜索都要檢查n2個元素。以此類推,k維數組搜索的時間復雜度為O(nk)。

            ArrayList:可存儲不同類型數據、自增長的數組

            明確地,數組在設計時受到一些限制,因為一維數組只能存儲相同類型的數據,而且在使用數組時,必須為數組定義特定的長度。很多時候,開發人員要求數組更加靈活,它可以存儲不同類型的數據,也不用去關心數組空間的分配。在.Net Framework基類庫中提供了滿足這樣條件的數據結構——System.Collections.ArrayList。

            如下的一小段代碼是ArrayList的示例。注意到使用ArrayList時可以添加任意類型的數據,且不需要分配空間。所有的這些都由系統控制。

            ArrayList countDown = new ArrayList();
            countDown.Add(5);
            countDown.Add(4);
            countDown.Add(3);
            countDown.Add(2);
            countDown.Add(1);
            countDown.Add("blast off!");
            countDown.Add(new ArrayList());

            從深層次的含義來講,ArrayList使用的存放類型為object的System.Array對象。既然所有類型都是直接或間接從object派生,自然一個object類型的數組也可以存放任何類型的元素。ArrayList默認創建16個object類型元素的數組,當然我們也可以通過構造函數中的參數或設置Capacity屬性來定制ArrayList大小。通過Add()方法添加新元素,數組內部自動檢查其容量。如果添加新元素導致越界,則容量則自動成倍增加,我們稱為自增長。

            ArrayList和Array一樣,也可以通過索引直接訪問:

            // Read access
            int x = (int) countDown[0];
            string y = (string) countDown[5];

            // Write access
            countDown[1] = 5;

            // 會產生ArgumentOutOfRange 異常
            countDown[7] = 5;

            既然ArrayList存儲的是object類型的元素,因此從ArrayList中讀元素時應該顯示的指定類型轉換。同時要注意的是,如果你訪問的數組元素超過ArrayList的長度,系統會拋出System.ArgumentOutOfRange異常。

            ArrayList提供了標準數組所不具備的自增長靈活性,但這種靈活性是以犧牲性能為代價的,尤其是當我們存儲的是值類型——例如System.Int32,System.Double,System.Boolean等。它們在托管堆中是以未封箱形式(unboxed form)連續存放的。然而,ArrayList的內部機制是一個引用的object對象數組;因此,即使ArrayList中只存放了值類型,這些元素仍然會通過封箱(boxing)轉換為引用類型。如圖三所示:
             1-3.gif

            圖三:存儲連續塊的object引用的ArrayList

            在ArrayList中使用值類型,將額外進行封箱(boxing)和撤箱(unboxing)操作,當你的應用程序是一個很大的ArrayList,并頻繁進行讀寫操作時,會很大程度上影響程序性能。如圖3所示,對于引用類型而言,ArrayList和數組的內存分配是相同的。

            比較數組而言,ArrayList的自增長并不會導致任何性能的下降。如果你知道存儲到ArrayList的元素的準確數量,可以通過ArrayList構造函數初始化容量以關閉其自增長功能。而對于數組,當你不知道具體容量時,不得不在插入的數據元素超過數組長度的時候,手動改變數組的大小。

            一個經典的計算機科學問題是:當程序運行時超出了緩存空間,應該分配多少新的空間為最佳。一種方案是是原來分配空間的基礎上每次加1。例如數組最初分配了5個元素,那么在插入第6個元素之前,將其長度增加為6。顯然,這種方案最大程度上節約了內存空間,但代價太大,因為每插入一個新元素都要進行一次再分配操作。

            另一種方案剛好相反,也就是每次分配都在原來大小的基礎上增加100倍。如果數組最初分配了5個元素,那么在插入第6個元素之前,數組空間增長為500。顯然,該方案大大地減少了再分配操作的次數,但僅當插入極少的數據元素時,就會有上百的元素空間未使用,實在太浪費空間了!

            ArrayList的漸近運行時間和標準數組一樣。即使對ArrayList的操作是高開銷的,尤其是存儲值類型,其元素個數和每次操作的代價之間的關系與標準數組相同。

            posted on 2005-12-24 15:36 夢在天涯 閱讀(715) 評論(0)  編輯 收藏 引用 所屬分類: Data Arithmetic

            公告

            EMail:itech001#126.com

            導航

            統計

            • 隨筆 - 461
            • 文章 - 4
            • 評論 - 746
            • 引用 - 0

            常用鏈接

            隨筆分類

            隨筆檔案

            收藏夾

            Blogs

            c#(csharp)

            C++(cpp)

            Enlish

            Forums(bbs)

            My self

            Often go

            Useful Webs

            Xml/Uml/html

            搜索

            •  

            積分與排名

            • 積分 - 1804607
            • 排名 - 5

            最新評論

            閱讀排行榜

            久久99精品国产麻豆蜜芽| 亚洲一本综合久久| 国产精品久久久天天影视香蕉 | 久久精品无码一区二区三区免费| 国产呻吟久久久久久久92| 亚洲国产天堂久久综合| 亚洲人成网亚洲欧洲无码久久| 三上悠亚久久精品| 九九精品99久久久香蕉| 国产精品成人99久久久久| 国产精品99久久久久久董美香| 精品久久久久久久国产潘金莲| 久久精品国产99久久久古代 | 精品无码人妻久久久久久| 色综合合久久天天给综看| 国产精品久久亚洲不卡动漫| 亚洲国产成人久久综合区| 久久国产精品-久久精品| 国产色综合久久无码有码| 99久久精品免费看国产| 色综合久久中文字幕无码| 99久久综合国产精品免费| 国产成人精品久久亚洲高清不卡 国产成人精品久久亚洲高清不卡 国产成人精品久久亚洲 | 久久天天躁狠狠躁夜夜avapp| 成人午夜精品久久久久久久小说| 一本色道久久99一综合| 日本精品一区二区久久久| 四虎国产精品免费久久5151| 久久综合狠狠综合久久综合88| 一本大道久久香蕉成人网| 香港aa三级久久三级| 国内精品久久久人妻中文字幕| 精品国产青草久久久久福利| 亚洲国产日韩欧美久久| 久久综合九色综合久99| 2020最新久久久视精品爱| 久久国产精品久久国产精品| 国产精品一区二区久久| 9191精品国产免费久久| 91精品国产高清久久久久久91| 久久免费精品视频|