日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

读书笔记_算法第四版(一)

發布時間:2023/12/10 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 读书笔记_算法第四版(一) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

算法第四版(謝路云譯)

官方網站:http://algs4.cs.princeton.edu/home/有部分源代碼和部分課后習題答案。

個人練習代碼:https://github.com/morefans/AlgorithmsFourthEdition

第1章 基礎

1.1 基礎編程模型

l? Java程序的基本結構;原始數據類型與表達式;語句;簡便記法;數組;靜態方法;API;字符串;輸入輸出;二分查找。

l? 以下為“答疑”和“練習”中的知識點:

l? Math.abs(-2147483648)返回-2147483648(整數溢出)。

1.2 數據抽象

l? 使用抽象數據類型;抽象數據類型舉例;抽象數據類型的實現;更多抽象數據類型的實現;數據類型的設計。

l? 以下為“答疑”和“練習”中的知識點:

1.3 背包、隊列和棧

l? 許多基礎數據類型都和對象的集合有關。具體來說,數據類型的值就是一組對象的集合,所有操作都是關于添加、刪除或是訪問集合的對象。背包、隊列和棧就是,三者不同之處在于刪除或是訪問對象的順序不同。

l? 用到泛型和迭代:(Iterable接口要實現publicIterator<Item> iterator()方法)

public class Bag<Item> implementsIterable<Item>

public class Queue<Item> implementsIterable<Item>

public class Stack<Item> implementsIterable<Item>

原始數據類型則使用Java的自動裝箱將boolean、byte、char、short、int、float、double、long轉換為Boolean、Byte、Character、Short、Integer、Double、Long。并且用自動拆箱轉換回來。

l? 背包:是一種不支持從中刪除元素的集合數據類型,它的目的就是幫助用力收集元素并迭代遍歷所有收集到的元素,迭代的書序不確定且與用例無關。

l? 先進先出隊列(FIFO):一種基于先進先出策略的集合類型,用集合保存元素的同時保存它們的相對順序,并且入列順序和出列順序相同。

l? 下壓棧(LIFO):一種基于后進先出策略的集合類型,元素的處理順序和它們被壓入的順序正好相反。

l? 順序棧實現:先實現定容棧FixedCapacityStackOfStrings類,只能存儲String類型,并且大小固定;然后用泛型實現FixedCapacityStack類;再添加resize(int capacity)方法并更改push和pop使之可以調整自動大小保證不會溢出且使用率大于四分之一,即ResizableStack類;最后實現Iterable接口,完成ResizingArrayStack類。

l? 對象游離:Java垃圾收集策略是回收所有無法被訪問的對象的內存。保存著一個不需要的對象的引用就稱為游離。要避免對象游離只要將引用設為null即可。

l? 鏈表棧實現:結點類Node,表頭插入結點,表頭刪除結點,表尾插入結點,鏈表的遍歷。然后實現Stack類

l? 鏈表隊列的實現,背包就是去掉pop()的Stack。

l? 自己嘗試實現順序隊列:固定大小的循環隊列FixedCapacityQueue類,可調整大小可迭代遍歷的循環隊列ResizingArrayQueue類。

l? 其他數據結構(除去背包、隊列、棧):父鏈接樹、二分查找樹、字符串、二叉堆、散列表(拉鏈法)、散列表(線性探測法)、圖的鄰接鏈表、單詞查找樹、三向單詞查找樹。

l? 以下為“答疑”和“練習”中的知識點:

l? 為什么Java不允許泛型數組:專家們仍在爭論這一點,初學者請先了解共變數組(covariantarray)和類型擦除(type erasure)。

l? Stack的內部類Node經過javac編譯后會生成一個Stack$Node.class文件。

l? Java數組支持foreach遍歷(for(inti : int[] array))。

l? 應當避免使用寬接口(實現了很多功能以提升適用性),因為這樣無法保證高效,并且可能出現意外情況,其實是累贅。

l? 可以用Stack類來將十進制轉化為二進制(習題1.3.5),結合Stack和Queue來反轉隊列(習題1.3.6),中序表達式轉化為后續表達式(習題1.3.10),有專門的鏈表練習最好自己多動手寫代碼,雙向隊列(雙向鏈表實現和動態數組實現),

