算法一:概述

算法一:概述

概述

1. 算法

算法algorithm,來自于數(shù)學領域。算法的種類也很多,有好有壞。我們用時間復雜度和空間復雜度來衡量一個算法的好壞。
算法在實際開發(fā)中主要用在下面:

  • 運算:比如超大整數(shù)進行運算;
  • 查找:搜索引擎搜索內容,以及數(shù)據(jù)庫查找
  • 排序:各種排序算法
  • 最優(yōu)決策

2. 數(shù)據(jù)結構

數(shù)據(jù)結構是數(shù)據(jù)的組織、管理和存儲格式,其使用目的是為了高效的訪問和修改數(shù)據(jù)。
數(shù)據(jù)結構的組成有下面幾種:

  • 線性結構:最簡單的結構,包括數(shù)據(jù)、鏈表、棧、隊列、哈希表等。
  • 樹:二叉樹、二叉堆。
  • 圖:元素間多對多的關系,更復雜的一種數(shù)據(jù)結構。

時間復雜度

1. 概念

時間復雜度是用來評估一段算法執(zhí)行時間的長短。受運行環(huán)境和輸入規(guī)模的影響,代碼的絕對執(zhí)行時間是無法預估的,但我們卻可以預估代碼的基本操作執(zhí)行次數(shù)。假設輸入規(guī)模是n,那么會有一個函數(shù)T(n)來表示當前算法的執(zhí)行次數(shù)。

2. O(n)官方定義

若存在函數(shù)f(n),使得當n趨近于無窮大時,T(n)/f(n)為不等于0的常數(shù),則稱f(n)是T(n)的同量級函數(shù)。記作T(n)=O(f(n))。O為算法的漸進時間復雜度。也被稱為大O表示法。

3. 舉例

  • T(n)=3n,那么去掉最高階系數(shù)3之后的漸進時間復雜度就是O(n)。
  • T(n)=5logn,去掉最高階系數(shù)5,T(n)=O(logn)
  • T(n)=2,去掉最高階2,T(n)=O(1)
  • T(n)=0.5n2+0.5n,還是以最高階為準,去掉系數(shù)之后就是T(n)=O(n2)。

空間復雜度

1. 概念

算法的執(zhí)行除了上面說的時間成本,還有空間成本。因為算法在執(zhí)行指令的過程中,需要存儲一些中間數(shù)據(jù)。存儲這部分數(shù)據(jù)的成本,稱之為空間成本。因為內存是優(yōu)先的,所以在時間復雜度相同的情況下,空間復雜度越小越好。

和時間復雜度一樣,我們也用O(n)來標示算法在執(zhí)行過程中占用的內存空間。

2. 常見的空間復雜度

  • 常量空間:算法的存儲空間和輸入規(guī)模無關,那么空間復雜度記作O(1)。比如向字典中插入一個數(shù)據(jù)。

  • 線性空間:算法分配的空間是一個線性集合,并且集合大小和輸入規(guī)模成n成正比。那么空間復雜度記作O(n)。

  • 二維空間:比如一個二位數(shù)組,集合的長度和寬度都和輸入規(guī)模n成正比。記作O(n2)

  • 遞歸空間:有一類比較特殊的程序,自己內部會調動自己,我們稱之為遞歸調用。

    1. 計算機在執(zhí)行程序的時候,會專門分配一塊內存,用來存儲方法調用棧。

    2. 方法調用棧分為進棧和出棧兩個行為:當進入一個新方法時會執(zhí)行進棧操作,方法執(zhí)行完再執(zhí)行出棧操作。

    所以,執(zhí)行遞歸操作所需要的內存空間和遞歸的深度成正比。如果遞歸的深度是n,那么空間復雜度就是O(n)。

3. 時間和空間復雜度的取舍

在絕大多數(shù)時候,時間復雜度更重要,我們寧可多分配一些內存空間,也要提升程序的執(zhí)行速度。

數(shù)組

1. 基本介紹

(1) 對應的英文是array。有限個相同類型變量組成的有序集合。數(shù)組中的每一個變量成為元素。

(2) 數(shù)組在內存中是連續(xù)的。也就是說順序不能亂,而且也不能跳過某個存儲單元。

