1. 程式人生 > 其它 >(四)排序與搜尋

(四)排序與搜尋

排序與搜尋

排序演算法(Sorting algorithm)是一種能將一串資料依照特定順序進行排序的一種演算法。

穩定排序:序列中任意兩個記錄的關鍵字相同,即ki=kj(i不等於j),若排序之前ki領先於kj,排序後這種關係保持不變的排序稱為穩定排序,否則稱為不穩定排序。

1. 氣泡排序

氣泡排序(Bubble Sort)演算法的運作如下:

  • 比較相鄰的元素。如果第一個比第二個大(升序),叫就換他們兩個;
  • 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。這步做完之後,最後的元素會是最大的數;
  • 針對所有的元素重複以上步驟,除了最後一個;
  • 持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。
def bubble_sort(alist):
    """氣泡排序"""
    #最外層排序輪數n-1次
    for j in range(0,len(alist)-1):  
        #最內層是每一輪的比較
        count=0
         for i in range(0,len(alist)-1-j):
        	if alist[i]>alist[i+1]:
            	alist[i],alist[i+1]=alist[i+1],alist[i]
                count +=1
            if 0==count:
               return
            
 if __name__="__main__" :
    li=[4,9,0,8,3,2]
    bubble_sort(li)
    print(li)
  • 時間複雜度
    • 最優時間複雜度:O(n),表示遍歷一次發現沒有任何可以交換的元素,排序結束
    • 最壞時間複雜度:O(n^2)
    • 穩定性:穩定

2. 選擇排序

每次選擇最小的數,將之放到陣列的最前面

def selection_sort(alist):
    """選擇排序"""
    for j in range(0,len(alist)-1): #j: 0~n-2
   		 min_index=j
    	for i in range(1+j,len(alist)):   #j及前面的元素都已經有序
        	if alist[min_index]>alist[i]
           		min_index=i
    	 alist[j],alist[min_index]=alist[min_index],alist[j] 
  • 時間複雜度
    • 最優時間複雜度:O(n^2)
    • 最壞時間複雜度:O(n^2)
    • 穩定性:不穩定(考慮升序每次選擇最大的情況)

3.插入排序

選擇排序認為左邊的序列有序,然後從右邊的序列中找到最小值,然後放到左邊來,操作的是右邊的無序序列;

插入排序是將右邊無序序列的第一個元素與左邊的序列元素依次比較,看將該元素放到左邊序列的哪一個位置上。

def insert_sort(alist):
    """插入排序"""
   #從右邊的無序序列中取出多少個元素執行這樣的過程
    for j in range(1,len(alist)):  #產生j:1~n-1
    	i=j    #i代表內層迴圈的起始值
        #執行從右邊的無序序列中取出第一個元素,即i位置的元素,然後將其插入到前面的正確位置中
   		 while(i>0):
   			if  alist[i] < alist[i-1]:
       		 	alist[i],alist[i-1]=alist[i-1],alist[i]
        	 	i-=1
        	 else:
            	break
  • 時間複雜度
    • 最優時間複雜度:O(n),(升序排序,序列已經處於升序狀態)
    • 最壞時間複雜度:O(n^2)
    • 穩定性:穩定

4.快速排序

快速排序(Quick Sort),又稱為劃分交換排序,通過一趟排序將要排序的資料分割成獨立的兩部分,其中一部分的所有資料都比另一部分的所有資料都要小,然後再按此方法對這兩部分資料分別進行快速排序,整個排序過程可以遞迴進行,以此達到整個資料變成有序序列。

步驟為:

  1. 從數列中挑選出一個元素,稱為“基準”;
  2. 重新排序數列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺放在基準的後面(相同的數可以放到同一邊),這個稱為分割槽操作。
  3. 遞迴地把小於基準值元素的子數列和大於基準值元素的子數列排序。
def quick_sort(alist,first,last):
    """快速排序"""
     #遞迴演算法的終止條件
    if first>=last:   
        return
    mid_value=alist[first]
    low=first
    high=last
   while low<high: 
    	#high 左移
        while low<high and  alist[high]>=mid_value :
                high-=1
        alist[low]=alist[high]
        
		#low 右移
        while low<high and alist[low]<mid_value:
              low+=1
         alist[high]=alist[low]   
   #從迴圈退出時,low和high相等
   alist[low]=mid_value
    
    #對low左邊的列表執行快速排序
	quick_sort(alist,first,low-1)
    #對low右邊的列表執行快速排序
	quick_sort(alist,low+1,last)	

  • 時間複雜度

    • 最優時間複雜度O(nlogn)

    • 最壞時間複雜度O(n^2)

    • 穩定性:不穩定