1.4 算法分析

l? 科學方法:1、細致地觀察真實世界的特點,通常還要有精確地測量;2、根據觀察結果提出結社模型;3、根據模型預測未來的時間;4、繼續觀察并核實預測的準確性;5、如此反復知道確認預測和觀察一致。

l? 一個程序運行的總時間主要和兩點有關:執行每條語句的耗時(取決于計算機、Java編譯器和操作系統),執行每條語句的頻率(取決于程序本身和輸入)。二者相乘并將程序中所有指令的成本相加得到運行總時間。

l? 時間復雜度(程序運行時間的增長數量級)和空間復雜度。

l? 理解問題規模的上界和時間復雜度的下界。

l? 注意事項:大常數,非決定性的內循環,指令時間,系統因素,不分伯仲,對輸入的強烈依賴,多個問題參量。

l? Java對象要有24字節的對象頭,而數組如int數組就是24+4N字節(N為奇數時還有填充字節),double數組則是24+8N字節。String對象總共會使用40個字節(16字節表示對象,3個int實例變量個需要4字節,加上數組引用的8字節和4個填充字節)+(24+2N)字節字符數組(字符串間共享)。

l? 以下為“答疑”和“練習”中的知識點:

l? 雙調查找,僅用加減實現的二分查找(斐波那契查找),扔雞蛋,扔兩個雞蛋,兩個棧可以實現隊列(三種方法,要注意細節),一個隊列實現棧,兩個隊列實現棧,熱還是冷。

1.5 案例研究:union-find算法

動態連通性,union-find算法,三種實現。

1、 用集合來保存,標識為某一個觸點,即quick-find算法。

2、 用森林來保存,每個觸點的id[i]指向自己或連通分量中另一個觸點,指向自己的則是根觸點,為quick-union算法,但是每次兩樹合并是不確定的,不保證比quick-find算法快。

3、 用森林保存,改進quick-union,為加權quick-union算法,保存樹的節點數,每次把小樹連接到大樹上,保證對數級別。

4、 使用路徑壓縮的加權quick-union,是當前最優算法,但并非所有的操作都能在常數時間內完成。

第2章 排序

2.1 初級排序算法

l? 排序算法可以分為兩類:除了桉樹調用需要的棧和固定數目的實例變量之外無需額外內存的原地排序算法,以及需要額外內存空間來存儲另一份數組副本的其他排序算法。

l? 選擇排序:選擇數組最小放到數組前面,每次選擇最小放到數組第k小的位置。N2/2次比較和N次交換,O(n2),運行時間和輸入無關,數據移動是最少的。

l? 插入排序:數組左端有序,每次插入一個元素是比較,較小則前移。平均情況需要~N2/4次比較和~N2/4次交換,最壞情況需要~N2/2次比較和~N2/2次交換,最好情況需要N-1次比較和0次交換。插入排序對部分有序數組很有效,事實上,當倒置的數量很少時,插入排序很可能比本章中任何算法都要快。

l? 希爾排序:基于插入排序,間隔h進行間隔元素插入排序,h逐漸減小為1。高效的原因是權衡了子數組的規模和有序性??梢杂糜诖笮蛿到M。運行時間達不到平方級別。屬于O(n1.5)級別。有經驗的程序員有時會選擇希爾排序,因為對于中等大小的數組它的運行時間是可以接受的,它的代碼量很小,且不需要使用額外的內存空間,在下面的幾節中我們會看到更加高效的算法,但是除了對于很大的N,它們可能只會比希爾排序快2倍(可能還達不到),而且更復雜。

l? 通過提升速度來解決其他方式無法解決的問題是研究算法的設計和性能的主要原因之一。

l? 以下為“答疑”和“練習”中的知識點:

l? 所有主鍵相同插入排序比選擇排序更快。逆序數組選擇排序比插入排序更快。元素只有三種值的隨機數組插入排序仍然是平方級別的。出列排序,按照限定條件每次選出最小(大)值然后將有序的放入底部。

2.2 歸并排序

