【程序人生】數據結構雜記(二)

說在前面

個人讀書筆記

向量和列表

最爲基本的線性結構統稱爲序列(sequence),根據其中數據項的邏輯次序與其物理存儲地址的對應關係不同,又可進一步地將序列區分爲向量(vector)和列表(list)。
在向量中,所有數據項的物理存放位置與其邏輯次序完全吻合,此時的邏輯次序也稱作秩(rank);而在列表中,邏輯上相鄰的數據項在物理上未必相鄰,而是採用間接定址的方式通過封裝後的位置(position)相互引用。

向量

若集合S由n個元素組成,且各元素之間具有一個線性次序,則可將它們存放於起始於地址A、物理位置連續的一段存儲空間,並統稱作數組(array)

向量(vector)就是線性數組的一種抽象與泛化,我們不再限定同一向量中的各元素都屬於同一基本類型,它們本身可以是來自於更具一般性的某一類的對象。另外,各元素也不見得同時具有某一數值屬性,故而並不保證它們之間能夠相互比較大小。

若向量S[0,n)S[0, n)中的所有元素不僅按線性次序存放,而且其數值大小也按此次序單調分佈,則稱作有序向量(sorted vector)。有序向量的定義中隱含了另一更強的先決條件:各元素之間必須能夠比較大小。

有序向量唯一化

低效版
在這裏插入圖片描述
其正確性基於如下事實:
有序向量中的重複元素必然前後緊鄰。於是,可以自前向後地逐一檢查各對相鄰元素:
若二者雷同則調用remove()接口刪除靠後者,否則轉向下一對相鄰元素。如此,掃描結束後向量中將不再含有重複元素。

這裏的運行時間,主要消耗於while循環,共需迭代_size - 1 = n - 1步。此外,在最壞情況下,每次循環都需執行一次remove()
操作。因此,時間複雜度爲O(n2)O(n^2)。這一效率竟與向量未排序時相同,說明該方法未能充分利用此時向量的有序性。

高效版
在這裏插入圖片描述

在這裏插入圖片描述

有序向量查找

二分查找
假設在區間S[lo,hi)S[lo, hi)中查找目標元素ee
在這裏插入圖片描述
以任一元素S[mi]=xS[mi] = x爲界,都可將區間分爲三部分,且根據此時的有序性必有:
S[lo,mi)<=S[mi]<=S(mi,hi)S[lo, mi) <= S[mi] <=S(mi, hi)
於是,只需將目標元素eexx做一比較,即可視比較結果分三種情況做進一步處理:

  1. e<xe <x,則目標元素如果存在,必屬於左側子區間S[lo,mi)S[lo, mi),故可深入其中繼續查找;
  2. x<ex < e,則目標元素如果存在,必屬於右側子區間S(mi,hi)S(mi, hi),故也可深入其中繼續查找;
  3. e=xe = x,則意味着已經在此處命中,故查找隨即終止。

在這裏插入圖片描述
時間複雜度:O(logn)O(logn)

查找算法的整體效率更主要地取決於其中所執行的元素大小比較操作的次數,即所謂查找長度(search length)

排序與下界

一般地,任一問題在最壞情況下的最低計算成本,即爲該問題的複雜度下界(lower bound)。

歸併排序
與起泡排序通過反覆調用單趟掃描交換類似,歸併排序也可以理解爲是通過反覆調用所謂二路歸併(2-way merge)算法而實現的。所謂二路歸併,就是將兩個有序序列合併成爲一個有序序列。這裏的序列既可以是向量,也可以是列表,這裏首先考慮有序向量。歸併排序所需的時間,也主要決定於各趟二路歸併所需時間的總和。

二路歸併屬於迭代式算法。每步迭代中,只需比較兩個待歸併向量的首元素,將小者取出並追加到輸出向量的末尾,該元素在原向量中的後繼則成爲新的首元素。如此往復,直到某一向量爲空。最後,將另一非空的向量整體接至輸出向量的末尾。

