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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

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

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


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

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

在開始正式講解之前呢,先來介紹一個(gè)工具,對數(shù)器:

比如說我們寫了一個(gè)比較NB的Algorithm,但是又不確定right or wrong的時(shí)候,就可以通過對數(shù)器來驗(yàn)證。

拿第一個(gè)要講的冒泡排序?yàn)槔?#xff1a;

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是我們自己編寫的一個(gè)算法,但是我不確定結(jié)果是不是正確,這時(shí)候,我們可以隨機(jī)造一堆數(shù)據(jù),然后拷貝一份,第一份用Python內(nèi)置的排序算法進(jìn)行排序,第二份用我們自己編寫的algorithm進(jìn)行排序,如果兩個(gè)算法排序的結(jié)果一樣的話,就可以大致證明我們的算法正確。

當(dāng)然,一次驗(yàn)證的結(jié)果可能存在偶然性,所以我們可以多驗(yàn)證幾次,如果對于大量隨機(jī)的結(jié)果來說,我們的algorithm輸出結(jié)果都正確,那么就有很大把握確定這個(gè)algorithm是right的。

一、比較類排序

比較類排序還是比較好理解的,就是兩個(gè)元素之間比大小然后排隊(duì)嘛,比較常規(guī)。

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

1.冒泡排序Bubble Sort

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

冒泡排序每次都可以將一個(gè)當(dāng)前最大的數(shù)移動(dòng)到數(shù)列的最后,就好像冒泡泡一樣,算法的名字也是由此而來。

先來看一張動(dòng)圖演示:

實(shí)現(xiàn)思路

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

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

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

    算法分析

    冒泡排序每次都要比較完所有的相鄰的兩個(gè)數(shù),但實(shí)際上,如果在某一次比較過程沒有交換發(fā)生的話,即可證明數(shù)列已經(jīng)有序的,因此我們可以在這點(diǎn)下文章,稍微優(yōu)化一下。

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

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

    2.選擇排序Select Sort

    選擇排序的思路比較類似于我們?nèi)祟惖南敕?#xff0c;它的工作原理:首先在未排序數(shù)列中找到最小或最大的元素,交換到已排序數(shù)列的末尾,然后再從剩余未排序數(shù)列中繼續(xù)尋找最小的元素或最大的元素繼續(xù)做交換,以此類推,直到所有元素都排序完。

    還是先來看一個(gè)動(dòng)圖演示:

    實(shí)現(xiàn)思路

  • 初始狀態(tài):有序區(qū)間為空,無序區(qū)間為[1:n];
  • 第i(i=1,2,3,…)趟排序時(shí),有序區(qū)間為[1:i],無序區(qū)間為(i:n],該趟排序在無序區(qū)間中找到最小或最大的元素,插入到有序區(qū)間的最后;
  • 重復(fù)循環(huá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]

    算法分析

    選擇排序是最穩(wěn)定的排序算法之一,任何數(shù)列放進(jìn)去都是O(n2)的時(shí)間復(fù)雜度,所以適用于數(shù)據(jù)規(guī)模比較小的數(shù)列,不過選擇排序不占用額外的內(nèi)存空間。

    3.插入排序Insert Sort

    插入排序的思想類似于我們打撲克的時(shí)候抓牌,保證手里的牌有序,當(dāng)抓到一張新的牌時(shí),按照大小排序?qū)⑴撇迦氲竭m當(dāng)?shù)奈恢谩?/p>

    來看動(dòng)圖演示:

    實(shí)現(xiàn)思路

  • 從第一個(gè)元素開始,該元素可以被認(rèn)為已經(jīng)被排序;
  • 取出下一個(gè)元素,從后向前掃描已排序的數(shù)列,如果被掃描的元素大于新元素則繼續(xù)向前掃描,否則將新元素插入到該元素后邊;
  • 重復(fù)步驟2,直到數(shù)列有序。
  • 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]

    算法分析

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

    4.希爾排序Shell Sort

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

    動(dòng)圖演示:

    實(shí)現(xiàn)思路

    Shell Sort是把數(shù)列按照一定的間隔分組,在每組內(nèi)使用直接插入排序,隨著間隔的減小,整個(gè)數(shù)列將會(huì)變得有序。

  • 確定一個(gè)增量序列,即間隔長度t1,t2,…,tk,最終tk=1;
  • 按照增量序列的個(gè)數(shù)x,對數(shù)列進(jìn)行x趟排序;
  • 在每趟排序過程中,根據(jù)對應(yīng)的增量ti將待排數(shù)列分割成若干長度為m的子數(shù)列,分別在各個(gè)子數(shù)列中進(jìn)行直接插入排序。
  • 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 的核心在于增量序列的設(shè)定,既可以提前設(shè)定好增量序列,也可以在排序的過程中動(dòng)態(tài)生成。

    5.快速排序

    快速排序的基本思想比較有意思,它通過一趟排序?qū)⒋庞涗浄指畛蓛刹糠?#xff0c;其中一部分?jǐn)?shù)列均比關(guān)鍵字小,另一部分均比關(guān)鍵字大,然后繼續(xù)對這個(gè)兩部分進(jìn)行快速排序,最終達(dá)到整個(gè)數(shù)列有序。

    動(dòng)圖演示:

    實(shí)現(xiàn)思路

  • 從數(shù)列中隨機(jī)挑選一個(gè)元素作為基準(zhǔn)(pivot);
  • 遍歷整個(gè)序列,所有比基準(zhǔn)值小的放在基準(zhǔn)前,比基準(zhǔn)值大的放在基準(zhǔn)后,這樣就將基準(zhǔn)值放在了數(shù)列的中間位置;
  • 遞歸重復(fù)排序小于基準(zhǔn)值的數(shù)列和大于基準(zhǔn)值的數(shù)列。
  • 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 分別表示與基準(zhǔn)值相等的數(shù)列的左右邊界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):"""將比基準(zhǔn)值小的數(shù)放在左邊,相等的放中間,大的放右邊"""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

    算法分析

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

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

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

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

    6.歸并排序

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

    動(dòng)圖演示:

    實(shí)現(xiàn)思路

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

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

    算法分析

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

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

    7.堆排序

    堆排序這個(gè)算法就比較有意思了,利用堆這種數(shù)據(jù)結(jié)構(gòu),其實(shí)就是將數(shù)列想象成一個(gè)完全二叉樹,然后根據(jù)最大堆或者最小堆的性質(zhì)做調(diào)整,即可將數(shù)列排序。

    動(dòng)圖演示:

    實(shí)現(xiàn)思路

  • 將初始數(shù)列構(gòu)成成大根堆,此為初始無序區(qū)間;
  • 堆頂元素與數(shù)列末尾元素交換,即將無序區(qū)間最大值插入到有序區(qū)間;
  • 交換后的新堆違反大根堆的性質(zhì),必須重新調(diào)整為大根堆;
  • 重復(fù)2、3步驟,直至數(shù)列有序。
  • 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:# 左右子節(jié)點(diǎn)中的最大值索引largest = left + 1 if (left + 1 < length) and (arr[left + 1] > arr[left]) else left# 節(jié)點(diǎn)與子節(jié)點(diǎn)中的最大值索引largest = largest if arr[largest] > arr[index] else indexif largest == index:# 如果節(jié)點(diǎn)即為最大值則無需繼續(xù)調(diào)整breakelse:# 否則交換節(jié)點(diǎn)與最大值節(jié)點(diǎn)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.計(jì)數(shù)排序

    計(jì)數(shù)排序是一種統(tǒng)計(jì)排序,而不是比較排序了,計(jì)數(shù)排序需要知道待排序數(shù)列的范圍,然后統(tǒng)計(jì)在范圍內(nèi)每個(gè)元素的出現(xiàn)次數(shù),最后按照次數(shù)輸出即是排序結(jié)果。

    動(dòng)圖演示:

    實(shí)現(xiàn)思路

  • 根據(jù)數(shù)列的最大元素計(jì)數(shù)創(chuàng)建空間;
  • 遍歷整個(gè)數(shù)列,統(tǒng)計(jì)每個(gè)元素出現(xiàn)的次數(shù);
  • 遍歷統(tǒng)計(jì)區(qū)間,按照統(tǒng)計(jì)次數(shù)輸出結(jié)果。
  • 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)

    算法分析

    計(jì)數(shù)排序的速度非常快,但是它需要知道數(shù)列的元素范圍,如果數(shù)列元素的范圍非常大,則需要?jiǎng)?chuàng)建非常大的額外空間。

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

    計(jì)數(shù)排序是一個(gè)穩(wěn)定的排序算法。當(dāng)輸入的元素是 n 個(gè) 0到 k 之間的整數(shù)時(shí),時(shí)間復(fù)雜度是O(n+k),空間復(fù)雜度也是O(n+k),其排序速度快于任何比較排序算法。

    當(dāng)k不是很大并且序列比較集中時(shí),計(jì)數(shù)排序是一個(gè)很有效的排序算法。

    2.桶排序

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

    動(dòng)圖演示:

    實(shí)現(xiàn)思路

  • 設(shè)置一個(gè)定量的數(shù)組作為空桶;
  • 遍歷待排序數(shù)列,并把元素一個(gè)一個(gè)放到對應(yīng)的桶里;
  • 對每個(gè)桶內(nèi)的元素進(jìn)行排序;
  • 從非空桶中將排好序的元素拼接起來。
  • 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)

    算法分析

    桶排序的最佳時(shí)間復(fù)雜度為線性時(shí)間O(n),平均時(shí)間復(fù)雜度取決于桶內(nèi)數(shù)據(jù)排序的時(shí)間復(fù)雜度,因?yàn)槠渌糠值臅r(shí)間復(fù)雜度都是O(n),所以桶劃分的越小,各個(gè)桶之間的數(shù)據(jù)越少,排序所用的時(shí)間也會(huì)越少,但相應(yīng)消耗的空間就會(huì)增大。

    3.基數(shù)排序

    基數(shù)排序的實(shí)現(xiàn)原理比較特別,對于數(shù)列中的每個(gè)元素,先按照它的個(gè)位進(jìn)行排序,然后按照十位進(jìn)行排序,以此類推。

    動(dòng)圖演示:

    實(shí)現(xiàn)思路

  • 取得數(shù)列中最大數(shù),計(jì)算其位數(shù);
  • 從最低位開始對數(shù)列的每一個(gè)元素分類;
  • 將每個(gè)分類中的元素按照順序重新組合在一次;
  • 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

    算法分析

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

    三、總結(jié)



    總結(jié)

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

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。