(3) 數(shù)組的下標從0開始,一直到長度-1的位置

2. 基本操作

(1) 讀取和更新元素

根據(jù)下標index,從數(shù)組中拿到該元素,直接取值和賦值就可以了。

(2) 插入元素

根據(jù)插入位置的不同,分為中間插入和尾部插入。

  • 尾部插入:直接把元素放在數(shù)組最后空閑位置即可。
  • 中間插入:稍微復雜一些,需要從數(shù)組尾部元素開始,一直到插入點index,依次向后挪一位。從而把待插入點的位置空出來,然后把元素插入進去。

(3) 超范圍插入(數(shù)組擴容)

上面說的插入元素,都是在插入后長度不超過數(shù)組原長度的情況下進行。但還有一種情況,就是插入元素后,導致數(shù)組長度超出之前的長度。這就需要對數(shù)組進行擴容。

擴容的方法很簡單,再創(chuàng)建一個新數(shù)組,長度是舊數(shù)組的2倍。然后把舊數(shù)組中的元素全部復制過來,就實現(xiàn)了數(shù)組擴容。

(4) 刪除元素

這個就簡單多了,把要刪除index后的元素,依次向前挪動一位就可以了。

3. 時間復雜度

(1) 插入元素:涉及到擴容和挪位兩部分,這兩部分的時間復雜度都是O(n),所以插入的時間復雜度也是O(n)。

(2) 刪除元素:只有挪位的操作,時間復雜度是O(n)。

(3) 刪除時不記順序:有這么一種騷操作,在刪除一個元素的時候,后面的元素不進行挪位。而是直接把最后一個元素復制到被刪除的位置上,然后再把最后一個元素刪除掉。這樣的時間復雜度是O(1),但代價是數(shù)組順序發(fā)生改變。一般不常用。

4. 數(shù)組的優(yōu)勢和劣勢

(1) 優(yōu)勢:高效的隨機訪問能力,只要給出下標,就能以常量的時間找到對應的元素。

(2) 劣勢:由于數(shù)組在內存中是連續(xù)存儲,所以插入和刪除元素,必然會帶動大量元素的移動,影響效率。

總結:數(shù)組適合讀操作多,寫操作少的情景。

鏈表

1. 基本介紹

(1) 物理上一種非順序,非連續(xù)的數(shù)據(jù)結構,由若干個節(jié)點組成。

(2) 第一個節(jié)點成為頭結點,最后一個節(jié)點成為尾節(jié)點。

(3) 單向鏈表:每個節(jié)點有兩部分構成:存放數(shù)據(jù)的變量data,指向下一個節(jié)點的指針next。單向鏈表只能通過一個節(jié)點找到它的下一個節(jié)點,卻不能找到上一個節(jié)點。

(4) 雙向鏈表:每個節(jié)點由三部分組成,除了data和next之外,還有一個指向上一個節(jié)點的prev指針。雙向鏈表中,我們可以通過prev來找到上一個節(jié)點。

(5) 數(shù)組在內存中是占用了一片連續(xù)完整的內存空間,而鏈表則是見縫插針。鏈表分布在內存中的不同位置,依靠next和prev來關聯(lián)起來。

2. 基本操作

(1) 查找節(jié)點

鏈表不能快速定位,只能從頭節(jié)點開始,依次next來查找給定的index。時間復雜度是O(n)

(2) 更新節(jié)點

主要還是查找的耗時,更新的過程和數(shù)組一樣。

(3) 插入節(jié)點

因為鏈表是不連續(xù)的,所以不需要像數(shù)組那樣考慮擴容。只要內存空間足夠,可以一直添加。根據(jù)插入位置的不同,分為下面三種情況:

  • 尾部插入:把最后一個節(jié)點的next指向新節(jié)點即可。
  • 頭部插入:把新節(jié)點的next指向原頭節(jié)點,再把新節(jié)點變?yōu)殒湵淼念^結點。
  • 中間插入:同樣很簡單,先把插入點的前置節(jié)點的next指向新節(jié)點,然后再把新節(jié)點的next指向原插入的節(jié)點。

(4) 刪除節(jié)點