l? 歸并排序:將數組分成兩半分別排序,然后將結果歸并起來。是分治思想的體現。

l? 自頂向下的歸并:從大分到小,使用遞歸,但實際上最終還是從兩個元素開始歸并。需要0.5NlgN至NlgN次比較,最多需要訪問數組6NlgN次。

l? 自底向上的歸并:從兩個到多,使用迭代。需要0.5NlgN至NlgN次比較,最多需要訪問數組6NlgN次。

l? 歸并排序告訴我們,當能夠用其中一種方法解決一個問題時,你都應該試試另一種。

l? 沒有任何基于比較的算法能夠保證使用少于lg(N!)~NlgN次比較將長度為N的數組排序。(借用二叉比較樹可以證明)

l? 以下為“答疑”和“練習”中的知識點:

l? 所有元素相同時歸并排序運行時間是線性的。如果是多個值重復則歸并排序仍然是線性對數的。

歸并排序的三項改進:用插入排序加快小數組的排序速度,用array[mid]array[mid+1]檢測數組是否已經有序,通過在敵對中交換參數來避免數組復制。

鏈表排序(選擇排序,插入排序,冒泡排序,所有排序)。

歸并有序的隊列,然后自底向上實現有序隊列的歸并排序。自然的歸并排序(自適應歸并排序),利用數組中已有的有序部分。

打亂鏈表(直接的就是每次隨機選出一個,但運行時間是平方級別的?;蛘呃脭到M來實現打亂,O(n)時間復雜度和O(n)空間復雜度。題目要求線性對數級別時間和對數級別空間。最開始的思路我的思路是遞歸實現的,每次隨機排序左右兩個鏈表,但我只是簡單的將兩個鏈表的前后順序打亂,這樣的結果是鄰近的元素仍然是在附近的,并沒有實現,沒有想出來突破點。只要靠搜索引擎了。百度“打亂鏈表”居然沒有結果,只有百度“shuffling a linked list”才有結果而且都是英文的。用谷歌搜索“shufflinga linked list”發現了一個人在Quora上發的討論,然后看了他在StackOverflow上的回答,但是代碼是python的,不過差不多能看懂,然后才發現,合并兩個鏈表時,應該是每次隨機從一個鏈表中選出頭元素來合并成新鏈表,這樣才是真正的打亂。然后寫出代碼就行了,這里迭代的寫法可能麻煩點,因為要靠大小來確定左右鏈表的大小或者左右鏈表的尾結點。)。

間接排序,不改變數組的歸并排序,返回int[] perm,perm[i]為第i小的元素位置,其實就是對perm進行歸并,只不過比較時要用array和perm來取元素比較。

三向歸并排序,把數組分成三部分而不是兩部分進行歸并排序,其實就是多一些判斷,本質上還是差不多的,運行時間仍然是線性對數級別的。

2.3 快速排序

l? 可能是應用最廣泛的排序算法了,原因是實現簡單,適用于各種不同的輸入數據且在一般應用中比其他排序算法都要快得多,而且是原地排序,時間復雜度是O(nlgn)。

l? 快速排序是一種分支的排序算法,它將一個數組分成兩個子數組,將兩部分獨立地排序,切分(partition)的位置取決于數組的內容。

l? 快速排序平均需要~2NlnN次比較(以及1/6的交換),最多需要約N2/2次比較,但隨即打亂數組能夠預防這種情況。

l? 快速排序的改進:小數組插入排序更快,所以小數組用插入排序。三取樣切分,取三個元素比較用中間大小的元素作為切分元素。熵最優排序,重復值較多時用三向切分,即分為大于、等于、小于。對于只有若干不同主鍵的隨機數組,三向切分快速排序是線性的。對于大小為N的數組,三向切分快速排序需要~(2ln2)NH次比較,其中H為由主鍵值出現頻率定義的香農信息量。

l? 以下為“答疑”和“練習”中的知識點:

l? 將數組平分希望提高性能,用現有算法,是做不到的。

將已知只有兩種主鍵值得數組排序。

非遞歸的快速排序,借助棧,存取每個要進行切分的子數組首尾位置。

