數據結構與算法之基本概念
前言
數據結構與算法是程序員內功體現的重要標準之一,且數據結構也應用在各個方面,業界更有程序 = 數據結構 + 算法這個等式存在。各個中間件開發者,架構師他們都在努力的優化中間件、項目結構以及算法提高運行效率和降低內存佔用,在這裏數據結構起到相當重要的作用。此外數據結構也蘊含一些面向對象的思想,故學好掌握數據結構對邏輯思維處理抽象能力有很大提升。
爲什麼學習數據結構與算法?如果你還是學生,那麼這門課程是必修的,考研基本也是必考科目。工作在內卷嚴重的大廠中找工作數據結構與算法也是面試、筆試必備的非常重要的考察點。如果工作了數據結構和算法也是內功提升一個非常重要的體現,對於程序員來說,想要得到滿意的結果,數據結構與算法是必備功力!
數據結構
概念
數據結構是計算機存儲、組織數據的方式。數據結構是指相互之間存在一種或多種特定關係的數據元素的集合。通常情況下,精心選擇的數據結構可以帶來更高的運行或者存儲效率。
簡言之,數據結構是一系列的存儲結構按照一定執行規則
、配合一定執行算法
所形成的高效的存儲結構。在我們所熟知的關係數據庫、非關係數據庫、搜索引擎存儲、消息隊列等都是比較牛的大型數據結構良好的運用。當然這些應用中間件不單單要考慮單純的結構問題。還考慮實際 os、網絡等其他因素。
而對於數據結構與算法這個專欄。我們程序員更改掌握的首先是在內存
中運行的抽象的數據結構
。是一個相對比較單一的數據結構類型,比如線性結構
、樹
、圖
等等.
相關術語
在數據結構與算法中,數據、數據對象、數據元素、數據項很多人搞不清其中的關係。通過畫一張圖來捋一捋,然後下面舉個例子給大家分享一下。
用戶信息表users
users 的 pojo 對象
1class users
2{
3 //略
4 int id;
5 String name;
6 String sex;
7}
8//list和woman是數據
9List<users>list;//數據對象list
10List<users>woman;//數據對象woman
11list.add(new users(001,"bigsai","man"));//添加數據元素 一個users由(001,bigsai,man)三個數據項組成
12list.add(new users(002,"smallsai","man"));//數據元素
13list.add(new users(003,"菜虛鯤","woman"));//數據元素
14woman.add(list.get(2));//003,"菜虛鯤","woman"三個數據項構成的一個數據元素
15
16
數據:對客觀事物的符號表示,指所有能輸入到計算機中並被計算機程序處理的符號的集合總稱。上述表中的三條用戶信息的記錄就是數據 (也可能多表多集合這裏只有一個)。這些數據一般都是用戶輸入
或者是自定義構造完成。當然,還有一些圖像、聲音也是數據。
數據元素:數據元素是數據的基本單位。一個數據元素由若干數據項
構成!可認爲是一個 pojo 對象、或者是數據庫的一條記錄。比如菜虛鯤
那條記錄就是一個數據元素。
數據項:而構成用戶字段 / 屬性的有id
、name
、sex
等,這些就是數據項. 數據項是構成數據元素的最小不可分割字段
。可以看作一個 pojo 對象或者一張表 (people) 的一個屬性/字段
的值。
數據對象:是相同性質數據元素的集合。是數據的一個子集。比如上面的users
表、list
集合、woman
集合都是數據對象。單獨一張表,一個集合都可以是一個數據對象。
總的捋一捋,數據範圍最廣,所有數據即數據,而數據對象僅僅是有相同性質的一個集合,這個集合是數據的子集,但並不是數據的基本單位,而數據元素纔是數據的基本單位。舉個例子表 cat 和表 dog 都是數據,然後表 cat 是個數據對象 (因爲都描述 cat 這種對象),但是數據的基本單位並不是貓和狗,而是他們的具體的每一條,比如小貓咪 1 號,大貓咪二號,哈士奇 1 號,藏獒 2 號這些每一條纔是數據的基本單位。
對於數據類型和抽象數據類型兩者容易混淆注意區分開:
數據類型
原子類型
:其值不可再分的類型。比如 int,char,double,float 等。
結構類型
:其值可以再分爲若干成分的數據類型。比如結構體構造的各種結構等。
抽象數據類型 (ADT):抽象數據類型(ADT)是一個實現包括儲存數據元素的存儲結構以及實現基本操作的算法。使得只研究和使用它的結構而不用考慮它的實現細節成爲可能。比如我們使用 List、Map、Set 等等只需要瞭解它的 api 和性質功能即可。而具體的實現可能是不同的方案,比如 List 的實現有數組和鏈表不同選擇。
三要素
邏輯結構:數據元素之間的邏輯關係
。邏輯結構分爲線性結構
和非線性結構
。線性結構就是順序表、鏈表之類。而非線性就是集合、樹、圖這些結構。
存儲結構:數據結構在計算機中的表示 (又稱映像,也稱物理結構),存儲結構主要分爲順序存儲
、鏈式存儲
、索引存儲
和散列(哈希)存儲
,這幾種存儲通過下面這張圖簡單瞭解一下 (僅僅爲理解不考慮更多):
數據的運算:施加在數據上的運算包括運算的定義
和實現
, 運算的定義基於邏輯結構,運算的實現基於存儲結構。
在這裏容易混淆的是邏輯結構與存儲結構的概念。對於邏輯結構,不難看得出邏輯二字,邏輯關係也就是兩者存在數據上的關係而不考慮物理地址的關係,比如線性結構和非線性結構,它描述的是一組數據中聯繫的方式和形式,他針對的是數據。看中的是數據結構的功能,比如線性表就是前後有序的,我需要一個有序的集合就可以使用線性表。
而存儲結構就是跟物理地址掛鉤的。因爲同樣邏輯結構採用不同存儲結構實現適用場景和性能可能不同。比如同樣是線性表, 可能有多種存儲結構的實現方式。比如順序表
和鏈表
(Arraylist,Linkedlist)它們的存儲結構就不同,一個是順序存儲 (數組) 實現,一個是鏈式存儲 (鏈表) 實現。它關注的是計算機運行物理地址的關係。但通常同一類存儲結構實現的一些數據結構有一些類似的共同點和缺點(線性易查難插、鏈式易插難查等等)。
算法分析
上面講了數據結構相關概念,下面對算法分析的一些概念進行描述。
算法的五個重要特徵:有窮性、確定性、可行性、輸入、輸出。這些從字面意思即可理解,其中有窮性強調算法要有結束的時候不能無限循環;而確定性是每條指令有它意義,相同的輸入得到相同的輸出;可行性是指算法每個步驟經過若干次執行可以實現;輸入是 0 個或多個輸入 (可 0); 輸出是 1 個或多個輸出 (一定要有輸出)。
而一個好的算法,通常更要着重考慮的是效率和空間資源佔用 (時間複雜度和空間複雜度),通常複雜度更多描述的是一個量級
程度而很少用具體數字描述。
空間複雜度
概念:是對一個算法在運行過程中臨時佔用存儲空間大小的量度,記做 S(n)=O(f(n))
空間複雜度其實在算法的衡量佔比是比較低的 (我們經常使用犧牲空間換時間的數據結構和算法),但是不能忽視空間複雜度中重要性。無論在刷題還是實際項目生產內存都是一個極大額指標。對於 Java 而言更是如此。本身內存就大,如果採用的存儲邏輯不太好會佔用更多的系統資源,對服務造成壓力。
而算法很多情況都是犧牲空間換取時間 (效率)。就比如我們熟知的字符串匹配String.contains()
方法,我們都知道他是暴力破解,時間複雜度爲 O(n^2), 不需要藉助額外內存。而KMP
算法在效率和速度上都原生暴力方法,但是 KMP 要藉助其他數組 (next[]
) 進行標記儲存運算。就用到了空間開銷。再比如歸併排序也會藉助新數組在遞歸分冶的適合進行逐級計算,提高效率,但增加點影響不大的內存開銷。
當然,算法的空間花銷最大不能超過 jvm 設置的最大值,一般爲 2G.(2147483645) 如果開二維數組多種多維數據不要開的太大,可能會導致heap OutOfMemoryError
。
時間複雜度
概念:計算機科學中,算法的時間複雜度是一個函數
,它定性描述了該算法的運行時間。這是一個關於代表算法輸入值的字符串的長度的函數。時間複雜度常用大 O 符號表述,不包括這個函數的低階項和首項係數。使用這種方式時,時間複雜度可被稱爲是漸近的,它考察當輸入值大小趨近無窮時的情況。
時間複雜度的排序:O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) <O(n!) < O(n^n)
常見時間複雜度:對於時間複雜度,很多人的概念是比較模糊的。下面舉例子說明一些時間複雜度。
O(1): 常數函數
a=15
O(logn): 對數函數
-
for(int i=1;i<n;i*=2)
分析:假設執行t
次使得i=n
; 有 2^t=n; t=log2~n, 爲 log 級別時間複雜度爲 O(logn)。 -
還有典型的二分查找,拓展歐幾里得,快速冪等算法均爲 O(logn)。屬於高效率算法。
O(n): 線性函數
-
for (int i=0;i<n;i++)
-
比較常見,能夠良好解決大部分問題。
O(nlogn):
-
for (int i=1;i<n;i++)``for (int j=1;j<i;j*=2)
-
常見的排序算法很多正常情況都是 nlogn,比如快排、歸併排序。這種算法效率大部分也還不錯。
O(n^2)
-
for(int i=0;i<n;i++)``for(int j=0;j<i;j++)
-
其實 O(n^2) 的效率就不敢恭維了。對於大的數據 O(n^2) 甚至更高次方的執行效果會很差。
當然如果同樣是 n=10000. 那麼不同時間複雜度額算法執行次數、時間也不同。
降低算法複雜度有些會靠數據結構的特性和優勢,比如二叉排序樹的查找,線段樹的動態排序等等,這些數據結構解決某些問題有些非常良好的性能。還有的是靠算法策略解決,比如同樣是排序,冒泡排序這種笨而簡單的方法就是 O(n2), 但快排、歸併等聰明方法就能 O(nlogn)。要想變得更快,那就得掌握更高級的數據結構和更精巧的算法。
時間複雜度計算時間複雜度計算一般步驟
:1、找到執行次數最多的語句; 2、計算語句執行的數量級 ; 3、用 O 表示結果。並且有兩個規則:
加法規則:同一程序下如果多個並列關係的執行語句那麼取最大的那個, eg:
1T(n)=O(m)+O(n)=max(O(m),O(n));
2T(n)=O(n)+O(nlogn)=max(O(n),O(nlogn))=O(nlogn);
3
4
乘法規則:循環結構,時間複雜度按乘法進行計算, eg:
1T(n)=O(m)*O(n)=O(mn)
2T(n)=O(m)*O(m)=O(m^2)(兩層for循環)
3
4
當然很多算法的時間複雜度還跟輸入的數據有關,分爲還會有最優時間複雜度 (可能執行次數最少時),最壞時間複雜度 (執行次數最少時),平均時間複雜度,這在排序算法中已經具體分析,但我們通常使用平均時間複雜度來衡量一個算法的好壞。
數據結構與算法學習
捋過數據結構與算法基本概念的介紹,在學習數據結構與算法方面,個人把經典的數據結構與算法學習過程步驟寫在下面,希望能給大家一個參考:
數據結構
-
單鏈表 (帶頭結點、不帶頭結點) 設計與實現(增刪改查),雙鏈表設計與實現
-
棧設計與實現 (數組和鏈表),隊列設計與實現 (數組和鏈表)
-
二叉樹概念學習,二叉樹前序、中序、後序遍歷遞歸、非遞歸實現 ,層序遍歷
-
二叉排序樹設計與實現 (插入刪除)
-
堆 (優先隊列、堆排序)
-
AVL(平衡) 樹設計與實現 (四種自旋方式理解實現)
-
伸展樹、紅黑樹原理概念理解
-
B、B + 原理概念理解
-
哈夫曼樹原理概念理解 (貪心策略)
-
哈希 (散列表) 原理概念理解(幾種解決哈希衝突方式)
-
並查集 / 不相交集合 (優化和路徑壓縮)
-
圖論拓撲排序
-
圖論 dfs 深度優先遍歷、bfs 廣度優先遍歷
-
最短路徑 Dijkstra 算法、Floyd 算法、spfa 算法
-
最小生成樹 prim 算法、kruskal 算法
-
其他數據結構線段樹、後綴數組等等
經典算法
-
遞歸算法 (求階乘、斐波那契、漢諾塔問題)
-
二分查找
-
分治算法 (快排、歸併排序、求最近點對等問題)
-
貪心算法 (使用較多,區間選點問題,區間覆蓋問題)
-
常見動態規劃 (LCS(最長公共子序列) LIS(最長上升子序列) 揹包問題等等)
-
回溯算法 (經典八皇后問題、全排列問題)
-
位運算常見問題 (參考劍指 offer 和 LeetCode 問題)
-
快速冪算法 (快速求冪乘、矩陣快速冪)
-
kmp 等字符串匹配算法
-
一切其他數論算法 (歐幾里得、拓展歐幾里得、中國剩餘定理等等)
相信看完這篇文章,你應該對數據結構與算法有個不錯的認知。數據結構與算法有着非常密切的關聯,數據結構是爲了實現某種算法,算法是核心目的。學習數據結構與算法之前,可以先參考書本或者博客先了解其功能,再研究其運行原理,再動手實戰 (編寫數據結構或者相關題目) 這樣層次漸進,想要深入的學習數據結構與算法光理解是不行的,需要有大量代碼實戰纔可。並且這條路是沒有止境的,活到老,學到老,刷到老。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/dosB74m85guyOQi_e2oYHw