同樣根據(jù)刪除節(jié)點位置的不同分為下面幾種情況:

  • 頭部刪除
  • 中間刪除
  • 尾部刪除

3. 時間復雜度

(1) 查找過程的時間復雜度是O(n)。

(2) 插入和刪除,如果不考慮查找的過程,那么單純操作的時間復雜度是O(1)。

4. 數(shù)組和鏈表對比

(1) 數(shù)組的優(yōu)勢是快速定位元素,適合讀操作多,寫操作少的情況。

(2) 鏈表的優(yōu)勢在于能靈活的插入和刪除元素,如果需要在尾部頻繁的插入、刪除元素,鏈表更合適。

棧和隊列

1. 物理結構和邏輯結構

(1) 物理結構是實實在在的結構。比如上面說的數(shù)組和鏈表都是內存中實實在在的存儲結構,所以屬于物理結構。

(2) 邏輯結構是抽象的概念,它依賴于物理結構而存在。比如后面說到的棧和隊列,他們的實現(xiàn)方式皆可以是數(shù)組也可以是鏈表。

2. 棧的Stack基本介紹

(1) 比如一個羽毛球筒,一端有底,一端有口。我們向里面放入羽毛球。那么就會出現(xiàn)先放入的最后拿出,后放入的先拿出。這就是典型的棧stack。

(2) 棧是一種線性結構,棧中的元素只能先入后出FILO(First In Last Out)。最先進入的元素是棧底,最后進入的是棧頂。

(3) 棧既可以用數(shù)組來實現(xiàn),也可以用鏈表來實現(xiàn)。

3. 棧的基本操作

就兩種:入棧和出棧。

(1) 入棧Push:把一個元素放入棧中。只能從棧頂一側放入,新入棧的元素會成為新的棧頂。

(2) 出棧Pop:把元素從棧中彈出。只有棧頂?shù)脑夭旁试S出棧。如果要把棧中間的元素出棧,那么必須先讓棧頂?shù)脑匾来纬鰲2趴梢浴?/p>

4. 隊列Queue的基本介紹

(1) 類似于汽車排隊過隧道。一端進,一端出,方向不能改變。

(2) 隊列遵循的是先入先出FIFO(First In First Out)原則。隊列的出口端叫隊頭,隊列的入口端叫隊尾。

(3) 隊列的實現(xiàn)既可以用數(shù)組也可以用鏈表。

注意:在使用數(shù)組來實現(xiàn)的時候,要在數(shù)組最后多留一個空位,這個空位是隊尾。當新元素入隊的時候,直接放到這個隊尾就可以了。

5. 隊列的基本操作

(1) 入隊
如果是用數(shù)組實現(xiàn),那么就把新元素放到隊尾空白位上。

(2) 出隊
把隊頭的元素移除隊列。只允許隊頭一側移除元素。出隊元素的后一個元素會成為新的隊頭。

(3) 循環(huán)隊列
1) 在用數(shù)組實現(xiàn)隊列的時候,會有這樣的問題:隨著出隊元素的增多,隊頭左側的空間就失去作用,隊列的容量也會越來越小。如果出隊和入隊頻繁的發(fā)生,一遍是數(shù)組開頭空出大量空閑位置,另一方面數(shù)組還在不斷擴容。
2) 這時候循環(huán)隊列就出現(xiàn)了。在標記好隊頭和隊尾元素的情況下,新插入的元素,優(yōu)先去占已經(jīng)移除隊列的坑。打破原有的存儲順序。讓數(shù)組的空間更加合理的利用起來。

6. 棧和隊列的應用

(1) 棧的應用
通常用于對“歷史”的回溯。比如從瀏覽網(wǎng)頁返回上一級,再返回上上級。

(2) 隊列的應用
通常用于對“歷史”的回放。比如多線程等等隊列,就要根據(jù)誰先加進來,誰先執(zhí)行。

(3) 雙端隊列
結合棧和隊列的特性:既可以先入先出,也可以后入先出。

散列表

1. 基本介紹

散列表也叫做哈希表hash table。這種數(shù)據(jù)結構提供了鍵key值value的映射關系。只要給出yigekey,就可以高效查找到它所匹配的value,時間復雜度接近于O(1)。