快速三向切分,用將重復元素放置于子數組兩端的方式實現一個信息量最優的排序算法。

2.4 優先隊列

l? 優先隊列是一種抽象數據類型,它表示了一組值和對這些值的操作,優先隊列最重要的操作就是刪除最大元素和插入元素。

l? 優先隊列的一些重要的一個應用場景包括模擬系統、任務調度、數值計算等。

l? 優先隊列初級實現:無序數組實現(惰性方法),有序數組實現(積極方法),鏈表實現(無序或者有序)。這些要不就是插入操作為O(n)要不就是刪除為O(n)。而使用堆的隊列,可以插入刪除都為O(logn)。

l? 堆(二叉堆):實際上就是一棵二叉樹,一般用數組實現。第0個元素不使用,k的子結點是2k和2k+1。而k的父結點是k/2向下取整。插入元素:將新元素加到數組末尾,增加堆的大小并讓這個新元素上浮到合適的位置。刪除最大元素:從數組頂端刪去最大元素并將數組的最后一個元素放到頂端,減小堆的大小并讓這個元素下沉到合適的位置。

l? 對于一個含有N個元素的基于堆的優先隊列,插入元素操作只需不超過lgN+1次比較,刪除最大元素操作需要不超過2lgN次比較。

l? 索引優先隊列:僅對索引進行優先隊列排序。

l? 堆排序:將所有元素插入一個查找最小元素的優先隊列,然后再重復調用刪除最小元素的操作來將他們按順序刪去。用下沉操作由N個元素構造堆只需少于2N次比較以及少于N次交換。

l? 先下沉后上浮(sink、swim):大多數在下沉排序期間重新插入堆的元素會被直接加入到堆底,Floyd在1964年觀察發現,我們正好可以通過免去檢查元素是否到達正確位置來節省時間,在下沉中總是直接提升較大的子結點直至到達堆底,然后再使元素上浮到正確的位置。這個想法幾乎可以將比較次數減少一半,但是這種方法需要額外的空間,因此在實際應用中只有當比較操作代價較高時才有用。(例如在進行字符串或者其他鍵值較長類型的元素排序時)

l? 堆排序在排序復雜性研究中有這著重要的地位,因為它是我們所知的唯一能夠同時最優地利用空間和時間的方法,在最壞的情況下也能保證~2NlgN次比較和恒定的額外空間??臻g緊張時如嵌入式系統或低成本的移動設備中很流行。但現代系統的許多應用中很少使用它,因為它無法利用緩存,很少和相鄰元素進行比較,因此緩存未命中的次數要遠遠高于大多數比較都在相鄰元素之間進行的算法,如快速排序、歸并排序,甚至是希爾排序。另一方面用堆實現的優先隊列在現代應用程序中越來越重要,因為它能在插入操作和刪除最大元素操作混合的動態場景中保證對數級別的運行時間。

l? 以下為“答疑”和“練習”中的知識點:

l? MaxPQ使用泛型的Item是因為這樣delMax()的用力就不需要將返回值轉換為某種具體的類型,比如String。一般來說,應該盡量避免在用例中進行類型轉換。

堆中不利用第0個元素是因為能夠稍稍簡化計算,另外將第0個元素的值用作哨兵在某些堆的應用中很有用。

可以用優先隊列實現棧、隊列、隨機隊列等數據結構。

2.5 應用

l? 將各種數據排序(實現Comparable接口的數據類型皆可以):交易事務,指針排序,不可變的鍵(如String、Integer、Double、File),廉價的交換(引用),多種排序方法,多鍵數組(定義比較器Comparator),使用比較器實現優先隊列,穩定性(能否保留重復元素的相對位置)。

l? 用Comparator接口來代替Comparable接口能夠更好地將數據類型定義和兩個該烈性的對象應該如何比較的定義區分開來。

l? 我們應該使用哪種排序算法:取決于應用場景和具體實現??焖倥判蚴亲羁斓耐ㄓ门判蛩惴?。(選擇排序,插入排排序,希爾排序,快速排序,三向快速排序,歸并排序,堆排序)插入排序和歸并排序是穩定的,其余不穩定。歸并排序不是原地排序,其余是原地排序,(三向)快速排序空間復雜度為lgN,歸并排序空間復雜度為N,其余為1。插入排序效率取決于輸入情況,快速排序的效率由概率保證。

