日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

教小学妹学算法:十大经典排序算法深度解析

發布時間:2024/5/7 编程问答 63 豆豆
生活随笔 收集整理的這篇文章主要介紹了 教小学妹学算法:十大经典排序算法深度解析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

最近有一位小學妹 Coco 入坑了算法,結果上來就被幾個排序算法給整懵逼了,各種排序眼花繚亂,也分不清什么時候該用什么排序了。
今天呢,就在這分享一下我給小學妹講十大經典排序算法的過程。


好吧,那我們就先來看一下十大經典排序算法是哪些:

排序算法大致可以分為兩大類,一種是比較類排序,即通過元素之間的比較來決定相對次序;另一種是非比較類排序,運行時間比較快,但是也有諸多限制。

在開始正式講解之前呢,先來介紹一個工具,對數器:

比如說我們寫了一個比較NB的Algorithm,但是又不確定right or wrong的時候,就可以通過對數器來驗證。

拿第一個要講的冒泡排序為例:

import copy import randomdef bubbleSort(arr: list):length = len(arr)for trip in range(length):for index in range(length - trip - 1):if arr[index] > arr[index + 1]:arr[index], arr[index + 1] = arr[index + 1], arr[index]if __name__ == '__main__':flag = Truefor i in range(100):list1 = [random.randint(0, 100) for _ in range(random.randint(0, 100))]list2 = copy.deepcopy(list1)list3 = copy.deepcopy(list1)bubbleSort(list2)list3.sort()if list2 != list3:flag = Falseprint(list1)print(list2)print(list3)breakprint("Nice" if flag else "Fuck")

假如說bubbleSort是我們自己編寫的一個算法,但是我不確定結果是不是正確,這時候,我們可以隨機造一堆數據,然后拷貝一份,第一份用Python內置的排序算法進行排序,第二份用我們自己編寫的algorithm進行排序,如果兩個算法排序的結果一樣的話,就可以大致證明我們的算法正確。

當然,一次驗證的結果可能存在偶然性,所以我們可以多驗證幾次,如果對于大量隨機的結果來說,我們的algorithm輸出結果都正確,那么就有很大把握確定這個algorithm是right的。

一、比較類排序

比較類排序還是比較好理解的,就是兩個元素之間比大小然后排隊嘛,比較常規。

在算法層面,比較類排序由于其時間復雜度不能突破O(nlogn),所以也被稱為非線性時間復雜度排序。

1.冒泡排序Bubble Sort

冒泡排序是一種非常簡單易懂的排序算法,它在遍歷整個數列的過程中,一次次的比較相鄰的兩個元素的大小,如果順序錯誤就將其交換過來。

冒泡排序每次都可以將一個當前最大的數移動到數列的最后,就好像冒泡泡一樣,算法的名字也是由此而來。

先來看一張動圖演示:

實現思路

  • 比較相鄰的兩個元素,如果順序錯誤,就交換兩個的位置;
  • 對每兩個相鄰的元素都做相同的工作,這樣一趟下來會將最大的元素排在最后;
  • 對除最后一個元素之外剩下的數列重復上述操作;
  • 重復步驟1~3,直至排序完成
  • Code

    def bubbleSort(arr: list):length = len(arr)for trip in range(length):for index in range(length - trip - 1):# 相鄰的兩個元素,如果順序錯誤,就交換兩個的位置if arr[index] > arr[index + 1]:arr[index], arr[index + 1] = arr[index + 1], arr[index]

    可以看到,冒泡排序必須通過兩層循環,并且循環的次數與待排序數組的長度有關,因此其時間復雜度為O(n2)。

    算法分析

    冒泡排序每次都要比較完所有的相鄰的兩個數,但實際上,如果在某一次比較過程沒有交換發生的話,即可證明數列已經有序的,因此我們可以在這點下文章,稍微優化一下。

    def bubbleSortV1(arr: list):length = len(arr)for trip in range(length):# 交換標志exChange = Falsefor index in range(length - trip - 1):# 相鄰的兩個元素,如果順序錯誤,就交換兩個的位置if arr[index] > arr[index + 1]:# 如果有交換發生, 標記為 TrueexChange = Truearr[index], arr[index + 1] = arr[index + 1], arr[index]# 如果沒有交換發生,說明數列已經有序了if not exChange:break

    如果待排序的數列本身就是有序的,那么bubbleSortV1走一遍就可以了,即最好時間復雜度為O(n),如果待排序的數列本身是逆序的,那么時間復雜度還是O(n2)。

    2.選擇排序Select Sort

    選擇排序的思路比較類似于我們人類的想法,它的工作原理:首先在未排序數列中找到最小或最大的元素,交換到已排序數列的末尾,然后再從剩余未排序數列中繼續尋找最小的元素或最大的元素繼續做交換,以此類推,直到所有元素都排序完。

    還是先來看一個動圖演示:

    實現思路

  • 初始狀態:有序區間為空,無序區間為[1:n];
  • 第i(i=1,2,3,…)趟排序時,有序區間為[1:i],無序區間為(i:n],該趟排序在無序區間中找到最小或最大的元素,插入到有序區間的最后;
  • 重復循環n-1趟。
  • Code

    def selectSort(array: list):length = len(array)for trip in range(length - 1):for index in range(trip + 1, length):if array[index] < array[trip]:array[trip], array[index] = array[index], array[trip]

    算法分析

    選擇排序是最穩定的排序算法之一,任何數列放進去都是O(n2)的時間復雜度,所以適用于數據規模比較小的數列,不過選擇排序不占用額外的內存空間。

    3.插入排序Insert Sort

    插入排序的思想類似于我們打撲克的時候抓牌,保證手里的牌有序,當抓到一張新的牌時,按照大小排序將牌插入到適當的位置。

    來看動圖演示:

    實現思路

  • 從第一個元素開始,該元素可以被認為已經被排序;
  • 取出下一個元素,從后向前掃描已排序的數列,如果被掃描的元素大于新元素則繼續向前掃描,否則將新元素插入到該元素后邊;
  • 重復步驟2,直到數列有序。
  • Code

    def insertSort(arr: list):for trip in range(1, len(arr)):for index in range(trip - 1, -1, -1):if arr[index] > arr[index + 1]:arr[index], arr[index + 1] = arr[index + 1], arr[index]

    算法分析

    插入排序在實現中采用in-place排序,從后往前掃描的過程中需要反復將已排序元素向后移動為新元素提供插入空間,因此時間復雜度也為O(n2)。

    4.希爾排序Shell Sort

    希爾排序(Shell Sort),這是一個以人命名的排序算法,1959年由Shell發明,這是第一個時間復雜度突破O(2)的排序算法,它是簡單插入排序的改進版,與其不同之處在于Shell Sort會優先比較距離較遠的元素,所以也叫縮小增量排序

    動圖演示:

    實現思路

    Shell Sort是把數列按照一定的間隔分組,在每組內使用直接插入排序,隨著間隔的減小,整個數列將會變得有序。

  • 確定一個增量序列,即間隔長度t1,t2,…,tk,最終tk=1;
  • 按照增量序列的個數x,對數列進行x趟排序;
  • 在每趟排序過程中,根據對應的增量ti將待排數列分割成若干長度為m的子數列,分別在各個子數列中進行直接插入排序。
  • Code

    def shellSort(array: list):length, gap = len(array), len(array) // 2while gap > 0:for trip in range(gap, length):for index in range(trip - gap, -1, -gap):if array[index] > array[index + gap]:array[index], array[index + gap] = array[index + gap], array[index]gap //= 2

    算法分析

    Shell Sort 的核心在于增量序列的設定,既可以提前設定好增量序列,也可以在排序的過程中動態生成。

    5.快速排序

    快速排序的基本思想比較有意思,它通過一趟排序將待排記錄分割成兩部分,其中一部分數列均比關鍵字小,另一部分均比關鍵字大,然后繼續對這個兩部分進行快速排序,最終達到整個數列有序。

    動圖演示:

    實現思路

  • 從數列中隨機挑選一個元素作為基準(pivot);
  • 遍歷整個序列,所有比基準值小的放在基準前,比基準值大的放在基準后,這樣就將基準值放在了數列的中間位置;
  • 遞歸重復排序小于基準值的數列和大于基準值的數列。
  • Code

    def randomQuickSort(array: list):if len(array) < 2:return_randomQuickSort(array, 0, len(array) - 1)def _randomQuickSort(array: list, left: int, right: int):if left < right:# less, more 分別表示與基準值相等的數列的左右邊界less, more = partition(array, left, right, array[random.randint(left, right)])_randomQuickSort(array, left, less)_randomQuickSort(array, more, right)def partition(array: list, left: int, right: int, pivot: int):"""將比基準值小的數放在左邊,相等的放中間,大的放右邊"""less, more = left - 1, right + 1while left < more:if array[left] < pivot:less += 1array[left], array[less] = array[less], array[left]left += 1elif array[left] > pivot:more -= 1array[left], array[more] = array[more], array[left]else:left += 1return less, more

    算法分析

    隨機快速排序的一次劃分從數列的兩頭開始交替搜索,知道left和right重合,因此其時間復雜度為O(n)。

    快速排序算法的時間復雜度與劃分的趟數有關系,理想的情況是每次劃分所選擇的基準值恰好是當前數列的中位數,經過log2n趟劃分,便可得到長度為1的數列,因此快速排序的時間復雜度為O(nlog2n)。

    最壞的情況是,每次所選的基準值是當前數列的最大或最小值,這使得每次劃分的數列中有一個為空,另一個數列的長度為原數列的長度減去基準值數列的長度,這樣,長度為n的數列的快速排序需要經過n趟劃分,這時整個隨機快速排序的時間復雜度為O(n2)。

    從空間性能上看,隨機快速排序可以在數列內部進行交換,因此隨機快速排序的空間復雜度為O(1)。

    6.歸并排序

    歸并排序采用分治法(Divide and Conquer),將已有序的子數列合并,得到完全有序我的數列,即先使每個子數列有序,再使子數列間有序,將兩個有序數列合并成一個有序數列成為2-路歸并。

    動圖演示:

    實現思路

  • 把長度為n的數列分成兩個長度為n/2的子數列;
  • 對兩個子數列分別采用歸并排序;
  • 將兩個排好序的子數列合并成一個有序數列。
  • Code

    def mergeSort(arr: list, left: int, right: int):if left == right:return# 通過位運算計算可以加快計算效率,下式可以避免溢出mid = left + ((right - left) >> 1)# 遞歸排序子數列mergeSort(arr, left, mid)mergeSort(arr, mid + 1, right)# 將排序好的子數列合并merge(arr, left, mid, right)def merge(arr: list, left: int, mid: int, right: int):helpList, p1, p2 = [], left, mid + 1# 合并兩個子數列直至一個數列為空while p1 < mid + 1 and p2 < right + 1:if arr[p1] < arr[p2]:helpList.append(arr[p1])p1 += 1else:helpList.append(arr[p2])p2 += 1# 將剩下的數列全部添加到合并數列的末尾while p1 < mid + 1:helpList.append(arr[p1])p1 += 1while p2 < right + 1:helpList.append(arr[p2])p2 += 1# 將合并數列拷貝到原數列for index in range(len(helpList)):arr[left + index] = helpList[index]

    算法分析

    歸并排序的性能不受輸入數據的影響,時間復雜度始終是O(nlogn),然而代價是需要額外的內存空間。

    其實歸并排序的額外空間復雜度可以變成O(1),采用歸并排序內部緩存法,但是非常難。

    7.堆排序

    堆排序這個算法就比較有意思了,利用堆這種數據結構,其實就是將數列想象成一個完全二叉樹,然后根據最大堆或者最小堆的性質做調整,即可將數列排序。

    動圖演示:

    實現思路

  • 將初始數列構成成大根堆,此為初始無序區間;
  • 堆頂元素與數列末尾元素交換,即將無序區間最大值插入到有序區間;
  • 交換后的新堆違反大根堆的性質,必須重新調整為大根堆;
  • 重復2、3步驟,直至數列有序。
  • Code

    def heapInsert(array: list, index: int):while array[(index - 1) // 2] < array[index] and index > 0:array[(index - 1) // 2], array[index] = array[index], array[(index - 1) // 2]index = (index - 1) // 2def heapify(arr: list, index: int, length: int):left = 2 * index + 1while left < length:# 左右子節點中的最大值索引largest = left + 1 if (left + 1 < length) and (arr[left + 1] > arr[left]) else left# 節點與子節點中的最大值索引largest = largest if arr[largest] > arr[index] else indexif largest == index:# 如果節點即為最大值則無需繼續調整breakelse:# 否則交換節點與最大值節點arr[index], arr[largest] = arr[largest], arr[index]index = largestleft = 2 * index + 1def heapSort(array: list):length = len(array)if length < 2:returnfor index in range(1, length):heapInsert(array, index)for index in range(length - 1, -1, -1):array[0], array[index] = array[index], array[0]heapify(array, 0, index)

    二、非比較類排序

    1.計數排序

    計數排序是一種統計排序,而不是比較排序了,計數排序需要知道待排序數列的范圍,然后統計在范圍內每個元素的出現次數,最后按照次數輸出即是排序結果。

    動圖演示:

    實現思路

  • 根據數列的最大元素計數創建空間;
  • 遍歷整個數列,統計每個元素出現的次數;
  • 遍歷統計區間,按照統計次數輸出結果。
  • Code

    def countSort(array: list):count = [0 for _ in range(max(array) + 1)]for value in array:count[value] += 1array.clear()for index, values in enumerate(count):for _ in range(values):array.append(index)

    算法分析

    計數排序的速度非常快,但是它需要知道數列的元素范圍,如果數列元素的范圍非常大,則需要創建非常大的額外空間。

    作為一種線性時間復雜度的排序,計數排序要求輸入的數據必須是有確定范圍的整數。

    計數排序是一個穩定的排序算法。當輸入的元素是 n 個 0到 k 之間的整數時,時間復雜度是O(n+k),空間復雜度也是O(n+k),其排序速度快于任何比較排序算法。

    當k不是很大并且序列比較集中時,計數排序是一個很有效的排序算法。

    2.桶排序

    桶排序在計數排序的方法上利用了函數的映射關系進行改進,不需要知道數列元素的范圍,但也需要額外創建一個序列空間,空間中的每個區間存放屬于該范圍的有序元素,最后遍歷整個空間,從小到大輸出即是有序數列。

    動圖演示:

    實現思路

  • 設置一個定量的數組作為空桶;
  • 遍歷待排序數列,并把元素一個一個放到對應的桶里;
  • 對每個桶內的元素進行排序;
  • 從非空桶中將排好序的元素拼接起來。
  • Code

    def randomQuickSort(array: list):if len(array) < 2:return_randomQuickSort(array, 0, len(array) - 1)def _randomQuickSort(array: list, left: int, right: int):if left < right:less, more = partition(array, left, right, array[random.randint(left, right)])_randomQuickSort(array, left, less)_randomQuickSort(array, more, right)def partition(array: list, left: int, right: int, pivot: int):less, more = left - 1, right + 1while left < more:if array[left] < pivot:less += 1array[left], array[less] = array[less], array[left]left += 1elif array[left] > pivot:more -= 1array[left], array[more] = array[more], array[left]else:left += 1return less, moredef bucketSort(array: list):length = len(array)if length < 2:returnbucketNumber = 10maxNumber, bucket = max(array), [[] for _ in range(bucketNumber)]for item in array:index = min(item // (maxNumber // bucketNumber), bucketNumber - 1)bucket[index].append(item)randomQuickSort(bucket[index])array.clear()for value in bucket:array.extend(value)

    算法分析

    桶排序的最佳時間復雜度為線性時間O(n),平均時間復雜度取決于桶內數據排序的時間復雜度,因為其它部分的時間復雜度都是O(n),所以桶劃分的越小,各個桶之間的數據越少,排序所用的時間也會越少,但相應消耗的空間就會增大。

    3.基數排序

    基數排序的實現原理比較特別,對于數列中的每個元素,先按照它的個位進行排序,然后按照十位進行排序,以此類推。

    動圖演示:

    實現思路

  • 取得數列中最大數,計算其位數;
  • 從最低位開始對數列的每一個元素分類;
  • 將每個分類中的元素按照順序重新組合在一次;
  • Code

    def radixSort(array: list):length, maxNumber, base = len(array), max(array), 0while 10 ** base <= maxNumber:buckets = [[] for _ in range(10)]for value in array:buckets[(value // 10 ** base) % 10].append(value)array.clear()for bucket in buckets:array.extend(bucket)base += 1

    算法分析

    基數排序是穩定的,但是性能要比桶排序略差,每一次元素的桶分配都需要O(n)的時間復雜度,而且分配之后得到新的數列又需要O(n)的時間復雜度,假如待排數列可以分為K個關鍵字,則基數排序的時間復雜度將是O(d*2n),當然d要遠小于n,因此基本上是線性級別的。

    三、總結



    總結

    以上是生活随笔為你收集整理的教小学妹学算法:十大经典排序算法深度解析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。