2. 哈希函數(shù)

(1) 在上面介紹的數(shù)據(jù)結構中,數(shù)組的查找效率最高。但是數(shù)組只能根據(jù)下標index進行查找,而散列表的key是字符串。所以我們需要一個中轉站,來將可以轉換為數(shù)組的index,這個中轉站就是哈希函數(shù)。

(2) 哈希函數(shù)在不同語言中的實現(xiàn)方式不一樣。

3. 散列表的寫操作

(1) 步驟

先通過哈希函數(shù),把key轉換為index。然后看下數(shù)組在該下標的元素是否為空。如果為空就直接存入。如果不為空,那就是典型的哈希沖突了。

(2) 哈希沖突

隨著數(shù)據(jù)的增多,哈希沖突不可避免。所以我們必須要有解決方案。常用的有兩種:開放尋址法和鏈表法。
1) 開放尋址法:就是要插入的index被占用了,那么就去尋找其它沒被占用的index。比如依次向后查找。
2) 鏈表法:這是一個重點了,Java的HashMap使用的就是這種。
HashMap數(shù)組的元素不僅僅是一個Entry對象,還是一個鏈表的頭結點。每個Entry對象通過next指向她的下一個Entry節(jié)點。當新來的Entry映射到與之沖突的數(shù)組位置時,只要插入到對應的鏈表中即可。

4. 散列表的讀操作

讀操作和寫操作差不多。也是通過哈希函數(shù)從key得到index。然后去數(shù)組中取值。如果對應index元素的key和要查找的key一直,那么直接取值即可。如果不一直,那么就查找該index開頭的鏈表。在該鏈表中依次查找key相同的就可以了。

5. 散列表的擴容

(1) 散列是基于數(shù)組的,既然數(shù)組要擴容,那么散列也會遇到擴容的問題。

(2) 什么時候需要擴容
經(jīng)過多次元素插入,散列表達到一定的飽和度,key映射位置發(fā)生沖突的概率會逐漸提高。這樣一來,大量元素擁擠在相同的數(shù)組下標位置,行成很長的鏈表。對后續(xù)插入操作和查詢操作都有很大的影響。這個時候,就需要擴容了。

(3) JDK中HashMap影響擴容的兩個因素:

  • Capacity:當前HashMap的長度

  • LoadFactor:HashMap的負載因子,默認值是0.75f。

    當HashMap滿足下面的條件時,就行需要進行擴容:

            HashMap.size >= Capacity * LoadFactor```
            
    

(4) 擴容的過程

新建一個新的Entry數(shù)組,長度為原來的2倍。

重新Hash:遍歷原Entry數(shù)組,把所有的Entry重新hash到新數(shù)組中。經(jīng)過擴容,原本擁擠的散列表又會變得稀疏。以前的那些常常的鏈表也會小時很多。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 一些概念 數(shù)據(jù)結構就是研究數(shù)據(jù)的邏輯結構和物理結構以及它們之間相互關系,并對這種結構定義相應的運算,而且確保經(jīng)過這...
    Winterfell_Z閱讀 6,612評論 0 13
  • hashmap實現(xiàn)的數(shù)據(jù)結構,數(shù)組、桶等。 如圖所示 JDK 1.7,是以數(shù)組+鏈表組成的,鏈表為相同hash的鍵...
    不需要任何閱讀 954評論 0 1
  • ? 數(shù)據(jù)結構 數(shù)據(jù)結構是計算機存儲和組織數(shù)據(jù)的的方式 1. 數(shù)組 在Java中,數(shù)組是用來存放同一種數(shù)據(jù)類型的集合...
    欲火逢生閱讀 572評論 0 3
  • 轉載請注明出處:http://www.itdecent.cn/p/9f23c9604a2e 數(shù)據(jù)結構學了有一年的時...
    Alent閱讀 2,410評論 5 51
  • 假使你在此刻出現(xiàn), 我要擁抱你的身體—— 我愛你。 溫暖的秋天; 外頭陽光,以及 音樂播放器。 你會出現(xiàn)嗎?親愛的...
    繪子啊閱讀 330評論 0 3

友情鏈接更多精彩內容