算法基础之搜索和经典排序
?
目錄
簡介
搜索算法
二分法查找
排序算法
冒泡排序(Bubble Sort)
選擇排序(Selection Sort)
插入排序(Insert Sort)
快速排序(Quick Sort)
歸并排序(Merge Sort)
堆排序Heap Sort)
希爾排序(Shell?Sort)
計(jì)數(shù)排序(count_sort)
桶排序(Bucket Sort)
基數(shù)排序(Radix Sort)
總結(jié)
簡介
定義和特征
定義:算法(Algorithm)是指解題方案的準(zhǔn)確而完整的描述,是一系列解決問題的清晰指令,算法代表著用系統(tǒng)的方法描述解決問題的策略機(jī)制。也就是說,能夠?qū)σ欢ㄒ?guī)范的輸入,在有限時間內(nèi)獲得所要求的輸出。如果一個算法有缺陷,或不適合于某個問題,執(zhí)行這個算法將不會解決這個問題。不同的算法可能用不同的時間、空間或效率來完成同樣的任務(wù)。一個算法的優(yōu)劣可以用空間復(fù)雜度與時間復(fù)雜度來衡量。
一個算法應(yīng)該具有以下五個重要的特征:
- 有窮性:算法的有窮性是指算法必須能在執(zhí)行有限個步驟之后終止;
- 確切性:算法的每一步驟必須有確切的定義;
- 輸入項(xiàng):一個算法有0個或多個輸入,以刻畫運(yùn)算對象的初始情況,所謂0個輸入是指算法本身定出了初始條件;
- 輸出項(xiàng):一個算法有一個或多個輸出,以反映對輸入數(shù)據(jù)加工后的結(jié)果,沒有輸出的算法是毫無意義的;
- 可行性:算法中執(zhí)行的任何計(jì)算步驟都是可以被分解為基本的可執(zhí)行的操作步,即每個計(jì)算步都可以在有限時間內(nèi)完成(也稱之為有效性)。
?
設(shè)計(jì)要求
算法設(shè)計(jì)的要求:?
- 確定性: 指的是算法至少應(yīng)該有輸入,輸出和加工處理無歧義性,能正確反映問題的需求,能夠得到問題的正確答案。確定性大體分為四個層次:
- 可讀性: 程序便于閱讀,理解交流。?
- 健壯性: 當(dāng)輸入數(shù)據(jù)不合法時,算法也能作出相關(guān)處理,而不是產(chǎn)生異常,崩潰或者莫名其妙的結(jié)果。?
- 時間效率高和存儲量低。
算法效率的度量方法
事后統(tǒng)計(jì)方法:主要是通過設(shè)計(jì)好的測試程序和數(shù)據(jù),利用計(jì)算機(jī)計(jì)時器對不同算法編制的程序的運(yùn)行時間進(jìn)行比較,從而確定算法效率的高低,但這種方法有很大缺陷,一般不予采納。
事前分析估算方法:在計(jì)算機(jī)程序編制前,依據(jù)統(tǒng)計(jì)方法對算法進(jìn)行估算。
一個用高級語言編寫的程序在計(jì)算機(jī)上運(yùn)行時所消耗的時間取決于以下因素:
算法時間復(fù)雜度
定義:在進(jìn)行算法分析時,語句總的執(zhí)行次數(shù)T(n)是關(guān)于問題規(guī)模n的函數(shù),進(jìn)而分析T(n)隨n的變化情況并確定T(n)的數(shù)量級。算法的時間復(fù)雜度,也就是算法的時間量度,記作:T(n) = O(f(n))。它表示隨問題規(guī)模n的增大,算法執(zhí)行時間的增長率和f(n)的增長率相同,稱作算法的漸近時間復(fù)雜度,簡稱為時間復(fù)雜度。其中f(n)是問題規(guī)定n的某個函數(shù)。
根據(jù)定義,求解算法的時間復(fù)雜度的具體步驟是:
如何推導(dǎo)大o階呢?下面是基本的推導(dǎo)方法:
簡單的說,就是保留求出次數(shù)的最高次冪,并且把系數(shù)去掉。 ?如T(n)=n2+n+1 =O(n2)
一些例子
######復(fù)雜度O(1) print("this is wd")######復(fù)雜度O(n) for i in range(n):print(i)######復(fù)雜度O(n2) for i in range(n):for j in range(n):print(j)######復(fù)雜度O(n3) for i in range(n):for j in range(n):for k in range(n):print('wd')######復(fù)雜度O(log2n) while n > 1:print(n)n = n // 2常見的復(fù)雜度按效率排序:O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(2nlogn)<O(n2)
常見的時問復(fù)雜度如表所示。
空間復(fù)雜度?
空間復(fù)雜度(Space Complexity)是對一個算法在運(yùn)行過程中臨時占用存儲空間大小的量度。一個算法在計(jì)算機(jī)存儲器上所占用的存儲空間,包括存儲算法本身所占用的存儲空間,算法的輸入輸出數(shù)據(jù)所占用的存儲空間和算法在運(yùn)行過程中臨時占用的存儲空間這三個方面。算法的輸入輸出數(shù)據(jù)所占用的存儲空間是由要解決的問題決定的,是通過參數(shù)表由調(diào)用函數(shù)傳遞而來的,它不隨本算法的不同而改變。存儲算法本身所占用的存儲空間與算法書寫的長短成正比,要壓縮這方面的存儲空間,就必須編寫出較短的算法。算法在運(yùn)行過程中臨時占用的存儲空間隨算法的不同而異,有的算法只需要占用少量的臨時工作單元,而且不隨問題規(guī)模的大小而改變,這種算法是節(jié)省存儲的算法;有的算法需要占用的臨時工作單元數(shù)與解決問題的規(guī)模n有關(guān),它隨著n的增大而增大,當(dāng)n較大時,將占用較多的存儲單元。
如當(dāng)一個算法的空間復(fù)雜度為一個常量,即不隨被處理數(shù)據(jù)量n的大小而改變時,可表示為O(1);當(dāng)一個算法的空間復(fù)雜度與以2為底的n的對數(shù)成正比時,可表示為0(log2n);當(dāng)一個算法的空間復(fù)雜度與n成線性比例關(guān)系時,可表示為0(n).若形參為數(shù)組,則只需要為它分配一個存儲由實(shí)參傳送來的一個地址指針的空間,即一個機(jī)器字長空間;若形參為引用方式,則也只需要為其分配存儲一個地址的空間,用它來存儲對應(yīng)實(shí)參變量的地址,以便由系統(tǒng)自動引用實(shí)參變量。?
最壞情況與平均情況
我們查找一個有n 個隨機(jī)數(shù)字?jǐn)?shù)組中的某個數(shù)字,最好的情況是第一個數(shù)字就是,那么算法的時間復(fù)雜度為O(1),但也有可能這個數(shù)字就在最后一個位置上待著,那么算法的時間復(fù)雜度就是O(n),這是最壞的一種情況了。
最壞情況運(yùn)行時間是一種保證,那就是運(yùn)行時間將不會再壞了。 在應(yīng)用中,這是一種最重要的需求, 通常, 除非特別指定, 我們提到的運(yùn)行時間都是最壞情況的運(yùn)行時間。
而平均運(yùn)行時間也就是從概率的角度看, 這個數(shù)字在每一個位置的可能性是相同的,所以平均的查找時間為n/2次后發(fā)現(xiàn)這個目標(biāo)元素。平均運(yùn)行時間是所有情況中最有意義的,因?yàn)樗瞧谕倪\(yùn)行時間。也就是說,我們運(yùn)行一段程序代碼時,是希望看到平均運(yùn)行時間的。可現(xiàn)實(shí)中,平均運(yùn)行時間很難通過分析得到,一般都是通過運(yùn)行一定數(shù)量的實(shí)驗(yàn)數(shù)據(jù)后估算出來的。一般在沒有特殊說明的情況下,都是指最壞時間復(fù)雜度。
搜索算法
搜索是在一個數(shù)據(jù)集合中找到一個特定數(shù)據(jù)的算法,通常的答案是真的或假的。搜索的常見方法有二分查找、哈希查找等
二分法查找
二分查找又稱折半查找,優(yōu)點(diǎn)是比較次數(shù)少,查找速度快,平均性能好。缺點(diǎn)是要求待查表為有序表。因此,折半查找方法適用于不經(jīng)常變動而查找頻繁的有序列表。
二分查找的工作原理
- 首先,假設(shè)表中元素是按升序排列,將表中間位置記錄的關(guān)鍵字與查找關(guān)鍵字比較,如果兩者相等,則查找成功。
- 否則利用中間位置記錄將表分成前、后兩個子表,如果中間位置記錄的關(guān)鍵字大于查找關(guān)鍵字,則進(jìn)一步查找前一子表,否則進(jìn)一步查找后一子表。
- 重復(fù)以上過程,直到找到滿足條件的記錄,使查找成功,或直到子表不存在為止,此時查找不成功。
二分查找的實(shí)現(xiàn)
def binary(l, val):max = len(l)min = 0while min <= max:mid = (max + min) // 2if l[mid] > val:max = mid - 1elif l[mid] < val:min = mid + 1elif l[mid] == val:print(mid)breakelse:print('no')def binary_rec(l, val, min, max):mid = (max + min) // 2if min > max:print('no')return 'no'if l[mid] == val:print(mid)return midelif l[mid] < val:return binary_rec(l, val, mid+1, max)elif l[mid] > val:return binary_rec(l, val, min, mid-1)排序算法
冒泡排序(Bubble Sort)
- 時間復(fù)雜度:最優(yōu)時間復(fù)雜度:O(n),最壞時間復(fù)雜度:O(n2)。
- 優(yōu)點(diǎn):穩(wěn)定,簡單
- 缺點(diǎn):效率不很高,運(yùn)行時間較長
原理如下:
代碼實(shí)現(xiàn)
def bubble_sort(data):"""冒泡排序:param data: :return: """for i in range(len(data)-1): # 趟數(shù)for j in range(len(data)-i-1): # 遍歷數(shù)據(jù),依次交換if data[j]>data[j+1]: # 當(dāng)較大數(shù)在前面data[j],data[j+1]=data[j+1],data[j] #交換兩個數(shù)的位置優(yōu)化版本:當(dāng)某一趟走完以后發(fā)現(xiàn)并沒有進(jìn)行數(shù)據(jù)交換,那么此時的數(shù)列已經(jīng)排列好了,沒有必要在進(jìn)行下去。例如:極端情況下,數(shù)列本來已經(jīng)排序好的,我們只需要走一趟即可完成排序。
def bubble_sort(data):"""冒泡排序優(yōu)化版:param data: :return: """for i in range(len(data)-1): # 趟數(shù)exchange=False # 交換標(biāo)志for j in range(len(data)-i-1): # 遍歷數(shù)據(jù),依次交換if data[j]>data[j+1]: # 當(dāng)較大數(shù)在前面data[j],data[j+1]=data[j+1],data[j] # 交換兩個數(shù)的位置exchange = True # 改變標(biāo)志if not exchange: # 如果某一趟沒有進(jìn)行交換,代表排序完成breakreturn i # 返回次數(shù)的趟數(shù)選擇排序(Selection Sort)
- 時間復(fù)雜度:最優(yōu)時間復(fù)雜度:O(n2);最壞時間復(fù)雜度:O(n2)
- 穩(wěn)定性:不穩(wěn)定
- 優(yōu)點(diǎn):移動次數(shù)少
- 缺點(diǎn):比較次數(shù)多
工作原理
實(shí)現(xiàn)
def select_sort(data):"""選擇排序:param data: 待排序的數(shù)據(jù)列表:return: """for i in range(len(data)-1): #趟數(shù)min_index=i # 記錄i趟開始最小的數(shù)的索引,我們從最左邊開始for j in range(i+1,len(data)): # 每一次趟需要循環(huán)的次數(shù)if data[j] < data[min_index]: # 當(dāng)數(shù)列中的某一個數(shù)比開始的數(shù)要小時候,更新最小值索引位置min_index=jdata[i],data[min_index]=data[min_index],data[i] # 一趟走完,交換最小值的位置,第一趟最小插入排序(Insert Sort)
- 時間復(fù)雜度:最優(yōu)時間復(fù)雜度:O(n);最壞時間復(fù)雜度:O(n2)
- 穩(wěn)定性:穩(wěn)定
- 優(yōu)點(diǎn):穩(wěn)定,比較快
- 缺點(diǎn):比較次數(shù)不確定,數(shù)據(jù)量越大,該算法越渣
工作原理如下:
實(shí)現(xiàn)
def insert_sort(data):"""插入排序:param data: 待排序的數(shù)據(jù)列表:return: """for i in range(1, len(data)): # 無序區(qū)域數(shù)據(jù)tmp = data[i] # 第i次插入的基準(zhǔn)數(shù)for j in range(i, -1, -1):if tmp < data[j - 1]: # j為當(dāng)前位置,試探j(luò)-1位置data[j] = data[j - 1] # 移動當(dāng)前位置else: # 位置確定為jbreakdata[j] = tmp # 將當(dāng)前位置數(shù)還原快速排序(Quick Sort)
- 時間復(fù)雜度:
- 最優(yōu)時間復(fù)雜度:O(nlogn)遍歷每個數(shù)是O(n),訪問每個數(shù)是O(logn),最終是O(nlogn)可以轉(zhuǎn)換為求二叉樹深度的思想
- 最壞時間復(fù)雜度:O(n2)
- 穩(wěn)定性:不穩(wěn)定
- 優(yōu)點(diǎn):效率高,數(shù)據(jù)移動比較少,數(shù)據(jù)量越大,優(yōu)勢越明顯
- 缺點(diǎn):不穩(wěn)定
快速排序(Quicksort),又稱劃分交換排序(partition-exchange sort)。通過一趟排序?qū)⒁判虻臄?shù)據(jù)分割成獨(dú)立的兩部分,其中一部分的所有數(shù)據(jù)都比另外一部分的所有數(shù)據(jù)都要小,然后再按此方法對這兩部分?jǐn)?shù)據(jù)分別進(jìn)行快速排序。整個排序過程可以遞歸進(jìn)行,以此達(dá)到整個數(shù)據(jù)變成有序序列。
工作原理如下:
實(shí)現(xiàn)
def quick_sort(data,left,right):"""快速排序:param data: 待排序的數(shù)據(jù)列表:param left: 基準(zhǔn)數(shù)左邊元素的索引:param right: 基準(zhǔn)數(shù)右邊元素的索引:return: """if left < right:mid = partition(data,left,right) # 分區(qū)操作,mid代表基數(shù)所在的索引quick_sort(data,left,mid-1) # 對基準(zhǔn)數(shù)前面進(jìn)行排序quick_sort(data,mid+1,right) # 對基準(zhǔn)數(shù)后面進(jìn)行排序def partition(data,left,right):tmp=data[left] # 隨機(jī)選擇的基準(zhǔn)數(shù),從最左邊開始選while left < right:while left < right and data[right] >= tmp: # 右邊的數(shù)比基準(zhǔn)數(shù)大right-=1 # 保留該數(shù),然后索引指針往左移動data[left]=data[right] # 否則此時右邊數(shù)比基數(shù)小,則將該數(shù)放到基準(zhǔn)位置while left < right and data[left] <= tmp: # 右邊的數(shù)比基準(zhǔn)數(shù)小left+=1 # 此時保持該數(shù)位置不動,索引指針往前移動data[right]=data[left] # 否則此時左邊的數(shù)比基數(shù)大,則將該數(shù)放到右邊data[left] = tmp # 最后將基準(zhǔn)數(shù)量放回中間return left # 返回基準(zhǔn)數(shù)位置歸并排序(Merge Sort)
- 最優(yōu)時間復(fù)雜度:O(nlogn)最壞時間復(fù)雜度:O(nlogn)
- 穩(wěn)定性:穩(wěn)定
- 優(yōu)點(diǎn):穩(wěn)定,數(shù)據(jù)量越大越優(yōu)秀
- 缺點(diǎn):需要額外空間
歸并排序(MergeSort)是采用分治法的一個非常典型的應(yīng)用。歸并排序的思想就是先遞歸分解數(shù)組,再合并數(shù)組。
歸并排序是一種穩(wěn)定的排序方法。和選擇排序一樣,歸并排序的性能不受輸入數(shù)據(jù)的影響,但表現(xiàn)比選擇排序好的多,因?yàn)槭冀K都是O(nlogn)的時間復(fù)雜度。代價是需要額外的內(nèi)存空間。
工作原理如下:
- 申請空間,使其大小為兩個已經(jīng)排序序列之和,該空間用來存放合并后的序列;
- 設(shè)定兩個指針,最初位置分別為兩個已經(jīng)排序序列的起始位置;
- 比較兩個指針?biāo)赶虻脑?#xff0c;選擇相對小的元素放入到合并空間,并移動指針到下一位置;
- 重復(fù)步驟3直到某一指針達(dá)到序列尾;
- 將另一序列剩下的所有元素直接復(fù)制到合并序列尾
實(shí)現(xiàn)
堆排序Heap Sort)
- 時間復(fù)雜度:最優(yōu)時間復(fù)雜度:O(nlogn),最壞時間復(fù)雜度:O(nlogn)
- 穩(wěn)定性:不穩(wěn)定
本質(zhì)是一個完全二叉樹,如果根節(jié)點(diǎn)的值是所有節(jié)點(diǎn)的最小值稱為小根堆,如果根節(jié)點(diǎn)的值是所有節(jié)點(diǎn)的最大值,稱為大根堆。
原理:
- 重復(fù)3操作,直到堆為空,最后完成排序;??
希爾排序(Shell?Sort)
- 最優(yōu)時間復(fù)雜度:根據(jù)步長序列的不同而不同,最優(yōu)是1.3,根據(jù)數(shù)學(xué)運(yùn)算算出的gap
- 最壞時間復(fù)雜度:O(n2)
- 穩(wěn)定性:不穩(wěn)定
- 優(yōu)點(diǎn):平均時間短,數(shù)據(jù)移動少
- 缺點(diǎn):不穩(wěn)定
希爾排序(Shell Sort)是插入排序的一種,也稱縮小增量排序,是直接插入排序算法的一種更高效的改進(jìn)版本。希爾排序是非穩(wěn)定排序算法,是DL.Shell于1959年提出的。
工作原理如下:
計(jì)數(shù)排序(count_sort)
- 時間復(fù)雜度:最優(yōu)時間復(fù)雜度:O(n),最壞時間復(fù)雜度:O(n)
- 穩(wěn)定性:穩(wěn)定
- 優(yōu)點(diǎn):速度快
- 缺點(diǎn):有局限性,范圍不能太大,只能是整數(shù)
計(jì)數(shù)排序不是基于比較的排序算法,其核心在于將輸入的數(shù)據(jù)值轉(zhuǎn)化為鍵存儲在額外開辟的數(shù)組空間中。 作為一種線性時間復(fù)雜度的排序,計(jì)數(shù)排序要求輸入的數(shù)據(jù)必須是有確定范圍的整數(shù)。
計(jì)數(shù)排序是一個穩(wěn)定的排序算法。當(dāng)輸入的元素是 n 個 0到 k 之間的整數(shù)時,時間復(fù)雜度是O(n+k),空間復(fù)雜度也是O(n+k),其排序速度快于任何比較排序算法。當(dāng)k不是很大并且序列比較集中時,計(jì)數(shù)排序是一個很有效的排序算法。
算法描述
- 找出待排序的數(shù)組中最大和最小的元素;
- 統(tǒng)計(jì)數(shù)組中每個值為i的元素出現(xiàn)的次數(shù),存入數(shù)組C的第i項(xiàng);
- 對所有的計(jì)數(shù)累加(從C中的第一個元素開始,每一項(xiàng)和前一項(xiàng)相加);
- 反向填充目標(biāo)數(shù)組:將每個元素i放在新數(shù)組的第C(i)項(xiàng),每放一個元素就將C(i)減去1。
桶排序(Bucket Sort)
- 時間復(fù)雜度:最優(yōu)時間復(fù)雜度:O(n),最壞時間復(fù)雜度:O(n)
- 穩(wěn)定性:穩(wěn)定
- 缺點(diǎn):如果數(shù)據(jù)分布集中,如只分布在一個桶,效果不好。需要額外空間
桶排序是計(jì)數(shù)排序的升級版。它利用了函數(shù)的映射關(guān)系,高效與否的關(guān)鍵就在于這個映射函數(shù)的確定。桶排序 (Bucket sort)的工作的原理:假設(shè)輸入數(shù)據(jù)服從均勻分布,將數(shù)據(jù)分到有限數(shù)量的桶里,每個桶再分別排序(有可能再使用別的排序算法或是以遞歸方式繼續(xù)使用桶排序進(jìn)行排)。
桶排序最好情況下使用線性時間O(n),桶排序的時間復(fù)雜度,取決與對各個桶之間數(shù)據(jù)進(jìn)行排序的時間復(fù)雜度,因?yàn)槠渌糠值臅r間復(fù)雜度都為O(n)。很顯然,桶劃分的越小,各個桶之間的數(shù)據(jù)越少,排序所用的時間也會越少。但相應(yīng)的空間消耗就會增大。?
算法描述
- 設(shè)置一個定量的數(shù)組當(dāng)作空桶;
- 遍歷輸入數(shù)據(jù),并且把數(shù)據(jù)一個一個放到對應(yīng)的桶里去;
- 對每個不是空的桶進(jìn)行排序;
- 從不是空的桶里把排好序的數(shù)據(jù)拼接起來。?
基數(shù)排序(Radix Sort)
- 時間復(fù)雜度:最優(yōu)時間復(fù)雜度:O(n),最壞時間復(fù)雜度:O(n)
- 穩(wěn)定性:穩(wěn)定
- 缺點(diǎn):只能處理正整數(shù)。需要額外空間
基數(shù)排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次類推,直到最高位。有時候有些屬性是有優(yōu)先級順序的,先按低優(yōu)先級排序,再按高優(yōu)先級排序。最后的次序就是高優(yōu)先級高的在前,高優(yōu)先級相同的低優(yōu)先級高的在前。
基數(shù)排序基于分別排序,分別收集,所以是穩(wěn)定的。但基數(shù)排序的性能比桶排序要略差,每一次關(guān)鍵字的桶分配都需要O(n)的時間復(fù)雜度,而且分配之后得到新的關(guān)鍵字序列又需要O(n)的時間復(fù)雜度。假如待排數(shù)據(jù)可以分為d個關(guān)鍵字,則基數(shù)排序的時間復(fù)雜度將是O(d*2n) ,當(dāng)然d要遠(yuǎn)遠(yuǎn)小于n,因此基本上還是線性級別的。
基數(shù)排序的空間復(fù)雜度為O(n+k),其中k為桶的數(shù)量。一般來說n>>k,因此額外空間需要大概n個左右。
算法描述
- 取得數(shù)組中的最大數(shù),并取得位數(shù);
- arr為原始數(shù)組,從最低位開始取每個位組成radix數(shù)組;
- 對radix進(jìn)行計(jì)數(shù)排序(利用計(jì)數(shù)排序適用于小范圍數(shù)的特點(diǎn));
總結(jié)
?
https://www.cnblogs.com/onepixel/articles/7674659.html
https://www.cnblogs.com/wdliu/p/9481122.html
https://blog.csdn.net/danieljackz/article/details/90288964
?
?
?
總結(jié)
以上是生活随笔為你收集整理的算法基础之搜索和经典排序的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Flask 跨域问题
- 下一篇: 高效办公必备神器-Keychron键盘,