插入、歸併、堆、count、radix、快速排序算法運行時間

Insertion Sort

把最大的元素往右邊一直遷移java

for i in range(len(data)):
		num = data[i]
		j = i -1
		while j>-1 and data[j]>num:
			data[j+1] = data[j]
			j-=1
		data[j+1]=num
複製代碼

耗時分析:外部循環須要n次,內部最壞狀況下也須要n次,總共次數爲\theta(n^2)python

Merge Sort

把一個數組拆成左右兩個部分,一直到不可拆分,而後對左右兩個數組進行合併,合併方式爲,新建左右兩個數組來存儲原有的左右數據,而後使用兩個索引分別指向左右兩個數組,將比較的結果放入原數組便可git

def mergeLR(self,start,mid,end,data):
	i=0
	j=0
	k=start
	# python3 語法 range(start,mid+1) mid+1沒法返回
	l=[ data[v] for v in range(start,mid+1) ]
	r=[ data[v] for v in range(mid+1,end+1) ]
	while i<len(l) and j<len(r):
		if l[i] >= r[j]:
			data[k]=r[j]
			k+=1
			j+=1
		else:
			data[k]=l[i]
			i+=1
			k+=1
	for li in range(i,len(l)):
		data[k]=l[li]
		k+=1
	for rj in range(j,len(r)):
		data[k]=r[rj]
		k+=1

# end 取值 若是給的是數組的長度,那麼在merge的時候須要區分左側merge和右側merge end是否能夠取獲得
# 1分隔到最小的單元再合併
# 2合併左側取到中間值,右側則不獲取
def subMerge(self,start,end,data):
	if start != end :
		mid = (end+start) // 2
		self.subMerge(start,mid,data)
		self.subMerge(mid+1,end,data)
		self.mergeLR(start,mid,end,data)
複製代碼

耗時分析:api

T(n)=2T(n/2)+cn

使用遞歸樹分析以下: 數組

從上到下,每次疊半,那麼層數爲lgn,橫向看每層須要合併的數量之和爲cn,因此總共耗時爲 \theta(nlgn),同時須要的空間爲O(n)

java中的Arrays.sort(T[] a, int fromIndex, int toIndex,Comparator<? super T> c)可使用MergeSort,只是後續再也不使用,轉而使用TimSort安全

Heap Sort

什麼是堆

使用數組來存儲元素,這個數組能夠被看作一個徹底二叉樹bash

徹底二叉樹:全部的葉子節點具備相同的深度,樹的每一層,可能除了最後一層,都是滿的,好比數組a={16,14,10,8,7,9,3,2,4,1},展示形式爲 app

能夠獲得一些簡單的性質:parent(i)=i/2,left(i)=2i,right(i)=2i+1

  • 最大堆:一個節點的值大於它的子節點的值
  • 最小堆:一個節點的值小於它的子節點的值

從一個無序數組構建堆