5. 希爾排序

希爾排序(Shell Sort)是插入排序的一種。也稱為縮小增量排序,是直接插入排序演算法的一種更高效的改進版本。希爾排序是非穩定排序演算法。希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序演算法排序;隨著增量逐漸減少,每組包含的元素個數越來越多,當增量減至為1時,整個記錄恰好被分為一組,演算法便終止。

def shell_sort(alist):
    """希爾排序"""
    n=len(alist) #n=9
    gap=n//2     #4
    
    #gap變化到0之前,插入演算法執行的次數
    while gap>0:
        #插入演算法與普通的插入演算法的區別就是gap步長
        for j in range(gap,1,n):
        ##j=gap,gap+1,gap+2,……,n-1
            i=j
            while i>0:
                if alist[i]<alist[i-gap]:
                    alist[i],alist[i-gap]=alist[i-gap],alist[i]
                    i-=gap
                else:
                    break
          #縮短gap的長度          
          gap//=2
  • 時間複雜度

    最壞時間複雜度:O(n^2)

    最優時間複雜度:根據步長序列的不同而不同

    穩定性:不穩定

6.歸併排序

歸併排序是採用分治法的一個非常典型的應用。歸併排序的思想就是先遞迴分解陣列,再合併陣列。

將資料分解最小後,然後合併兩個有序陣列,基本思路是比較兩個陣列的最前面的數,誰小了就先取誰,取了後相應的指標就往後移一位。然後再比較,直到一個數組為空,最後把另一個數組的剩餘部分複製過來即可。

def merge_sort(alist):
    """歸併排序"""
    n=len(alist)
    if n<=1:
        return alist
    mid=n//2
    
    #left表示採用歸併排序後形成的有序的新列表
    left_li=merge_sort(alist[:mid])
    right_li=merge_sort(alist[mid:])
    
    #將兩個有序的子序列合併為一個新的整體
    left_pointer,right_pointer=0,0
    result=[]
    while left_pointer<len(left_li) and right_pointer<len(right_li):
        if left_li[left_pointer]<=right_li[right_pointer]:
            result.append(left_li[left_pointer])
            left_pointer+=1
        else:
             result.append(right_li[right_pointer])
              right_pointer+=1
            
    result +=left_li[left_pointer:]    
    result +=right_li[right_pointer:] 
    return result

  • 時間複雜度

    • 最優時間複雜度:O(nlogn)
    • 最壞時間複雜度:O(nlogn)
    • 穩定性:穩定

    結束後產生一個新的列表,產生空間上的開銷

7.堆排序

堆排序是利用堆這種資料結構而設計的一種排序演算法,堆排序是一種選擇排序,它的最壞,最好,平均時間複雜度均為O(nlogn),它也是不穩定排序。

8. 常見排序演算法效率比較

9. 搜尋

搜尋是在一個專案集合中找到一個特定專案的演算法過程。搜尋的幾種常見方法:順序查詢、二分法查詢、二叉樹查詢、雜湊查詢。

二分法查詢

二分法查詢又稱折半查詢,優點是比較次數少,查詢速度快,平均效能好;缺點是要求待查表為有序表。使用的物件涉及到下標,故只能是順序表。

def binary_search(alist,item):
    """二分法查詢----遞迴實現"""
    n=len(alist)
    if n>0:
        mid=n//2
        if alist[mid]==item:
            return True
        elif item<alist[mid]:
            return binary_search(alist[:mid],item)  #注意這裡並不包含mid,遞迴的時候產生了一個新的列表
         else:
            return binary_search(alist[mid+1:],item)  
      return False  

def binary_search_2(alist,item):
    """二分法查詢----非遞迴實現"""
    n=len(alist)
    first=0
    last=n-1
    while first<=last:
        mid=(first+last)//2
        if alist[mid]==item:
             return True
        elif alist[mid]>item:
             last=mid-1
         else:
             first=mid+1
    return False
  • 時間複雜度
    • 最優時間複雜度:O(1)
    • 最壞時間複雜度:O(logn)