l? 歸約:為某個問題而發明的算法正好可以用來解決另一種問題。中位數與順序統計(第k大,可以用快速排序的切分在線性時間內找出第k大的元素)。平均來說基于切分的選擇算法的運行時間是線性級別的。

l? 排序應用一覽:商業計算,信息搜索,運籌學,事件驅動模擬,數值計算,組合搜索。A*算法。

l? 以下為“答疑”和“練習”中的知識點:

l? ?

第3章 查找

3.1 符號表

l? 符號表最主要的目的就是將一個鍵和一個值聯系起來。

l? 每個鍵只對應一個值(表中不允許存在重復的鍵)。存入鍵值對和已有鍵沖突則新值替換舊值。鍵不能為空(null),值不能為空(null)。

l? 有序符號表,鍵為Comparable對象。

l? 有序數組中的二分查找,在N個鍵的有序數組中進行二分查找最多需要lgN+1次比較,無論是否成功。

l? 以下為“答疑”和“練習”中的知識點:

l? ?

3.2 二叉查找樹

l? 二叉查找樹BST是一棵二叉樹,其中每個結點都含有一個Comparable的鍵(以及相關聯的值)且每個結點的鍵都大于其左子樹中的任意結點的鍵而小于右子樹的任意結點的鍵。

l? 查找,插入,遞歸和非遞歸方式,最大鍵和最小鍵,向上取整和向下取整,選擇操作,排名,刪除最大鍵和刪除最小鍵,刪除操作,范圍查找。

l? 在由N個隨機鍵構造的二叉查找樹中插入操作和查找命中與未命中的平均所需的比較次數都為~2lnN(約1.39lgN)。(即二叉查找樹中查找隨機鍵的成本比二分查找高約39%)。

l? 以下為“答疑”和“練習”中的知識點:

l? 二叉樹檢查,有序性檢查,等值鍵檢查,非遞歸迭代器keys(),按層遍歷(使用Queue)。

3.3 平衡查找樹

l? 我們將一棵標準的二叉查找樹中的結點稱為2-結點(含有一個鍵和兩條鏈接),3-結點則是兩個鍵和三條鏈接。

l? 2-3查找樹:空樹或者由2-結點和3-結點組成的樹。完美平衡的2-3查找樹中的所有空連接到根節點的距離應該是相同的。

l? 查找,向2-結點中插入新鍵,向一棵只含有一個3-結點的樹中插入新鍵,向一個父節點為2-結點的3-結點中插入新鍵,向一個父節點為3-結點的3-結點中插入新鍵,分解根節點,局部變換不會影響全局有序性和平衡性。

l? 在一棵大小為N的2-3樹中,查找和插入操作訪問的結點必然不超過lgN個。

l? 紅黑二叉查找樹:用標準的二叉查找樹(完全由2-結點構成)和一些額外信息(替換3-結點)來表示2-3查找樹。鏈接分為兩種:紅鏈接將兩個2-結點連接起來構成一個3-結點,黑臉節則是2-3樹中的普通鏈接。

l? 紅黑樹的性質:所有基于紅黑樹的符號表實現都能保證操作的運行時間為對數級別(范圍查找除外,它所需要的額外空間和返回的鍵的數量成正比)。一棵大小為N的紅黑樹的高度不會超過2lgN。一棵大小為N的紅黑樹中,根結點到任意結點的平均長度為~1.001lgN。從根節點到所有空鏈接的路徑上的黑鏈接的數量相同。

l? 平衡樹是由根生長的,每次都有根結點分裂且保持平衡,每次分裂則樹高度加一。

l? 由路徑向下的算法可以用遞歸或迭代,而由路徑向上的算法一般用遞歸node=operate(node);的方法。

l? 刪除算法:保證當前結點不是2-結點,可以將另一子樹的結點旋轉借一個過來,等刪除后再平衡回來。

l? 以下為“答疑”和“練習”中的知識點:

l? 只允許紅色左鏈接的存在能夠減少可能出現的情況,因此實現所需要的代碼會少得多。

l? 如果按照升序將鍵插入一棵紅黑樹中,樹的高度是單調遞增。如果按照降序將鍵插入一棵紅黑樹中,樹的高度是逐漸遞增然后一下子減下來又逐漸遞增一直循環。

l? ?

3.4 散列表

l? 散列表的查找算法:第一步用散列函數將被查找的鍵轉化為數組的一個索引,第二步就是一個處理碰撞沖突的過程(拉鏈法和線性探測法)。

l? 散列函數和鍵的類型有關,對于每種類型的鍵我們都需要一個與之對應的散列函數。一個典型的例子就是社會保險。

l? 整數散列最常用的方法就是除留取余法。鍵為0到1之間的浮點數,可以將它乘以M并四舍五入得到一個0到M-1之間的索引值,但這會導致鍵的高位起的作用更大,修正方法則是將鍵表示為二進制數后再使用除留取余法。字符串也可以當做是整數來處理,組合鍵也可以如此。(Java的hashCode方法)

l? 均勻散列假設:我們使用的散列函數能能夠均勻并獨立地將所有的鍵散布于0到M-1之間。在一張含有M條鏈表和N個鍵的散列表中(在均勻散列假設成立的前提下),任意一條鏈表中的鍵的數量均在N/M的常數因子范圍內的概率無限趨向于1。

l? 拉鏈法:將大小為M的數組中的每個元素指向一條鏈表,每條鏈表中的結點都存儲了散列表為該元素的索引的鍵值對。

l? 開放地址散列表:依靠數組中的空位來解決散列表中碰撞沖突的策略。線性探測法:屬于開放地址散列表,當碰撞發生時(當一個鍵的散列值已經被另一個不同的鍵占用),我們直接檢查散列表中下一個位置(索引值加1)。(動態調整數組大小來保證使用率在1/8到1/2之間,到達1會無限循環)

l? 散列表的使用率:α=N/M(N為鍵總數,M為散列表大小)。拉鏈法中α是每條鏈表的長度一般大于1,線性探測表中α是表中已被占用的空間的比例,不可能大于1。

l? Knuth在1962年的推導:在一張大小為M并含有N=αM個鍵的基于線性探測的散列表中,基于均勻分布假設,α約為1/2時查找命中所需要的探測次數約為3/2,未命中所需要的約為5/2。

l? 散列表并非包治百病的靈丹妙藥:每種類型的鍵都需要一個優秀的散列函數;性能保證來自于散列函數的質量;散列函數的計算可能復雜而且昂貴;難以支持有序性相關的符號表操作。

l? 以下為“答疑”和“練習”中的知識點:

l? 完美散列函數:散列函數得到的每個索引都不相同即沒有碰撞。

3.5 應用

l? 我應該使用符號表的哪種實現:對于典型的應用程序,應該在散列表和二叉查找樹之間進行選擇。相對于二叉查找樹,散列表的有點在于代碼簡單,且查找時間最優(常熟級別,只要鍵的數據類型是標準的或者簡單到我們可以為它寫出滿足或近似滿足均勻性假設的高效散列函數即可)。二叉查找樹相對于散列表的有點在于抽象結構更簡單(不需要設計散列函數),紅黑樹可以保證最壞情況下的性能且它能夠支持的操作更多(如排名、選擇、排序、范圍查找)。根據經驗法則,大多數程序員的第一選擇都是散列表,在其他因素更重要時才會選擇紅黑樹。鍵是長字符串時有另外的選擇。(另外注意原始數據類型代替Key類型的節省,重復鍵的處理,Java標準庫的應用)

l? 集合用例:過濾器,白名單黑名單。字典類用例:電話黃頁,字典,基因組學,編譯器,文件系統,DNS。索引類用例:商業交易,網絡搜索,電影和演員。反向索引:互聯網電影數據庫,圖書索引,文件搜索。

l? 稀疏向量:google的PageRank算法。

?


未完待續



總結

以上是生活随笔為你收集整理的读书笔记_算法第四版(一)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。