def buildMaxHeap(self,data):
	length=len(data)
	# python3 語法,-1不會輸出
	for i in range(length//2,-1,-1):
		self.maxHeapify(i,data,length-1)
複製代碼

從length//2+1開始,後面全部的元素都是葉子節點函數

往前查詢的時候,新的元素可能小於它的子節點,須要維持堆的性質性能

def maxHeapify(self,i,data,heapSize):
	lChild = 2*i+1
	rChild = lChild+1
	largeIndex = i
	if lChild <= heapSize and data[lChild] > data[i] :
		largeIndex = lChild
	if rChild <= heapSize and data[rChild] > data[largeIndex]:
		largeIndex = rChild
	if largeIndex != i:
		temp = data[i]
		data[i]=data[largeIndex]
		data[largeIndex]=temp
		self.maxHeapify(largeIndex,data,heapSize)
複製代碼

假設要構建最大堆,當前數組以下

4違反了最大堆的性質,它的左右節點是知足最大堆的,這時須要調整,找到左右節點中最大值的下標,交換父節點的值,這裏就是交換下標2和下標4
再迭代,直到知足堆的性質就能夠中止

構建堆的時間分析

能夠看到首先要遍歷一半的數組,而後有可能面對從頂層到最底層的一次修正操做,而樹的高度爲lgn,那麼時間必定是O(nlgn),能夠更細的來分析:

  • 在葉子節點上一層,最多隻交換1次,所須要的時間是O(1),在葉子節點上的l層,次數爲O(l);
  • 第1層,總共元素爲 n/4個,第2層元素爲n/8個,。。。,第lgn層爲1個,那麼時間爲 n/4(1 c)+n/8(2 c)+...+1(1gn c),令 n/4=2^k,有
c 2^k(1/2^0+2/2^1+3/2^2+4/2^3+..+(k+1)/2^k) = s
c 2^k (1/2^1+2/2^2+3/2^3+4/2^4+..+(k+1)/2^{k+1})=s/2
c 2^k (1/2^0+1/2^1+1/2^2+1/2^3+..+1/2^k-(k+1)/2^{k+1})=s/2
c 2^k (1/2^0+1/2^1+1/2^2+1/2^3+..+1/2^k) \gt s/2
c 2^k \frac{1(1-2^{-k-1})}{1-2^{-1}} \gt s/2
c 2^k 2(1-2^{-k-1}) \gt s/2
c * 2^k * 4 \gt s
c *n \gt s

所以,構建一個堆的時間其實是O(n)

c 表明常量時間,實際可證得是\theta(n)

堆排序

  1. 從無序數組中構建一個最大堆,耗時\theta(n)
  2. 找到最大的元素,和數組最後一個元素交換,此時最大元素在數組的最後面
  3. 移除數組最後一個元素,使得堆大小減1
  4. 對頭部元素執行一次maxHeapify,恢復最大堆的性質
def maxHeap(self):
		data = self.loadData(self.tsf)
		# 1構建堆
		self.buildMaxHeap(data)
		# 2排序
		self.sort(data)


def sort(self,data):
	for i in range(len(data)-1,0,-1):
		temp=data[0]
		data[0]=data[i]
		data[i]=temp
		self.maxHeapify(0,data,i-1)
複製代碼

java中PriorityQueue就是一個堆,往PriorityQueue中添加元素後,維護原有的堆關係:

private void siftUpComparable(int k, E x) {
   Comparable<? super E> key = (Comparable<? super E>) x;
   while (k > 0) {
       int parent = (k - 1) >>> 1;
       Object e = queue[parent];
       if (key.compareTo((E) e) >= 0)//當父節點大於子節點的時候中止
           break;
       queue[k] = e;
       k = parent;
   }
   queue[k] = key;
}
複製代碼

注意它的自己不是線程安全的,線程安全的實現爲PriorityBlockingQueue

至此能夠獲得。堆排的時間是O(nlgn)

Count Sort

將要排序的每個數映射到一個數組的下標,而後按照順序輸出數組的值便可

def sort(self):
	k=self.maxValue+1
	L=[[] for i in range(k)] //建立大小爲k的數組
	n=len(self.data)
	for j in range(n):
		# 保證原有的相同元素順序不會更改 計算下標
		L[self.data[j]].append(self.data[j]) 
	print(L)
	output=[]
	for i in range(k):
		output.extend(L[i])
	print(output)
複製代碼

時間分析:須要建立最大的k個數組,時間爲O(k),而後遍歷n原值,時間爲O(n),最後拼接到原有的輸出值,每次須要判斷L[i]==0?不是則加入,一直須要判斷到k爲止,整個過程當中須要遍歷L,把全部的元素加入新的元素,總共有n個,那麼所耗時爲O(n+k)。所須要的空間也是O(n+k),若是k=O(n),性能不錯

Radix Sort

假設全部要排序的數字都是b進制,那麼這個數字的位數d=\log_nk,k表示最大的數(或者說要排序的數的最大的範圍),排序規則爲,首先查看全部數字的最低位,使用count sort來對最低位進行排序,而後第2位,一直到最高位便可

def sort(self):
	for i in range(1,self.maxDigit+1):
		self.__countSort(i)
		
	
def __countSort(self,n,b=10):
	"""b表明進制,好比10進制,說明,最大數字是10,使用count sort解決"""
	L=[[] for i in range(b)]
	for x in range(len(self.data)):
		v=self.data[x]
		vn=self.significantIntDigit(n,v) //獲取對應位數
		L[vn].append(self.data[x])
	print(L)
	self.data=[]
	for i in range(b):
		self.data.extend(L[i])
		
def significantIntDigit(self,n,value):
		""" 處理10進制 int類型直接使用,與str相比不須要len """
		return(value // 10 **(n-1))%10

複製代碼

時間分析:每次排序使用的是count sort,須要時間爲O(n+b),總共須要循環的次數爲d次,總時間爲O((n+b)d)=O((n+b)\log_bk),一般狀況下,b=\theta(n)時,能取到最小值爲O(n)

快速排序

核心思想:將要排序的數組分紅兩個部分,分別與選定的數據進行比較,使得,執行一遍以後,左邊的數都小於選定的數右邊的數都大於選定的數

def sort(data,start,end):
	if start>=end:
		return
	//使得執行完以後,數組左邊的數都小於選定的數,小於右邊的數
	index=partition(data,start,end)
	sort(data,start,index-1)
	sort(data,index+1,end)
def partition(data,start,end):
    //選定最後一個數做爲標準
	choose=data[end];
	i=start-1;
	j=start;
	while j<end:
		if data[j]<choose:
			i=i+1;
			swap(data,i,j)
		j=j+1
	i=i+1;
	swap(data,i,end)
	return i;
def swap(data,i,j):
	temp=data[i]
	data[i]=data[j];
	data[j]=temp;
複製代碼

圖例: 假設數組原始值以下

首選選定要比較的值爲最後一個,即 choose=data[end]。而後從作往右執行劃分。初始化的時候,全部變量的值分別爲

  • i=-1
  • j= 0
  • choose=4

第一回:data[j]<choose,須要交換,即交換 data[i]=data[0],data[j]=data[0],執行以後以下

第二回:data[j]>choose,不須要執行交換操做,僅j+1
第三回:data[j]>choose,不須要執行交換操做,僅j+1,通過這兩回合,數組以下
第四回:data[j]<choose,須要交換,即交換data[i]=data[1],data[j]=data[3],執行後以下:

此時已經到達最後的位置,執行最後一次的交換,data[i]=data[2]與choose=data[end]交換

能夠看到數組左邊的都小於選中的數據,小於右邊的數據
耗時分析:假設一次劃分使得恰好分半,並且每次如此,可得它的劃分函數爲

T(n)=2T(n/2)+\theta(n)

可得T(n)=nlgn

實際上能夠獲得指望運行時間就是 nlgn。

但若是恰好使得每次的劃分以後結果爲

T(n)=T(n-1)+\theta(n)

此時T(n)=\theta(n^2),這也就是快排的最壞結果

能夠不每次只選定最後一個數,而是隨機選一個數做爲值,而後再比較

相關文章
相關標籤/搜索