二路歸併示例:
在這裏插入圖片描述
歸併排序示例:
在這裏插入圖片描述

列表

數據結構支持的操作,通常無非靜態和動態兩類:前者僅從中獲取信息,後者則會修改數據結構的局部甚至整體。以向量結構,其size()和get()等靜態操作均可在常數時間內完成,而insert()和remove()等動態操作卻都可能需要線性時間。究其原因,在於“各元素物理地址連續”的約定——即所謂的“靜態存儲”策略
得益於這種策略,可在O(1)O(1)時間內由秩確定向量元素的物理地址;但反過來,在添加(刪除)元素之前(之後),又不得不移動O(n)O(n)個後繼元素。可見,儘管如此可使靜態操作的效率達到極致,但就動態操作而言,局部的修改可能引起大範圍甚至整個數據結構的調整。

列表(list)結構儘管也要求各元素在邏輯上具有線性次序,但對其物理地址卻未作任何限制——此即所謂“動態存儲”策略。具體地,在其生命期內,此類數據結構將隨着內部數據的需要,相應地分配或回收局部的數據空間。如此,元素之間的邏輯關係得以延續,卻不必與其物理次序相關。作爲補償,此類結構將通過指針或引用等機制,來確定各元素的實際物理地址。
例如,鏈表(linked list)就是一種典型的動態存儲結構。其中的數據,分散爲一系列稱作節點(node)的單位,節點之間通過指針相互索引和訪問。爲了引入新節點或刪除原有節點,只需在局部,調整少量相關節點之間的指針。這就意味着,採用動態存儲策略,至少可以大大降低動態操作的成本。

列表是鏈表結構的一般化推廣,其中的元素稱作節點(node),分別由特定的位置或鏈接指代。

雙向鏈表插入節點:
在這裏插入圖片描述
雙向鏈表刪除節點:
在這裏插入圖片描述

有序列表唯一化

與有序向量同理,有序列表中的雷同節點也必然(在邏輯上)彼此緊鄰。利用這一特性,可實現重複節點刪除算法如下圖所示。位置指針p和q分別指向每一對相鄰的節點,若二者雷同則刪除q,否則轉向下一對相鄰節點。如此反覆迭代,直至檢查過所有節點。
在這裏插入圖片描述

有序列表查找

儘管有序列表中的節點已在邏輯上按次序單調排列,但在動態存儲策略中,節點的物理地址與邏輯次序毫無關係,故無法像有序向量那樣自如地應用減治策略,從而不得不繼續沿用無序列表的順序查找策略。
在這裏插入圖片描述

排序

插入排序

理論最優時間複雜度:O(n2)O(n^2)
插入排序(insertionsort)算法適用於包括向量與列表在內的任何序列結構。算法的思路可簡要描述爲:
始終將整個序列視作並切分爲兩部分:有序的前綴,無序的後綴;通過迭代,反覆地將後綴的首元素轉移至前綴中。由此亦可看出插入排序算法的不變性:
在任何時刻,相對於當前節點e=S[r]e = S[r],前綴S[0,r)S[0, r)總是業已有序。
在這裏插入圖片描述

選擇排序

理論最優時間複雜度:O(nlogn)O(nlogn)
選擇排序(selectionsort)也適用於向量與列表之類的序列結構。
與插入排序類似,該算法也將序列劃分爲無序前綴和有序後綴兩部分;此外,還要求前綴不大於後後綴。如此,每次只需從前綴中選出最大者,並作爲最小元素轉移至後綴中,即可使有序部分的範圍不斷擴張。
同樣地,上述描述也給出了選擇排序算法過程所具有的不變性:
在任何時刻,後綴S[r, n)已經有序,且不小於前綴S[0, r)
在這裏插入圖片描述

結語

如果您有修改意見或問題,歡迎留言或者通過郵箱和我聯繫。
手打很辛苦,如果我的文章對您有幫助,轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章