快排堆排归排三种排序的比较
目錄
- 快排
- 堆排序
- 歸并排序
- 三種排序的比較
快排
快速排序中最簡單的(遞歸調(diào)用)
注:倒序,和 列表中有大量重復(fù)元素時(shí),時(shí)間復(fù)雜度很大
快排例子
注:快排代碼實(shí)現(xiàn)(類似于二叉樹 遞歸調(diào)用)
時(shí)間復(fù)雜度:O(nlog?n)
空間復(fù)雜度:O(nlog?n)
穩(wěn)定性:不穩(wěn)定
快排代碼
不使用遞歸實(shí)現(xiàn)快排
#! /usr/bin/env python # -*- coding: utf-8 -*- def quick_sort(arr):'''''模擬棧操作實(shí)現(xiàn)非遞歸的快速排序'''if len(arr) < 2:return arrstack = []stack.append(len(arr)-1)stack.append(0)while stack:l = stack.pop()r = stack.pop()index = partition(arr, l, r)if l < index - 1:stack.append(index - 1)stack.append(l)if r > index + 1:stack.append(r)stack.append(index + 1)def partition(arr, start, end):# 分區(qū)操作,返回基準(zhǔn)線下標(biāo)pivot = arr[start]while start < end:while start < end and arr[end] >= pivot:end -= 1arr[start] = arr[end]while start < end and arr[start] <= pivot:start += 1arr[end] = arr[start]# 此時(shí)start = endarr[start] = pivotreturn startlst = [1,3,5,7,9,2,4,6,8,10] quick_sort(lst) print lst # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]快排簡易實(shí)現(xiàn)
#! /usr/bin/env python # -*- coding: utf-8 -*- def quick(list):if len(list) < 2:return listtmp = list[0] # 臨時(shí)變量 可以取隨機(jī)值left = [x for x in list[1:] if x <= tmp] # 左列表right = [x for x in list[1:] if x > tmp] # 右列表return quick(left) + [tmp] + quick(right)li = [4,3,7,5,8,2] print quick(li) # [2, 3, 4, 5, 7, 8]#### 對(duì)[4,3,7,5,8,2]排序 ''' [3, 2] + [4] + [7, 5, 8] # tmp = [4] [2] + [3] + [4] + [7, 5, 8] # tmp = [3] 此時(shí)對(duì)[3, 2]這個(gè)列表進(jìn)行排序 [2] + [3] + [4] + [5] + [7] + [8] # tmp = [7] 此時(shí)對(duì)[7, 5, 8]這個(gè)列表進(jìn)行排序 '''快排的原理
快排思路詳解
快排與冒泡時(shí)間復(fù)雜度對(duì)比
快排最壞時(shí)間復(fù)雜度為何為O(n2)
2.如果用數(shù)畫出來,得到的將會(huì)是一棵單斜樹,也就是說所有所有的節(jié)點(diǎn)只有左(右)節(jié)點(diǎn)的樹;平均時(shí)間復(fù)雜度O(n*logn)
堆排序
堆的定義
若設(shè)二叉樹的深度為h,除第 h 層外,其它各層 (1~h-1) 的結(jié)點(diǎn)數(shù)都達(dá)到最大個(gè)數(shù)
第 h 層所有的結(jié)點(diǎn)都連續(xù)集中在最左邊,這就是完全二叉樹
一個(gè)高度為h的完全二叉樹最多有 2n -1 個(gè)節(jié)點(diǎn)
根為 i 號(hào)節(jié)點(diǎn),左孩子 為 2i、 右孩子為 2i+1,父親節(jié)點(diǎn) (i – 1) / 2
一個(gè)滿二叉樹 第 m層節(jié)點(diǎn)個(gè)數(shù) 等于 2m-1 個(gè)
推導(dǎo)一個(gè)h層的滿二叉樹為何 有 2h -1 個(gè)節(jié)點(diǎn)
調(diào)長定義(節(jié)點(diǎn)的左右子樹都是堆但自己不是堆)
- 調(diào)長圖解
- 調(diào)長原理
構(gòu)造堆:從最后一個(gè)有孩子的父親開始
構(gòu)造堆
#! /usr/bin/env python # -*- coding: utf-8 -*- def sift(data, low, high):''' 構(gòu)造堆 堆定義:堆中某節(jié)點(diǎn)的值總是不大于或不小于父節(jié)點(diǎn)的值:param data: 傳入的待排序的列表:param low: 需要進(jìn)行排序的那個(gè)小堆的根對(duì)應(yīng)的號(hào):param high: 需要進(jìn)行排序那個(gè)小堆最大的那個(gè)號(hào):return:'''i = low #i最開始創(chuàng)建堆時(shí)是最后一個(gè)有孩子的父親對(duì)應(yīng)根的號(hào)j = 2 * i+ 1 #j子堆左孩子對(duì)應(yīng)的號(hào)tmp = data[i] #tmp是子堆中原本根的值(拿出最高領(lǐng)導(dǎo))while j <= high: #只要沒到子堆的最后(每次向下找一層) #孩子在堆里# if j < high and data[j] < data[j + 1]:if j + 1 <= high and data[j] < data[j + 1]: #如果有右孩紙,且比左孩子大j += 1if tmp < data[j]: #如果孩子還比子堆原有根的值tmp大,就將孩子放到子堆的根data[i] = data[j] #孩子成為子堆的根i = j #孩子成為新父親(向下再找一層)j = 2 * i + 1 #新孩子 (此時(shí)如果j<=high證明還有孩,繼續(xù)找)else:break #如果能干就跳出循環(huán)就會(huì)流出一個(gè)空位data[i] = tmp #最高領(lǐng)導(dǎo)放到父親位置def heap_sort(data):'''調(diào)整堆'''n = len(data)# n//2-1 就是最后一個(gè)有孩子的父親那個(gè)子堆根的位置for i in range(n // 2 - 1, -1, -1): #開始位置,結(jié)束位置, 步長 這個(gè)for循環(huán)構(gòu)建堆# for循環(huán)輸出的是: (n // 2 - 1 ) ~ 0 之間的數(shù)sift(data, i , n-1) # i是子堆的根,n-1是堆中最后一個(gè)元素data = [20,50,20,60,70,10,80,30,40] heap_sort(data) print data # [80, 70, 20, 60, 50, 10, 20, 30, 40]- 在構(gòu)造有序堆時(shí),我們開始只需要掃描一半的元素(n/2-1 ~ 0)即可,為什么?
- 因?yàn)?n/2-1)~0的節(jié)點(diǎn)才有子節(jié)點(diǎn),如圖1,n=8,(n/2-1) = 3 即3 2 1 0這個(gè)四個(gè)節(jié)點(diǎn)才有子節(jié)點(diǎn)
- 所以代碼4~6行for循環(huán)的作用就是將3 2 1 0這四個(gè)節(jié)點(diǎn)從下到上,從右到左的與它自己的子節(jié)點(diǎn)比較并調(diào)整最終形成大頂堆,過程如下:
- 第一次for循環(huán)將節(jié)點(diǎn)3和它的子節(jié)點(diǎn)7 8的元素進(jìn)行比較,最大者作為父節(jié)點(diǎn)(即元素60作為父節(jié)點(diǎn))
- 第二次for循環(huán)將節(jié)點(diǎn)2和它的子節(jié)點(diǎn)5 6的元素進(jìn)行比較,最大者為父節(jié)點(diǎn)(元素80作為父節(jié)點(diǎn))
- 第三次for循環(huán)將節(jié)點(diǎn)1和它的子節(jié)點(diǎn)3 4的元素進(jìn)行比較,最大者為父節(jié)點(diǎn)(元素70作為父節(jié)點(diǎn))
- 第四次for循環(huán)將節(jié)點(diǎn)0和它的子節(jié)點(diǎn)1 2的元素進(jìn)行比較,最大者為父節(jié)點(diǎn)(元素80作為父節(jié)點(diǎn))
注:元素20和元素80交換后,20所在的節(jié)點(diǎn)還有子節(jié)點(diǎn),所以還要再和它的子節(jié)點(diǎn)5 6的元素進(jìn)行比較,這就是28行代碼 i = j 的原因
- 至此有序堆已經(jīng)構(gòu)造好了!如上面右圖
調(diào)整堆
堆頂元素80和尾40交換后–>調(diào)整堆
堆頂元素70和尾30交換后–>調(diào)整堆
堆頂元素60尾元素20交換后–>調(diào)整堆
其他依次類推,最終已排好序的元素如下
堆排序代碼實(shí)現(xiàn)
# !/usr/bin/env python # -*- coding:utf-8 -*- import randomdef sift(data, low, high):''' 構(gòu)造堆 堆定義:堆中某節(jié)點(diǎn)的值總是不大于或不小于父節(jié)點(diǎn)的值:param data: 傳入的待排序的列表:param low: 需要進(jìn)行排序的那個(gè)小堆的根對(duì)應(yīng)的號(hào):param high: 需要進(jìn)行排序那個(gè)小堆最大的那個(gè)號(hào):return:'''root = low # root最開始創(chuàng)建堆時(shí)是最后一個(gè)有孩子的父親對(duì)應(yīng)根的號(hào)child = 2 * root + 1 # child子堆左孩子對(duì)應(yīng)的號(hào)tmp = data[root] # tmp是子堆中原本根的值(拿出最高領(lǐng)導(dǎo))while child <= high: # 只要沒到子堆的最后(每次向下找一層) #孩子在堆里if child + 1 <= high and data[child] < data[child + 1]: # 如果有右孩紙,且比左孩子大child += 1if tmp < data[child]: # 如果孩子還比子堆原有根的值tmp大,就將孩子放到子堆的根data[root] = data[child] # 孩子成為子堆的根root = child # 孩子成為新父親(向下再找一層)child = 2 * root + 1 # 新孩子 (此時(shí)如果child<=high證明還有孩,繼續(xù)找)else:break # 如果能干就跳出循環(huán)就會(huì)流出一個(gè)空位data[root] = tmp # 最高領(lǐng)導(dǎo)放到父親位置def heap_sort(data):'''調(diào)整堆'''n = len(data)''' n//2-1 就是最后一個(gè)有孩子的父親那個(gè)子堆根的位置 '''for i in range(n // 2 - 1, -1, -1): # 開始位置,結(jié)束位置, 步長 這個(gè)for循環(huán)構(gòu)建堆# for循環(huán)輸出的是: (n // 2 - 1 ) ~ 0 之間的數(shù)sift(data, i, n - 1) # i是子堆的根,n-1是堆中最后一個(gè)元素# 堆建好了,后下面就是挨個(gè)出數(shù)for i in range(n - 1, -1, -1): # i指向堆的最后 這個(gè)for循環(huán)出數(shù)然后,調(diào)長調(diào)整堆# for循環(huán)輸出的是 : n-1 ~ 0之間所有的數(shù),n-1就是這個(gè)堆最后那個(gè)數(shù)的位置data[0], data[i] = data[i], data[0] # 將堆的第一個(gè)和最后一個(gè)值調(diào)換位置(將最大數(shù)放到最后)sift(data, 0, i - 1) # 將出數(shù)后的部分重新構(gòu)建堆(調(diào)長)data = list(range(100)) random.shuffle(data) # 將有序列表打亂 heap_sort(data) print(data)初始化建堆過程時(shí)間:O(n) 公式推導(dǎo)
參考博客:https://www.cnblogs.com/GHzz/p/9635161.html
說明:建堆時(shí)間復(fù)雜度指初始化堆需要調(diào)整父節(jié)點(diǎn)和子節(jié)點(diǎn)順序次數(shù)
推導(dǎo)初始化對(duì)時(shí)間復(fù)雜度:O(n)
''' 假設(shè)高度為:k ''' #### 1、推倒第i層的總時(shí)間:s = 2^( i - 1 ) * ( k - i ) # 說明:如果在最差的條件下,就是比較次數(shù)后還要交換;因?yàn)檫@個(gè)是常數(shù),所以提出來后可以忽略; ''' 1. 2^( i - 1):表示該層上有多少個(gè)元素 2. ( k - i):表示子樹上要下調(diào)比較的次數(shù):第一層節(jié)點(diǎn)需要調(diào)整(h-1)次,最下層非葉子節(jié)點(diǎn)需要調(diào)整1次。 3. 推倒倒數(shù)第1層下調(diào)次數(shù):s = 2^( i - 1 ) * 0 倒數(shù)第2層下調(diào)次數(shù):s = 2^( i - 1 ) * 1倒數(shù)第3層下調(diào)次數(shù):s = 2^( i - 1 ) * 2倒數(shù)第i層下調(diào)次數(shù):s = 2^( i - 1 ) * ( k - i ) '''#### 2、一次新建堆總時(shí)間:S = n - longn -1 # 根據(jù)1中公式帶人推倒 # S = 2^(k-2) * 1 + 2^(k-3)*2.....+2*(k-2)+2^(0)*(k-1) ===> 因?yàn)槿~子層不用交換,所以i從 k-1 開始到 1; ''' S = 2^(k-2) * 1 + 2^(k-3)*2.....+2*(k-2)+2^(0)*(k-1) # 等式左右乘上2,然后和原來的等式相減,就變成了: S = 2^(k - 1) + 2^(k - 2) + 2^(k - 3) ..... + 2 - (k-1) S = 2^k -k -1 # 又因?yàn)閗為完全二叉樹的深度,所以 (2^(k-1)) <= n < (2^k - 1 ) # 兩邊同時(shí)對(duì)2取對(duì)數(shù),簡單可得 k = logn # 實(shí)際計(jì)算得到應(yīng)該是 log(n+1) < k <= logn 綜上所述得到:S = n - longn -1,所以時(shí)間復(fù)雜度為:O(n) '''堆排序時(shí)間:O(nlogn) 公式推導(dǎo)
- 推導(dǎo)方法1
循環(huán) n -1 次,每次都是從根節(jié)點(diǎn)往下循環(huán)查找,所以每一次時(shí)間是 logn,總時(shí)間:logn(n-1) = nlogn - logn
- 推導(dǎo)方法2
歸并排序
歸并原理圖(遞歸調(diào)用)
時(shí)間復(fù)雜度:O(nlog?n)
空間復(fù)雜度:O(1)
穩(wěn)定性:穩(wěn)定
歸并排序代碼
#! /usr/bin/env python # -*- coding: utf-8 -*- def merge(li, low, mid, high):''':param li: 帶排序列表:param low: 列表中第一個(gè)元素下標(biāo),一般是:0:param mid: 列表中間位置下標(biāo):param high: 列表最后位置下標(biāo):return:'''i = lowj = mid + 1ltmp = []while i <= mid and j <= high:if li[i] < li[j]:ltmp.append(li[i])i += 1else:ltmp.append(li[j])j += 1while i <= mid:ltmp.append(li[i])i += 1while j <= high:ltmp.append(li[j])j += 1li[low:high+1] = ltmpdef mergesort(li, low, high):if low < high:mid = (low + high) // 2 #獲取列表中間的索引下標(biāo)mergesort(li, low, mid) #先分解mergesort(li, mid+1, high)merge(li, low, mid, high) #然后合并data = [10,4,6,3,8,2,5,7] mergesort(data, 0 , len(data) -1) print(data) # [2, 4, 6, 8, 10, 12, 14, 16, 18]三種排序的比較
- 三種排序算法時(shí)間復(fù)雜度都是( O(nlogn) )
- 一般情況下,就運(yùn)行時(shí)間而言
快速排序 < 歸并排序 < 堆排序 - 三種排序算法的缺點(diǎn)
快速排序: 極端情況下排序效率低( O(n2) )
歸并排序: 需要額外內(nèi)存開銷(需要新建一個(gè)列表放排序的元素)
堆排序: 在快的排序算法中相對(duì)較慢,堆排序最穩(wěn)定
詳細(xì)鏈接參考這個(gè)
總結(jié)
以上是生活随笔為你收集整理的快排堆排归排三种排序的比较的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 冒泡排序选择排序插入排序
- 下一篇: 数据结构栈队列链表数组