排序算法--(冒泡排序,插入排序,选择排序,归并排序,快速排序,桶排序,计数排序,基数排序)
一.時間復雜度分析
- **時間復雜度**:對排序數據的總的操作次數。反應當n變化時,操作次數呈現什么規律
- **空間復雜度**:算法在計算機內執行時所需要的存儲空間的容量,它也是數據規模n的函數。
1.例題:
有一個字符串數組,將數組中的每一個字符串按照字母序排序,之后再將整個字符串數組按照字典序排序,時間復雜度多少?
假設最長字符串長度為s,有n個字符串,則每個字符串按照字母序排序時間復雜度為:O(n*slogs)
整個字符串數組按照字典序排序時間復雜度為:O(s*nlogn),所以總的時間復雜度為:O(n*slogs+s*nlogn)
各種排序算法復雜度:
二.各種排序算法
1.冒泡排序:
**復雜度分析:**
- 時間復雜度:若給定的數組剛好是排好序的數組,采用改進后的冒泡排序算法,只需循環一次就行了,此時是最優時間復雜度:O(n),若給定的是倒序,此時是最差時間復雜度:O(n^2) ,因此綜合平均時間復雜度為:O(n^2)
- 空間復雜度:因為每次只需開辟一個temp的空間,因此空間復雜度是:O(1),是一個原地排序算法,等于的話不做交換,故是一個穩定的排序算法。
步驟:
1. 比較相鄰的元素。如果第一個比第二個大,就交換它們兩個;
2. 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最后一對,這樣在最后的元素應該會是最大的數;
3. 針對所有的元素重復以上的步驟,除了最后一個;
4. 重復步驟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++實現:
// // 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.插入排序
將未排序元素一個個插入到已排序列表中。對于未排序元素,在已排序序列中從后向前掃描,找到相應位置把它插進去;在從后向前掃描過程中,需要反復把已排序元素逐步向后挪,為新元素提供插入空間。空間復雜度是O(1),也就是一個原地排序算法,穩定的排序算法,平均時間復雜度O(n2),相比冒泡排序其更受歡迎,主要冒泡排序的數據交換要比插入排序復雜,冒泡排序需要三個賦值操作,而插入排序只需要一個。
""" 插入排序:從小到大 """ def InsertionnSort(array):lengths = len(array)# 從索引位置1開始for i in range(1, lengths):currentValue = array[i] # 當前索引對應的元素數值preIndex = i - 1 # 前一個索引位置# 循環條件: 前一個索引對應元素值大于當前值,前一個索引值大于等于0while array[preIndex] > currentValue and preIndex >= 0:array[preIndex + 1] = array[preIndex] # 前一個索引對應元素值賦值給當前值preIndex -= 1 # 前一個索引位置-1# preIndex+1,實現元素交換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.選擇排序
說明:首先在未排序序列中找到最小(大)元素,與起始位置元素進行交換,然后,再從剩余未排序元素中繼續尋找最小(大)元素,然后在交換。以此類推,直到所有元素均排序完畢。空間復雜度是O(1),是原地排序算法,平均時間復雜度是O(n2),是不穩定的排序算法,因為每次都要找未排序的最小值交換,
比如 5,8,5,2,9 這樣一組數據,使用選擇排序算法來排序,2要跟第一個5發生交換,那么這兩個5的順序就發生了變化,
?
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.歸并排序
歸并排序和快速排序,時間復雜度為O(nlogn),更適合大規模的數據排序,采用了分治的思想,由于借用temp數組,故空間復雜度為O(n),,是一個穩定的排序算法。
寫法1:
def Merge(q, r):left, right = 0, 0result = []while left < len(q) and right < len(r):# 比較兩個指針所指向的元素,選擇相對小的元素放入到合并空間,并移動指針到下一位置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中除以后取整的用法,大家不要以為我打錯了~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)寫法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個有序的數組現在將其歸并成一個有序數組怎么做最快。
def Merge(q, r):left, right = 0, 0result = []while left < len(q) and right < len(r):# 比較兩個指針所指向的元素,選擇相對小的元素放入到合并空間,并移動指針到下一位置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.快速排序
思想:通過一趟排序將待排列表分成獨立的兩部分,其中一部分的所有元素均比另一部分的元素小,則分別對這兩部分繼續重復進行此操作,以達到整個序列有序。
步驟:
使用分治法把一個串分為兩個子串,具體算法描述如下:
1,從數列中挑出一個元素,稱為'基準'(privot),本文將第一個選為基準;
2,比基準數小的所有元素放在基準前面,比基準數大的所有元素放在基準后面,這樣完成了分區操作;
3,遞歸地把小于基準值元素的子數列和大于基準值元素的子數列按上述操作進行排序,遞歸結束條件序列的大小為0或1。
根據分治,遞歸的處理思想,利用遞歸排序下標從p到q-1之間的數據,和下標從q+1到r之間的數據,直至區間縮小為1,說明所有的數據都有序了,跟歸并排序不同的是,要做成原地排序算法,不借用temp,空間復雜度為O(1),時間復雜度為O(nlogn)。由于涉及到選擇基準,倘若基準是兩個相同的數,經過分區處理后,順序可能發生變化,故是不穩定的算法。
流程:
代碼:
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)更快的方式:
#選擇中間的數作為基準 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.桶排序
桶排序,將要排序的數據分到幾個有序的桶里,每個桶里的數據單獨進行排序。桶內排完序之后,再把每個桶里的數據按照順序依次取出,組成的序列就是有序的了。桶排序的時間復雜度為什么是 O(n) 呢?排序數據有n個,均勻劃分到m個桶內,每個桶就有k=n/m個元素,每個桶使用快速排序,時間復雜度為O(k*logk)。m個桶排序時復雜度就是O(m*k*logk),故整個桶排序時間復雜度就是O(n*(log(n/m))),m足夠大時,log(n/m)是一個很小的值,故時間復雜度接近O(n)。極端情況下,分到一個桶里,就變為O(n*logn),其適合外部排序,也就是數據存儲在外部磁盤中,數據量比較大,內存有限,無法將數據全部加載到內存中。其是一個穩定的排序算法。
7.計數排序
計數排序可以看成是桶排序的一種特設請情況,考生的滿分是 900 分,最小是 0 分,這個數據的范圍很小,可以分成901個桶,將50萬考生劃分到901個桶,桶內的數據是相同分數的考生,只需要依次掃描每個桶,將桶內的考生依次輸出到一個數組中,即實現排序,也就是計數排序,其只適合于數據范圍不大的場景,如果數據范圍k比排序數據n大很多,就不是和計數排序了,而且只適合給非負整數排序,若有負數,需要轉換成正數,歸一化。其是一個穩定的排序算法,時間復雜度O(n),
8.基數排序
基數排序,假設有10萬個手機號碼,希望將這10萬個手機號碼按下到大排序,時間復雜度O(n),基數排序對要排序的數據是有要求的,需要可以分割出獨立的‘位’來比較,而且具有遞進的關系。其是一個穩定的排序算法。
下面用字母來代替。
總結:Glibc的qsort()函數會優先使用歸并排序,對于1k,2k左右的數據,完全可以用空間換時間,因為其空間復雜度是O(n),時間復雜度是O(nlogn)。
對于小數量數據的排序,O(n2)的排序算法并不一定比O(nlogn)排序算法執行時間長,對于小數據的排序,選擇簡單,
不需要遞歸的插入排序算法,遞歸過深容易導致堆棧溢出。
數據量較大的情況就用快速排序,分區點的選擇可以用三數取中法和隨機法
1,三數取中法,從區間的首,尾,中間分別取出一個數,然后比對大小,取這三個數的中間值作為分區點,如果排序的數組很大,可能就要用多個數來取中間值。
2,隨機法,每次從要排序的區間,隨機選擇一個元素作為分區點,從概率的角度講不會出現每個分區點都會選的很差的情況。
?
總結
以上是生活随笔為你收集整理的排序算法--(冒泡排序,插入排序,选择排序,归并排序,快速排序,桶排序,计数排序,基数排序)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android之shape属性详解
- 下一篇: git 撤销修改:未push 、已pus