<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                # 第二章 算法分析 > 原文:[Chapter 2 Analysis of Algorithms](http://greenteapress.com/thinkdast/html/thinkdast003.html) > 譯者:[飛龍](https://github.com/wizardforcel) > 協議:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) > 自豪地采用[谷歌翻譯](https://translate.google.cn/) 我們在前面的章節中看到,Java 提供了兩種`List`接口的實現,`ArrayList`和`LinkedList`。對于一些應用,`LinkedList`更快;對于其他應用,`ArrayList`更快。 要確定對于特定的應用,哪一個更好,一種方法是嘗試它們,并看看它們需要多長時間。這種稱為“性能分析”的方法有一些問題: + 在比較算法之前,你必須實現這兩個算法。 + 結果可能取決于你使用什么樣的計算機。一種算法可能在一臺機器上更好;另一個可能在不同的機器上更好。 + 結果可能取決于問題規模或作為輸入提供的數據。 我們可以使用算法分析來解決這些問題中的一些問題。當它有效時,算法分析使我們可以比較算法而不必實現它們。但是我們必須做出一些假設: + 為了避免處理計算機硬件的細節,我們通常會識別構成算法的基本操作,如加法,乘法和數字比較,并計算每個算法所需的操作次數。 + 為了避免處理輸入數據的細節,最好的選擇是分析我們預期輸入的平均性能。如果不可能,一個常見的選擇是分析最壞的情況。 + 最后,我們必須處理一個可能性,一種算法最適合小問題,另一個算法適用于較大的問題。在這種情況下,我們通常專注于較大的問題,因為小問題的差異可能并不重要,但對于大問題,差異可能是巨大的。 這種分析適用于簡單的算法分類。例如,如果我們知道算法`A`的運行時間通常與輸入規模成正比,即`n`,并且算法`B`通常與`n ** 2`成比例,我們預計`A`比`B`更快,至少對于`n`的較大值。 大多數簡單的算法只能分為幾類。 + 常數時間:如果運行時間不依賴于輸入的大小,算法是“常數時間”。例如,如果你有一個`n`個元素的數組,并且使用下標運算符(`[]`)來訪問其中一個元素,則此操作將執行相同數量的操作,而不管數組有多大。 + 線性:如果運行時間與輸入的大小成正比,則算法為“線性”的。例如,如果你計算數組的和,則必須訪問`n`個元素并執行`n - 1`個添加。操作的總數(元素訪問和加法)為`2 * n -1`,與`n`成正比。 + 平方:如果運行時間與`n ** 2`成正比,算法是“平方”的。例如,假設你要檢查列表中的任何元素是否多次出現。一個簡單的算法是將每個元素與其他元素進行比較。如果有`n`個元素,并且每個元素與`n - 1`個其他元素進行比較,則比較的總數是`n ** 2 - n`,隨著`n`增長它與`n ** 2`成正比。 ## 2.1 選擇排序 例如,這是一個簡單算法的實現,叫做“選擇排序”(請見 <http://thinkdast.com/selectsort>): ```java public class SelectionSort { /** * Swaps the elements at indexes i and j. */ public static void swapElements(int[] array, int i, int j) { int temp = array[i]; array[i] = array[j]; array[j] = temp; } /** * Finds the index of the lowest value * starting from the index at start (inclusive) * and going to the end of the array. */ public static int indexLowest(int[] array, int start) { int lowIndex = start; for (int i = start; i < array.length; i++) { if (array[i] < array[lowIndex]) { lowIndex = i; } } return lowIndex; } /** * Sorts the elements (in place) using selection sort. */ public static void selectionSort(int[] array) { for (int i = 0; i < array.length; i++) { int j = indexLowest(array, i); swapElements(array, i, j); } } } ``` 第一個方法`swapElements`交換數組的兩個元素。元素的是常數時間的操作,因為如果我們知道元素的大小和第一個元素的位置,我們可以使用一個乘法和一個加法來計算任何其他元素的位置,這都是常數時間的操作。由于`swapElements`中的一切都是恒定的時間,整個方法是恒定的時間。 第二個方法`indexLowest`從給定的索引`start`開始,找到數組中最小元素的索引。每次遍歷循環的時候,它訪問數組的兩個元素并執行一次比較。由于這些都是常數時間的操作,因此我們計算什么并不重要。為了保持簡單,我們來計算一下比較的數量。 + 如果`start`為`0`,則`indexLowest`遍歷整個數組,并且比較的總數是數組的長度,我稱之為`n`。 + 如果`start`為`1`,則比較數為`n - 1`。 + 一般情況下,比較的次數是`n - start`,因此`indexLowest`是線性的。 第三個方法`selectionSort`對數組進行排序。它從`0`循環到`n - 1`,所以循環執行了`n`次。每次調用`indexLowest`然后執行一個常數時間的操作`swapElements`。 第一次`indexLowest`被調用的時候,它進行`n`次比較。第二次,它進行`n - 1`比較,依此類推。比較的總數是 ``` n + n?1 + n?2 + ... + 1 + 0 ``` 這個數列的和是`n(n+1)/2`,它(近似)與`n ** 2`成正比;這意味著`selectionSort`是平方的。 為了得到同樣的結果,我們可以將`indexLowest`看作一個嵌套循環。每次調用`indexLowest`時,操作次數與`n`成正比。我們調用它`n`次,所以操作的總數與`n ** 2`成正比。 ## 2.2 大 O 表示法 所有常數時間算法屬于稱為`O(1)`的集合。所以,說一個算法是常數時間的另一個方法就是,說它是`O(1)`的。與之類似,所有線性算法屬于`O(n)`,所有二次算法都屬于`O(n ** 2)`。這種分類算法的方式被稱為“大 O 表示法”。 注意:我提供了一個大 O 符號的非專業定義。更多的數學處理請參見 <http://thinkdast.com/bigo>。 這個符號提供了一個方便的方式,來編寫通用的規則,關于算法在我們構造它們時的行為。例如,如果你執行線性時間算法,之后是常量算法,則總運行時間是線性的。`∈`表示“是...的成員”: ``` f ∈ O(n) && g ∈ O(1) => f + g ∈ O(n) ``` 如果執行兩個線性運算,則總數仍然是線性的: ``` f ∈ O(n) && g ∈ O(n) => f + g ∈ O(n) ``` 事實上,如果你執行任何次數的線性運算,`k`,總數就是線性的,只要`k`是不依賴于`n`的常數。 ``` f ∈ O(n) && k 是常數 => kf ∈ O(n) ``` 但是,如果執行`n`次線性運算,則結果為平方: ``` f ∈ O(n) => nf ∈ O(n ** 2) ``` 一般來說,我們只關心`n`的最大指數。所以如果操作總數為`2 * n + 1`,則屬于`O(n)`。主要常數`2`和附加項`1`對于這種分析并不重要。與之類似,`n ** 2 + 100 * n + 1000`是`O(n ** 2)`的。不要被大的數值分心! “增長級別”是同一概念的另一個名稱。增長級別是一組算法,其運行時間在同一個大 O 分類中;例如,所有線性算法都屬于相同的增長級別,因為它們的運行時間為`O(n)`。 在這種情況下,“級別”是一個團體,像圓桌騎士的階級,這是一群騎士,而不是一種排隊方式。因此,你可以將線性算法的階級設想為一組勇敢,仗義,特別有效的算法。 ## 2.3 練習 2 本章的練習是實現一個`List`,使用 Java 數組來存儲元素。 在本書的代碼庫(請參閱 0.1 節)中,你將找到你需要的源文件: + `MyArrayList.java`包含`List`接口的部分實現。其中四個方法是不完整的;你的工作是填充他們。 + `MyArrayListTest.java`包含 JUnit 測試,可用于檢查你的工作。 你還會發現 Ant 構建文件`build.xml`。你應該可以從代碼目錄運行`ant MyArrayList`,來運行`MyArrayList.java`,其中包含一些簡單的測試。或者你可以運行`ant MyArrayListTest`運行 JUnit 測試。 當你運行測試時,其中幾個應該失敗。如果你檢查源代碼,你會發現四條 TODO 注釋,表示你應該填充的方法。 在開始填充缺少的方法之前,讓我們來看看一些代碼。這里是類定義,實例變量和構造函數。 ```java public class MyArrayList<E> implements List<E> { int size; // keeps track of the number of elements private E[] array; // stores the elements public MyArrayList() { array = (E[]) new Object[10]; size = 0; } } ``` 正如注釋所述,`size`跟蹤`MyArrayList`中由多少元素,而且`array`是實際包含的元素的數組。 構造函數創建一個 10 個元素的數組,這些元素最初為`null`,并且`size`設為`0`。·大多數時候,數組的長度大于`size`,所以數組中由未使用的槽。 Java 的一個細節:你不能使用類型參數實例化數組;例如,這樣不起作用: ``` array = new E [10]; ``` 要解決此限制,你必須實例化一個`Object`數組,然后進行類型轉換。你可以在 <http://thinkdast.com/generics> 上閱讀此問題的更多信息。 接下來,我們將介紹添加元素到列表的方法: ```java public boolean add(E element) { if (size >= array.length) { // make a bigger array and copy over the elements E[] bigger = (E[]) new Object[array.length * 2]; System.arraycopy(array, 0, bigger, 0, array.length); array = bigger; } array[size] = element; size++; return true; } ``` 如果數組中沒有未使用的空間,我們必須創建一個更大的數組,并復制這些元素。然后我們可以將元素存儲在數組中并遞增`size`。 為什么這個方法返回一個布爾值,這可能不明顯,因為它似乎總是返回`true`。像之前一樣,你可以在文檔中找到答案:<http://thinkdast.com/colladd>。如何分析這個方法的性能也不明顯。在正常情況下,它是常數時間的,但如果我們必須調整數組的大小,它是線性的。我將在 3.2 節中介紹如何處理這個問題。 最后,讓我們來看看`get`;之后你可以開始做這個練習了。 ```java public T get(int index) { if (index < 0 || index >= size) { throw new IndexOutOfBoundsException(); } return array[index]; } ``` 其實`get`很簡單:如果索引超出范圍,它會拋出異常; 否則讀取并返回數組的元素。注意,它檢查索引是否小于`size`,大于等于`array.length`,所以它不能訪問數組的未使用的元素。 在`MyArrayList.java`中,你會找到`set`的樁,像這樣: ```java public T set(int index, T element) { // TODO: fill in this method. return null; } ``` 閱讀`set`的文檔,在 <http://thinkdast.com/listset>,然后填充此方法的主體。如果再運行`MyArrayListTest`,`testSet`應該通過。 提示:盡量避免重復索引檢查的代碼。 你的下一個任務是填充`indexOf`。像往常一樣,你應該閱讀 <http://thinkdast.com/listindof> 上的文檔,以便你知道應該做什么。特別要注意它應該如何處理`null`。 我提供了一個輔助方法`equals`,它將數組中的元素與目標值進行比較,如果它們相等,返回`true`(并且正確處理`null`),則 返回。請注意,此方法是私有的,因為它僅在此類中使用;它不是`List`接口的一部分。 完成后,`再次運行MyArrayListTest`;`testIndexOf`,以及依賴于它的其他測試現在應該通過。 只剩下兩個方法了,你需要完成這個練習。下一個是`add`的重載版本,它接受下標并將新值存儲在給定的下標處,如果需要,移動其他元素來騰出空間。 再次閱讀 <http://thinkdast.com/listadd> 上的文檔,編寫一個實現,并運行測試進行確認。 提示:避免重復擴充數組的代碼。 最后一個:填充`remove`的主體。文檔位于 <http://thinkdast.com/listrem>。當你完成它時,所有的測試都應該通過。 一旦你的實現能夠工作,將其與我的比較,你可以在 <http://thinkdast.com/myarraylist> 上找到它。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看