1. 程式人生 > >資料結構演算法 - 交換排序(冒泡、冒泡改進 and 快速排序)

資料結構演算法 - 交換排序(冒泡、冒泡改進 and 快速排序)

資料結構演算法 - 交換排序(冒泡、快速排序)

  • 交換排序

    交換排序的核心思想是通過交換位置來對序列進行排序(其實很多排序都會用到交換操作,但它們的思想,或者說思考角度並不是交換,而只是將交換作為實現的一種手段。)

    氣泡排序基本上是大學學習程式設計寫的第一個“程式”,姑且看作演算法的啟蒙,而快速排序是工業界最受歡迎的排序演算法之一,可以看做龍頭老大。這“一頭一尾”可謂將演算法設計中思想的重要性體現的淋漓盡致。

    那麼同樣是基於交換思想的演算法,為什麼氣泡排序的平均時間複雜度是 O

    ( n 2 ) O(n^2) ,而快速排序卻是 O
    ( n log n ) O(n\log n)
    呢?接下來讓我們一探究竟。

  • 氣泡排序

    · 最優時間複雜度: O ( n 2 ) O(n^2)
    · 最壞時間複雜度: O ( n 2 ) O(n^2)
    · 平均時間複雜度: O ( n 2 ) O(n^2)
    · 空間複雜度: O ( 1 ) O(1)
    · 穩定性:穩定排序

    基本思想是:遍歷陣列,對 相鄰 的兩個數進行比較,較大的數項表尾方向移動,而較小的數向表頭方向移動。

    如果將順序表的表頭看作溫泉的池底,表位看作溫泉的頁面,那麼就相當於每次小的元素往水底移動,大的元素向頁面冒出。正所謂“冒泡”…有點牽強…“Talking is cheap,show me the code!”,直接看 Python 程式碼吧。

    def bubble(array):
    
    	n = len(array)
    		# 從前向後兩兩比較,第 i 輪會將第 i 大的元素放到倒數第 i 個位置
    	    for i in range(n):
    	    	# 因為第 i 輪會確定第 i 大的元素,那麼倒數 i 個元素已經有序
    	    	# 所以後面的比較可以截止到倒數第 i 個元素為止
    	    	# 相當於前 n-i 個元素沒有經過排序,而後 i 個元素經過排序
        		for j in range(n-i-1):
        				# 比較相鄰的兩個元素
            			if array[j] > array[j+1]:
            				# 如果第二個數小,則交換位置
                			array[j], array[j+1] = array[j+1], array[j]
    	return array
    

    接下來對氣泡排序耗時進行計算

    from random import randint
    import time
    
    input_data = [randint(0, 20000) for _ in range(10000)]
    
    start = time.clock()
    bubble(input_data)
    end = time.clock()
    
    # 此次排序耗費時間為: 8.10140057378438
    # 通過多次執行後耗時基本在 8(+/-0.2)s
    print(end - start)
    

    還有一個氣泡排序的改進版,在前 n-i 元素有序的情況下不做多餘的比較:

    def bubble_improve(array):
    
    	n = len(array)
    	for i in range(n):
        	# flag = 0 代表沒有發生過交換
        	flag = 0
        	for j in range(n-i-1):
            		if array[j] > array[j+1]:
                		array[j], array[j+1] = array[j+1], array[j]
                		# 如果發生交換那麼 flag 就不為 0 
                		flag += 1
    		# 內層迴圈遍歷的是未排序列,如果遍歷未排序列沒有發生交換,
    		# 則證明沒有排序的序列已經有序,那麼可以直接退出
    		# 這樣的改進可以減少對已經有序的比較次數
        	if not flag:
            		return
    
     	return array
    

    對此改進版本的耗時進行計算:

    from random import randint
    import time
    
    input_data = [randint(0, 20000) for _ in range(10000)]
    
    start = time.clock()
    bubble_improve(input_data)
    end = time.clock()
    
    # 此次排序耗費時間為: 8.810628180255106
    # 通過多次執行後耗時基本在 8.8(+/-0.4)s
    print(end - start)
    

    說是改進,實際上增加了很多次判斷,而判斷操作也是有一定耗時的,所以“改進版”也不一定好~

  • 快速排序

    · 最優時間複雜度: O ( n log n ) O(n\log n)
    · 最壞時間複雜度: O ( n 2 ) O(n^2)
    · 平均時間複雜度: O ( n log n ) O(n\log n)
    · 空間複雜度: O ( l o g n ) O(log n)
    · 穩定性:不穩定排序

    氣泡排序只能與相鄰的元素進行比較,那麼可不可以與非相鄰元素比較呢?答案就是快速排序。

    基本思想:找元素在順序表中的正確位置,對一個順序表使用兩個指標,分別指向表頭與表尾,選取表頭的元素作為被比較元素,移動兩個指標進行比較,兩個指標重合位置即為正確位置。一趟排序後確定被比較元素的正確位置,即此元素的左面所有元素都比他小,右面所有元素都比他大。而後將兩邊的元素作為兩個順序表再使用此方法遞迴實現。

    看 Python 程式碼:

    def sort(array, start, end):
    
    	# 判斷此陣列是否只有一個元素
    	if start >= end:
        		return
    	# start,end 記錄表頭表尾位置,low,high 進行移動
    	low, high = start, end
    	# 選出被比較元素
    	obj = array[end]
    	# 判斷兩指標是否重合(實際上是 high 在 low 的左面一個位置)
    	while low < high:
    		# 移動 low 指標,如果小於被比較元素則繼續移動
            while low < high and array[low] <= obj:
                low += 1
            # 如果大於被比較元素則將這個大的元素賦值給此時 high 所指元素
            #(high 此時指示的位置的元素已經是重複元素,之前已經被換位)
            else:
                array[high] = array[low]
    		# 移動 high指標,如果大於被比較元素則繼續移動
            while low < high and array[high] > obj:
                high -= 1
            # 如果小於被比較元素則將這個小的元素賦值給此時 low 所指元素
            else:
                array[low] = array[high]
    		# 將被比較元素賦值到正確位置
        	array[low] = obj
    	# 根據表頭與表尾位置以及此時被比較元素的正確位置拆分順序表,
    	# 遞迴進行以上相同的操作
    	sort(array, start, low-1)
    	sort(array, low+1, end)
    

    接下來對快速排序耗時進行計算:

    from random import randint
    import time
    
    input_data = [randint(0, 20000) for _ in range(10000)]
    print(input_data)
    
    start = time.clock()
    sort(input_data, 0, len(input_data)-1)
    print(input_data)
    end = time.clock()
    
    # 此次排序耗費時間為: 0.02883855227136317
    # 通過多次執行後耗時基本在 0.02(+/-0.01)s
    print(end - start)
    

    可以看到快速排序的排序速度比氣泡排序快非常多,在 n=10000 時近乎 300 倍!這就是演算法、思想的力量!