排序算法--(冒泡排序,插入排序,选择排序,归并排序,快速排序,桶排序,计数排序,基数排序)
一.時(shí)間復(fù)雜度分析
- **時(shí)間復(fù)雜度**:對(duì)排序數(shù)據(jù)的總的操作次數(shù)。反應(yīng)當(dāng)n變化時(shí),操作次數(shù)呈現(xiàn)什么規(guī)律
- **空間復(fù)雜度**:算法在計(jì)算機(jī)內(nèi)執(zhí)行時(shí)所需要的存儲(chǔ)空間的容量,它也是數(shù)據(jù)規(guī)模n的函數(shù)。
1.例題:
有一個(gè)字符串?dāng)?shù)組,將數(shù)組中的每一個(gè)字符串按照字母序排序,之后再將整個(gè)字符串?dāng)?shù)組按照字典序排序,時(shí)間復(fù)雜度多少?
假設(shè)最長(zhǎng)字符串長(zhǎng)度為s,有n個(gè)字符串,則每個(gè)字符串按照字母序排序時(shí)間復(fù)雜度為:O(n*slogs)
整個(gè)字符串?dāng)?shù)組按照字典序排序時(shí)間復(fù)雜度為:O(s*nlogn),所以總的時(shí)間復(fù)雜度為:O(n*slogs+s*nlogn)
各種排序算法復(fù)雜度:
二.各種排序算法
1.冒泡排序:
**復(fù)雜度分析:**
- 時(shí)間復(fù)雜度:若給定的數(shù)組剛好是排好序的數(shù)組,采用改進(jìn)后的冒泡排序算法,只需循環(huán)一次就行了,此時(shí)是最優(yōu)時(shí)間復(fù)雜度:O(n),若給定的是倒序,此時(shí)是最差時(shí)間復(fù)雜度:O(n^2) ,因此綜合平均時(shí)間復(fù)雜度為:O(n^2)
- 空間復(fù)雜度:因?yàn)槊看沃恍栝_(kāi)辟一個(gè)temp的空間,因此空間復(fù)雜度是:O(1),是一個(gè)原地排序算法,等于的話不做交換,故是一個(gè)穩(wěn)定的排序算法。
步驟:
1. 比較相鄰的元素。如果第一個(gè)比第二個(gè)大,就交換它們兩個(gè);
2. 對(duì)每一對(duì)相鄰元素作同樣的工作,從開(kāi)始第一對(duì)到結(jié)尾的最后一對(duì),這樣在最后的元素應(yīng)該會(huì)是最大的數(shù);
3. 針對(duì)所有的元素重復(fù)以上的步驟,除了最后一個(gè);
4. 重復(fù)步驟1~3,直到排序完成。
代碼:
""" 冒泡排序:從小到大 """ def BubbleSort(array):lengths = len(array)for i in range(lengths-1):for j in range(lengths-1-i):if array[j] > array[j+1]:array[j+1], array[j] = array[j], array[j+1]return arrayif __name__ == '__main__':# array=[5,3,5,8,1,-4,56,87]# print(array)# QuickSort(array,0,len(array)-1)# print(array)array = [5, 3, 5, 8, 1, -4, 56, 87]print("Original array: ", array)array = BubbleSort(array)print("BubbleSort: ", array)c++實(shí)現(xiàn):
// // Created by fzh on 2021/4/29. // #include <iostream> #include <string> using namespace std; void bubbleSort(int* p, int length){for(int i = 0; i < length - 1; i++){for(int j = 0; j<length - i - 1; j++){if(p[j] > p[j + 1]){ // cout<<"==arr[j]:"<<arr[j]<<endl;int temp = p[j];p[j] = p[j + 1];p[j + 1] = temp;}}} } int main() {int arr[10] = {2,2,3,5,19,6,7,8,9,10};int length = sizeof(arr)/sizeof(arr[0]);cout<<"==length:"<<length<<endl;bubbleSort(arr, length);for(int i=0; i<10; i++){cout<< "==arr[i]:" <<arr[i] <<endl;} }2.插入排序
將未排序元素一個(gè)個(gè)插入到已排序列表中。對(duì)于未排序元素,在已排序序列中從后向前掃描,找到相應(yīng)位置把它插進(jìn)去;在從后向前掃描過(guò)程中,需要反復(fù)把已排序元素逐步向后挪,為新元素提供插入空間??臻g復(fù)雜度是O(1),也就是一個(gè)原地排序算法,穩(wěn)定的排序算法,平均時(shí)間復(fù)雜度O(n2),相比冒泡排序其更受歡迎,主要冒泡排序的數(shù)據(jù)交換要比插入排序復(fù)雜,冒泡排序需要三個(gè)賦值操作,而插入排序只需要一個(gè)。
""" 插入排序:從小到大 """ def InsertionnSort(array):lengths = len(array)# 從索引位置1開(kāi)始for i in range(1, lengths):currentValue = array[i] # 當(dāng)前索引對(duì)應(yīng)的元素?cái)?shù)值preIndex = i - 1 # 前一個(gè)索引位置# 循環(huán)條件: 前一個(gè)索引對(duì)應(yīng)元素值大于當(dāng)前值,前一個(gè)索引值大于等于0while array[preIndex] > currentValue and preIndex >= 0:array[preIndex + 1] = array[preIndex] # 前一個(gè)索引對(duì)應(yīng)元素值賦值給當(dāng)前值preIndex -= 1 # 前一個(gè)索引位置-1# preIndex+1,實(shí)現(xiàn)元素交換array[preIndex + 1] = currentValuereturn arrayarray = [1, 0, 8, -2, 3, 6, 9] print("Original array={}".format(array)) array = InsertionnSort(array) print("InsertionSort={}".format(array)) def insert_sort():a = [9, 8, 3, -3, -7, 1, -5]for i in range(1,len(a)):# value = a[i]for j in range(0,i-1):if a[j]>a[i]:a[j],a[i]=a[i],a[j]print(a)3.選擇排序
說(shuō)明:首先在未排序序列中找到最小(大)元素,與起始位置元素進(jìn)行交換,然后,再?gòu)氖S辔磁判蛟刂欣^續(xù)尋找最小(大)元素,然后在交換。以此類推,直到所有元素均排序完畢??臻g復(fù)雜度是O(1),是原地排序算法,平均時(shí)間復(fù)雜度是O(n2),是不穩(wěn)定的排序算法,因?yàn)槊看味家椅磁判虻淖钚≈到粨Q,
比如 5,8,5,2,9 這樣一組數(shù)據(jù),使用選擇排序算法來(lái)排序,2要跟第一個(gè)5發(fā)生交換,那么這兩個(gè)5的順序就發(fā)生了變化,
?
def SelectionSort(arr):for i in range(len(arr)-1):minIndex=ifor j in range(i+1,len(arr)):if arr[j]<arr[minIndex]:minIndex=jif i!=minIndex:arr[i],arr[minIndex]=arr[minIndex],arr[i]return arr arr=[-1,-3,-6,3,4,0,2] print(arr) arr=SelectionSort(arr) print(arr)4.歸并排序
歸并排序和快速排序,時(shí)間復(fù)雜度為O(nlogn),更適合大規(guī)模的數(shù)據(jù)排序,采用了分治的思想,由于借用temp數(shù)組,故空間復(fù)雜度為O(n),,是一個(gè)穩(wěn)定的排序算法。
寫(xiě)法1:
def Merge(q, r):left, right = 0, 0result = []while left < len(q) and right < len(r):# 比較兩個(gè)指針?biāo)赶虻脑?#xff0c;選擇相對(duì)小的元素放入到合并空間,并移動(dòng)指針到下一位置if q[left] < r[right]:result.append(q[left])left += 1else:result.append(r[right])right += 1result += q[left:] # 若最后left列表剩余,則將其剩余部分加入到result后面result += r[right:] # 若最后right列表剩余,則將其剩余部分加入到result后面return resultdef Merge_Sort(L):if len(L) <= 1:return Lmid = len(L) // 2 # 這里的//是python3中除以后取整的用法,大家不要以為我打錯(cuò)了~q = Merge_Sort(L[:mid])r = Merge_Sort(L[mid:])return Merge(q, r)a=[1,2,9,0,8,2,3] b=Merge_Sort(a) print(b)寫(xiě)法2:
class Solution:def mergeSort(self, nums, start, end):if start >= end:returnmid = start + (end - start) // 2self.mergeSort(nums, start, mid)self.mergeSort(nums, mid + 1, end)self.merge(nums, start, mid, end)def merge(self, nums, start, mid, end):i, j, temp = start, mid + 1, []while i <= mid and j <= end:if nums[i] <= nums[j]:temp.append(nums[i])i += 1else:self.cnt += mid - i + 1temp.append(nums[j])j += 1while i <= mid:temp.append(nums[i])i += 1while j <= end:temp.append(nums[j])j += 1for i in range(len(temp)):nums[start + i] = temp[i]print('==nums:', nums)def reversePairs(self, nums):self.cnt = 0self.mergeSort(nums, 0, len(nums) - 1)print('==after nums:', nums)return self.cntnums = [7,5,6,4] sol = Solution() sol.reversePairs(nums) print(sol.cnt)k路歸并排序:
給出K個(gè)有序的數(shù)組現(xiàn)在將其歸并成一個(gè)有序數(shù)組怎么做最快。
def Merge(q, r):left, right = 0, 0result = []while left < len(q) and right < len(r):# 比較兩個(gè)指針?biāo)赶虻脑?#xff0c;選擇相對(duì)小的元素放入到合并空間,并移動(dòng)指針到下一位置if q[left] < r[right]:result.append(q[left])left += 1else:result.append(r[right])right += 1result += q[left:] # 若最后left列表剩余,則將其剩余部分加入到result后面result += r[right:] # 若最后right列表剩余,則將其剩余部分加入到result后面return resultdef Merge_Sort(L):if len(L) <= 1:return L[0]mid = len(L) // 2q = Merge_Sort(L[:mid])r = Merge_Sort(L[mid:])print('==q==:', q)print('==r==:', r)return Merge(q, r)# a = [1, 2, 9, 0, 8, 2, 3] a = [[1, 2],[0, 3],[-4, 8]] b = Merge_Sort(a) print('==b:', b)5.快速排序
思想:通過(guò)一趟排序?qū)⒋帕斜矸殖瑟?dú)立的兩部分,其中一部分的所有元素均比另一部分的元素小,則分別對(duì)這兩部分繼續(xù)重復(fù)進(jìn)行此操作,以達(dá)到整個(gè)序列有序。
步驟:
使用分治法把一個(gè)串分為兩個(gè)子串,具體算法描述如下:
1,從數(shù)列中挑出一個(gè)元素,稱為'基準(zhǔn)'(privot),本文將第一個(gè)選為基準(zhǔn);
2,比基準(zhǔn)數(shù)小的所有元素放在基準(zhǔn)前面,比基準(zhǔn)數(shù)大的所有元素放在基準(zhǔn)后面,這樣完成了分區(qū)操作;
3,遞歸地把小于基準(zhǔn)值元素的子數(shù)列和大于基準(zhǔn)值元素的子數(shù)列按上述操作進(jìn)行排序,遞歸結(jié)束條件序列的大小為0或1。
根據(jù)分治,遞歸的處理思想,利用遞歸排序下標(biāo)從p到q-1之間的數(shù)據(jù),和下標(biāo)從q+1到r之間的數(shù)據(jù),直至區(qū)間縮小為1,說(shuō)明所有的數(shù)據(jù)都有序了,跟歸并排序不同的是,要做成原地排序算法,不借用temp,空間復(fù)雜度為O(1),時(shí)間復(fù)雜度為O(nlogn)。由于涉及到選擇基準(zhǔn),倘若基準(zhǔn)是兩個(gè)相同的數(shù),經(jīng)過(guò)分區(qū)處理后,順序可能發(fā)生變化,故是不穩(wěn)定的算法。
流程:
代碼:
def QuickSort(array,start,end):length=len(array)i=startj=endif i>=j:returnprivot=array[i]while i<j:#從右往左while i<j and privot<=array[j]:j-=1array[i]=array[j]# print(array)# 從左往右while i < j and privot >= array[i]:i+= 1array[j] = array[i]# print(array)array[i]=privot# print(array)#QuickSort(array,start,i-1)QuickSort(array, i+1, end) if __name__ == '__main__':array=[5,3,5,8,1,-4,56,87]print(array)QuickSort(array,0,len(array)-1)print(array)更快的方式:
#選擇中間的數(shù)作為基準(zhǔn) def quicksort(arr):if len(arr) <= 1:return arrpivot = arr[len(arr) // 2]left = [x for x in arr if x < pivot]print('left=',left)middle = [x for x in arr if x == pivot]print('middle=', middle)right = [x for x in arr if x > pivot]print('right=', right)return quicksort(left) + middle + quicksort(right) if __name__ == '__main__':print(quicksort([9,8,7,6,5,43,2]))6.桶排序
桶排序,將要排序的數(shù)據(jù)分到幾個(gè)有序的桶里,每個(gè)桶里的數(shù)據(jù)單獨(dú)進(jìn)行排序。桶內(nèi)排完序之后,再把每個(gè)桶里的數(shù)據(jù)按照順序依次取出,組成的序列就是有序的了。桶排序的時(shí)間復(fù)雜度為什么是 O(n) 呢?排序數(shù)據(jù)有n個(gè),均勻劃分到m個(gè)桶內(nèi),每個(gè)桶就有k=n/m個(gè)元素,每個(gè)桶使用快速排序,時(shí)間復(fù)雜度為O(k*logk)。m個(gè)桶排序時(shí)復(fù)雜度就是O(m*k*logk),故整個(gè)桶排序時(shí)間復(fù)雜度就是O(n*(log(n/m))),m足夠大時(shí),log(n/m)是一個(gè)很小的值,故時(shí)間復(fù)雜度接近O(n)。極端情況下,分到一個(gè)桶里,就變?yōu)镺(n*logn),其適合外部排序,也就是數(shù)據(jù)存儲(chǔ)在外部磁盤中,數(shù)據(jù)量比較大,內(nèi)存有限,無(wú)法將數(shù)據(jù)全部加載到內(nèi)存中。其是一個(gè)穩(wěn)定的排序算法。
7.計(jì)數(shù)排序
計(jì)數(shù)排序可以看成是桶排序的一種特設(shè)請(qǐng)情況,考生的滿分是 900 分,最小是 0 分,這個(gè)數(shù)據(jù)的范圍很小,可以分成901個(gè)桶,將50萬(wàn)考生劃分到901個(gè)桶,桶內(nèi)的數(shù)據(jù)是相同分?jǐn)?shù)的考生,只需要依次掃描每個(gè)桶,將桶內(nèi)的考生依次輸出到一個(gè)數(shù)組中,即實(shí)現(xiàn)排序,也就是計(jì)數(shù)排序,其只適合于數(shù)據(jù)范圍不大的場(chǎng)景,如果數(shù)據(jù)范圍k比排序數(shù)據(jù)n大很多,就不是和計(jì)數(shù)排序了,而且只適合給非負(fù)整數(shù)排序,若有負(fù)數(shù),需要轉(zhuǎn)換成正數(shù),歸一化。其是一個(gè)穩(wěn)定的排序算法,時(shí)間復(fù)雜度O(n),
8.基數(shù)排序
基數(shù)排序,假設(shè)有10萬(wàn)個(gè)手機(jī)號(hào)碼,希望將這10萬(wàn)個(gè)手機(jī)號(hào)碼按下到大排序,時(shí)間復(fù)雜度O(n),基數(shù)排序?qū)σ判虻臄?shù)據(jù)是有要求的,需要可以分割出獨(dú)立的‘位’來(lái)比較,而且具有遞進(jìn)的關(guān)系。其是一個(gè)穩(wěn)定的排序算法。
下面用字母來(lái)代替。
總結(jié):Glibc的qsort()函數(shù)會(huì)優(yōu)先使用歸并排序,對(duì)于1k,2k左右的數(shù)據(jù),完全可以用空間換時(shí)間,因?yàn)槠淇臻g復(fù)雜度是O(n),時(shí)間復(fù)雜度是O(nlogn)。
對(duì)于小數(shù)量數(shù)據(jù)的排序,O(n2)的排序算法并不一定比O(nlogn)排序算法執(zhí)行時(shí)間長(zhǎng),對(duì)于小數(shù)據(jù)的排序,選擇簡(jiǎn)單,
不需要遞歸的插入排序算法,遞歸過(guò)深容易導(dǎo)致堆棧溢出。
數(shù)據(jù)量較大的情況就用快速排序,分區(qū)點(diǎn)的選擇可以用三數(shù)取中法和隨機(jī)法
1,三數(shù)取中法,從區(qū)間的首,尾,中間分別取出一個(gè)數(shù),然后比對(duì)大小,取這三個(gè)數(shù)的中間值作為分區(qū)點(diǎn),如果排序的數(shù)組很大,可能就要用多個(gè)數(shù)來(lái)取中間值。
2,隨機(jī)法,每次從要排序的區(qū)間,隨機(jī)選擇一個(gè)元素作為分區(qū)點(diǎn),從概率的角度講不會(huì)出現(xiàn)每個(gè)分區(qū)點(diǎn)都會(huì)選的很差的情況。
?
總結(jié)
以上是生活随笔為你收集整理的排序算法--(冒泡排序,插入排序,选择排序,归并排序,快速排序,桶排序,计数排序,基数排序)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Android之shape属性详解
- 下一篇: git 撤销修改:未push 、已pus