数据结构杂谈(一)
在你點進來這里的一瞬間,歡迎你找到了寶藏
這是一些關于數據結構和算法里最詳細的闡述和學習心得,我十分樂意和你分享這些知識。
如果你已經看完這篇雜談,可以前往下一篇→數據結構雜談(二)_塵魚好美的小屋-CSDN博客
1 緒論
1.1 什么是數據結構
數據結構是一門研究非數值計算的程序設計問題中計算機操作對象以及它們之間關系和操作的學科。
1.2 基本概念和術語
數據:數據是對客觀事物的符號表示,在計算機科學中是指所有能輸入到計算機中并被計算機程序處理的符號的總稱。
數據元素:是數據的基本單位(相當于數據庫的元組),數據元素在計算機程序中通常作為一個整體進行考慮和處理,也簡稱為元素,或稱為記錄、結點或頂點;一個數據元素由若干個數據項組成。
在數據庫原理中,層次模型采用的就是記錄,也就是關系數據庫中所說的元組。從中我們可以理解了,某一個類的實例就是數據元素。
數據項:構成數據元素的不可分割的最小單位
相當于數據庫中的屬性;在數據庫中我們也曾叫作數據項。
數據對象:是性質相同的數據元素的集合,是數據的一個子集,在不產生混淆的情況下,我們一般把數據對象簡稱為數據。
數據結構:數據元素不是孤立存在的,它們之間存在著某種關系,數據元素相互之間的關系稱為結構。
在看完以上的概念后,我想我有必要來說幾句。
我們可以舉生活中的例子來說明以上的概念。假設我們把人看成數據(數據對象),而把小紅小明看成數據元素,把小紅小明等人的性別、學號、年齡看成數據項,如果我們要把小紅小明等人按照一定的規則放在一起,這個規則就叫數據結構。
對于數據結構來說包含以下三個方面的內容
1.2.1 數據結構的兩個層次
數據結構分為邏輯結構和物理結構,茲分別列舉如下:
| 描述數據元素之間的邏輯關系 | 數據元素及其關系在計算機存儲器中的結構(存儲方式) |
| 和數據的存儲無關,獨立于計算機 | 是數據結構在計算機中的存儲形式 |
| 是從具體問題抽象出來的數學模型 |
1.2.2 邏輯結構的種類
對于邏輯結構來說,有兩種劃分方式:
劃分方法一:
| 線性結構 | 有且僅有一個開始和一個終端結點,并且所有結點都最多只有一個直接前驅和一個直接后繼。例如:線性表,棧,隊列,圖 |
| 非線性結構 | 一個結點可能有多個直接前驅和直接后繼。例如:樹,圖 |
劃分方法二:
| 集合結構 | 結構中的數據元素直接除了同屬于一個集合的關系外,無任何其他關系 |
| 線性結構 | 結構中的數據元素之間存在著一對一的線性關系 |
| 樹形結構 | 結構中的數據元素之間存在著一對多的層次關系 |
| 圖狀結構或網狀結構 | 結構中的數據元素之間存在著多對多的任意關系 |
1.2.3 四種基本的存儲結構
我們可以把存儲結構劃分為四種,茲列舉如下:
| 順序存儲結構 | 用一組連續的存儲單元一次存儲數據元素,數據元素之間的邏輯關系由元素的存儲位置來表示 |
| 鏈式存儲結構 | 用一組任意的存儲單元存儲數據元素,數據元素之間的邏輯關系用指針來表示 |
| 索引存儲結構 | 在存儲結點信息的同時,還建立附加的索引表 |
| 散列存儲結構 | 根據結點的關鍵字直接計算出該結點的存儲地址 |
C語言中用數組來實現順序存儲結構
C語言中用指針來實現鏈式存儲結構
從上面的簡潔的總結來看,我們如何更好地理解這些數據結構呢?實際上,邏輯結構指的是數據元素之間的關系,如果拿數據庫中類比的話,就是你要用層次數據模型還是用關系數據模型來表示數據的區別。而物理結構指的是數據在電腦上面存放的形式,有的是整整齊齊連續排列在內存中,有些是不連續地排列在內存中。
而之所以出現這兩種結構,是因為實際業務的需要,你想想看,如果使用連續的內存(順序存儲結構)來存儲數據,萬一你頻繁地刪除數據,而且考慮最壞情況,剛好刪掉第二個數據元素,那么假設總共由n個數據元素的話,那么后面就有n-2個數據元素需要前移,十分麻煩。
但是如果用不連續存儲(鏈式存儲結構)就不一樣了,鏈式存儲結構采用的是一種尋址的方式,即數據元素能夠尋找上一個數據元素的地址。這樣的話如果頻繁做更改,拿刪除來說,只需要把一個數據元素刪除,然后前后的數據元素地址改動一下即可,當然,這些操作都是有小細節的,這里只是大概提一下罷了。
邏輯結構是面向問題的,而物理結構是面向計算機的,其基本的目標就是將數據及其邏輯關系存儲到計算機的內存中。
1.2.4 數據類型和抽象數據類型
數據類型:是一組性質相同的值的集合以及定義在這個值集上的一組操作的總稱
抽象數據類型(Abstract Data Type,ADT):是指一個數學模型以及定義在該模型上的一組操作。
抽象數據類型的說明
- 由用戶定義,從問題抽象出數據模型。(邏輯結構)
- 還包括定義在數據模型上的一組抽象運算。(相關操作)
- 不考慮計算機內的具體存儲結構和運算的具體實現算法
抽象數據類型的定義格式
ADT 抽象數據類型名{數據對象:<數據對象的定義>數據關系:<數據關系的定義>基本操作:<基本操作的定義> }ADT 抽象數據類型名基本操作的定義格式
基本操作名<參數表>初始條件:<初始條件描述>操作結果:<操作結果描述>說明:
數據類型的出現不是偶然而是必然,如果不規定數據類型的話,那么就會造成資源的浪費。拿C語言來舉例,如果你不使用int,long這些基本數據類型的話,那么無端給出一個7,誰知道你的7占幾個字節?好比住房子,誒,當然,如果你想提前舉手:住房子當然是越大越好啦,那國家可就不同意了,你住那么大,別人住哪里?所以,根據公民的所需來規定每個公民住所的大小,對應到C上,每個數據都應該有自己在內存中所占的對應大小。故數據類型出現了。
那么抽象數據類型又是怎么出現的呢?我們試想,如果要對兩個整數做加法,我們關心的是哪兩個整數,它們做的操作是不是加法,它們做出來的結果如何。而不關心它們在計算機的時候用到計算機的底層邏輯,比如動到了哪里的內存,CPU做了什么工作,這通通都是我們不關心的,由此抽象數據類型應運而生了。
抽象是指抽取出實物具有的普遍性的性質。它是抽出問題的特征而忽略非本質的細節,是對具體事物的一個概括。抽象是一種思考問題的方式,它隱藏了繁雜的細節,只保留實現目標所需要的信息;如果你學過java,你就知道里面的抽象類深諳此道理。
1.3 算法的基本概念
1.3.1 算法和算法分析
1.3.1.1 引入
我們首先寫一個求和算法出來,如下:
int sum = 0; for(int i = 1;i<=100;i++) {sum = sum + i; } cout << "100 = "<<sum<<endl;上面我想大多數人從上大學第一門課程C語言開始學的就是這個求和算法。可以我們可以看到,這個算法是采用for循環來設計的;在深度學習中,for循環是效率最低的順序掃描,所以在不得已的情況下,我們不會采用for循環。那我們有更好地求和方法嗎?
在高中的時候,我們曾經學過倒序相加法,值得一提的是,倒序相加法現在沒幾個人記得,為啥?因為它們根本不理解其中的原理,而是靠大量的題目堆出來的記憶,所以在這里,我講一下倒序相加法的由來。
大名鼎鼎數學家高斯在很小的時候就因為老師刁難的一道題很聞名,老師故意刁難1加到100要讓還在上小學的高斯做出來,而高斯花的時間也不多,三下五就做出來了,他是這么做的:既然要算1加到100,那么我再搞一個1加到100,但是是倒序排放,那么上面的1和下面的100相加剛好為101,2和99相加為101,以此類推總共有100個101,也就是10100,那么除以二就是1加到100的結果,即5050。
如果把100看成n,我們可以寫出下列的程序:
int i = 0; int sum = 0; int n = 100; sum = (1+n)*n/2; cout<<sum<<endl;由此我們可以發現,好的算法可以起到事半功倍的效果。由此我們引出算法的定義,算法是什么?
1.3.1.2 算法
算法:是對特定問題求解步驟的一種描述
弄懂了算法的概念,那么算法和程序有什么區別呢?
算法是解決問題的一種方法或一個過程,考慮如何將輸入轉換成輸出,一個問題可以有多種算法;而程序是用某種設計語言對算法的具體實現。
對于算法來說有幾大特性,如下所示:
| 有窮性 | 一個算法必須總是在執行有窮步之后結束,且每一步都在有窮時間內完成。 |
| 確定性 | 算法中的每一條指令必須有確切的含義,沒有二義性,在任何條件下,只有唯一地一條執行路徑,即對于相同的輸入只能得到相同的輸出 |
| 可行性 | 一個算法是能行的,即算法種描述的操作都是可以通過已經實現的基本運算執行有限次來實現的 |
| 輸入 | 一個算法可以有零個或多個輸入 |
| 輸出 | 一個算法可以有一個或多個輸出 |
在給出上面算法的特性后,我們來避免記憶,給出一個比較簡單的故事,假如你的算法是無窮步,那還有意義嗎?本來人們就是用來解決問題,不能解決問題要這個算法有什么用。每個算法的每個步驟都是確定的,也就是說不會出現二義性。也就是說,你不能說輸出1然后會出來兩個數。并且你的算法做出來要可行,如果你的算法開銷太大,以現有的機器根本跑不出來,那這個算法也是沒有什么意義的。當然,一個算法可以沒有輸入,但是不能沒有輸出,就比如我們最簡單的每個編程教材最開始都有的一個案例:hello world
1.3.1.3 衡量算法
對于算法設計我們應該有如下要求:
| 正確性 | 算法應當滿足具體問題的需求 |
| 可讀性 | 算法要方便人對算法的理解 |
| 健壯性(魯棒性) | 算法能應對非法情況 |
| 高效性 | 要求花費盡量少的時間和盡量低的存儲需求 |
一個好的算法首先要具備正確性,然后是健壯性,可讀性,在幾個方面都滿足的情況下,主要考慮算法的效率,通過算法的效率高低來評判不同算法的優劣程度。算法效率從以下兩個方面來考慮:
| 時間效率 | 指的是算法所耗費的時間 |
| 空間效率 | 指的是算法執行過程中所耗費的存儲空間 |
時間效率和空間效率有時候是矛盾的,有時候為了提高時間效率,不得已要犧牲空間效率;所以為了提升總體效率,我們要折中選擇。
算法時間效率可以用依據該算法編制的程序在計算機上執行所消耗的時間來度量。
對于一個算法的度量,我們有下面兩種方法:
**事后統計:**將算法實現,測算其時間和空間開銷。
事前分析:對算法所消耗資源的一種估算方法
事后統計一般就像馬后炮,所以我們一般采用事前分析
1.3.2 函數的漸近增長
輸入規模n在沒有限制下,只要超過一個數值N,這個函數就總是大于另一個函數,我們則稱函數是漸進增長的。即:給定兩個函數f(n)和g(n),如果存在一個整數N,使得對于所有的n>N,f(n)總是比g(n)大,那么,我們說f(n)的增長漸進快于g(n)。
1.4 算法的時間復雜度
1.4.1 漸進時間復雜度
漸進時間復雜度:若有某個輔助函數f(n),使得當n趨于無窮大時,T(n)/f(n)的極限值為不等于零的常數,則稱f(n)是T(n)的同數量級函數。記作T(n) = O(f(n)),稱O(f(n))為算法的漸進時間復雜度(O是數量級的符號),簡稱時間復雜度。
定義里用大寫O來體現算法時間復雜度的記法,我們稱之為大O記法。一般情況下,不必計算所有操作的執行次數,而只考慮算法中基本操作執行的次數,它是問題規模n的某個函數,用T(n)表示。而為了便于比較不同算法的時間效率,我們僅比較它們的數量級。
1.4.2 推導大O階的方法
指路:大O階推導方法
1.4.3 常見的時間復雜度
1.4.3.1 算法時間效率的比較
當n取得很大時,指數時間算法和多項式時間算法在所需時間上非常懸殊。
實際上下面的表不用記,只需要利用高數的函數圖形去理解就行。
1.4.3.2 三種復雜度
**最壞時間復雜度:**考慮輸入數據“最壞”的情況。
平均時間復雜度:考慮所有輸入數據都等概率出現的情況。
**最好時間復雜度:**考慮輸入數據“最好”的情況。
一般總是考慮在最壞情況下的時間復雜度,以保證算法的運行時間不會比它更長。
對于復雜的算法,可以將它分成幾個容易估算的部分,然后利用大O加法法則和乘法法則,計算算法的時間復雜度。
**加法規則:**T(n) = T1(n)+T2(n) = O(f(n))+O(g(n)) = O(max(f(n),g(n)))
**乘法規則:**T(n) = T1(n)T2(n) = O(f(n))O(g(n)) = O(f(n)g(n))
總結
- 上一篇: Excel 脚本编写
- 下一篇: java突击面试章程