排序大法--------快速排序VS歸併排序+實踐
排序演算法有很多,從時間複雜度比較高的氣泡排序,插入排序,到複雜度低的快速排序,歸併排序等,可以說很多很多,氣泡排序,插入排序這種比較好理解,不做詳細介紹就給一個簡單的例子吧!!!如下。本文主要討論一下複雜度低的快速排序,歸併排序
插入排序:
這裡看一下leetcode147
Definition for singly-linked list. # class ListNode(object): # def __init__(self, x): # self.val = x # self.next = None class Solution(object): def insertionSortList(self, head): """ :type head: ListNode :rtype: ListNode """ if not head: return head List = [] while head: List.append(head.val) head = head.next Len = len(List) for i in range(1, Len): tmp = List[i] index = i for j in range(i-1, -1, -1): if List[j] > tmp: List[j+1] = List[j] index = j List[index] = tmp return List
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
能夠有O(n lgn)時間複雜度的演算法為,快速排序,堆排序,歸併排序,三者的空間複雜度分別為O(1), O(N),O(N) ,但也不是絕對的,比如有的時候歸併排序的空間複雜度並不是O(N),具體見下面分析。
歸併排序
劃分+合併(先劃分成一個個有序的陣列,然後將其兩兩進行合併,最後合併為一個有序陣列)
這裡看一下leetcode148的例子:
# Definition for singly-linked list. # class ListNode(object): # def __init__(self, x): # self.val = x # self.next = None class Solution(object): def sortList(self, head): """ :type head: ListNode :rtype: ListNode """ if not head or not head.next: return head pre, slow, fast = head, head, head while fast and fast.next: pre = slow slow = slow.next fast = fast.next.next pre.next = None l1 = self.sortList(head) l2 = self.sortList(slow) return self.mergeTwoLists(l1, l2) def mergeTwoLists(self, l1, l2): head = ListNode(0) move = head if not l1: return l2 if not l2: return l1 while l1 and l2: if l1.val < l2.val: move.next = l1 l1 = l1.next else: move.next = l2 l2 = l2.next move = move.next move.next = l1 if l1 else l2 return head.next
注意這裡的劃分採用快慢指標,即fast每次走兩步,slow每次走一步,當fast到尾部時,slow恰好在當前陣列的一半的位置
pre在這裡就是為了截斷。三個指標初始化均為head
具體來說就是假如原始為【1,4,34,3,6,2,5】
當fast調到5的時候結束,那麼此時slow在3的位置,pre在34的位置
l1 = self.sortList(head)
l2 = self.sortList(slow)
這裡的l1和l2相當於前一半和後一半,因為
pre.next = None
所以導致從head開始,最多到達34,即前半部分是【1,4,34】
l2是從slow開始到最後即後半部分是【3,6,2,5】
mergeTwoLists合併部分就很簡單啦,需要注意的是
歸併排序按理來說需要額外空間N,但是因為這裡是指標,所以直接可以使用指標來指,並不需要額外空間具體來說就是
head = ListNode(0)
部分,注意這裡O(N)的額外空間,而僅僅是O(1),因為只是ListNode(0)的申請使用了一個空間,後面的它是使用指標去指向已經存在的資料,並沒有去申請新的空間儲存資料
所以如果原始資料是連結串列的話,歸併排序空間複雜度可以降為1,如果原始資料是陣列的話,這裡就需要重新申請一個和原陣列同樣大小的資料去儲存資料,空間複雜度就是O(N)啦
什麼意思呢?比如mergeTwoLists輸入引數是l1和l2,但是他們是陣列,那麼這裡head就需要l1+l2的空間啦,簡單虛擬碼大概就是:
def mergeTwoLists(self, l1, l2):
if not l1: return l2
if not l2: return l1
i=0
j=0
head = []
while i<len(l1) and j<len(l2):
if l1[i]<l2[j]:
head.append(l1[i])
i+=1
else:
head.append(l2[j])
j+=1
if i<len(l1):
head.append(l1[i,:])
if j<len(l2):
head.append(l2[j,:])
return head
可以看出head就是一個列表,它佔用的就是額外申請的空間。
快速排序
核心思想就是選取一個key,然後看序列中的元素,大於該key的放在右邊,小於的放在左面,所以最後得到以該key為分界線的左右兩個陣列,然後分別再在兩個子陣列中選取各自的key重複進行上面的操作,不斷遞迴下去,,,,
這裡的key選擇可以就簡單的將陣列的第一個元素作為key
簡單的例子就是:
quicksorts函式的作用就是每一個子陣列再利用key進行分組,返回的就是分界線
def quicksorts(ints, left, right):
key = ints[left]
while left < right:
while left < right and ints[right] >= key:
right -= 1
if left < right:
ints[left],ints[right] = ints[right],ints[left]
else:
break
while left < right and ints[left] < key:
left += 1
if left < right:
ints[right], ints[left] = ints[left], ints[right]
else:
break
return left
def quick_sort_standord(ints,left,right):
if left < right:
key_index = quicksorts(ints,left,right)
quick_sort_standord(ints,left,key_index)
quick_sort_standord(ints,key_index+1,right)
if __name__ == '__main__':
num = [5,6,1,4,3,4]
quick_sort_standord(ints, 0, len(num) - 1)
print ints
上面舉的例子待排序是一個數組,如果待排序是一個連結串列,還是以leetcode上面那道題為例, 那麼可以這樣:
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def sortList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
if head==None:
return head
endpoint = head
while endpoint.next:
endpoint = endpoint.next
self.QuickSort(head,endpoint)
return head
def SubSort(self,begin,end):
key = begin.val
temp_mix = begin
temp_cur = begin.next
while temp_cur!=end.next:
if temp_cur.val<key:
temp_mix = temp_mix.next
temp_mix.val ,temp_cur.val =temp_cur.val,temp_mix.val
temp_cur = temp_cur.next
temp_mix.val,begin.val = begin.val,temp_mix.val
return temp_mix
def QuickSort(self,begin,end):
if begin!=end:
key = self.SubSort(begin,end)
self.QuickSort(begin,key)
if key.next!=None:
self.QuickSort(key.next,end)
先說一下結論,這裡其實並沒有完全通過,在16個測試用例中,通過了15個,有一個沒有通過,即沒通過的那一個用例對應的連結串列非常長,結果導致超時,為什麼快速排序會比歸併排序慢?
其實當待排序是陣列時快排一般是比歸併快的,但當是連結串列時則相反,至於為什麼可以參考:
簡單來說就是陣列是連續存放的,而連結串列是分散儲存在整個記憶體中,快排的SubSort(上面帖子中的partitioning)主要訪問的就是前面和後面的連續陣列元素,訪問速度更快,而當是連結串列時這些優點就會消失。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
一:歸併排序的思想就是用已經排好序的陣列不斷去兩兩合併,直到合併成一個有序陣列,當是陣列排序是佔用空間複雜度是 O(N),連結串列時可以降為O(1)
二:快速排序思想就是選一個元素作為分界線,大於該元素的放在左面,小於放在右面形成兩個序列,然後分別在兩個序列遞迴 上面的做法
三:當待排序是陣列時快排一般是比歸併排序快,但當待排序是連結串列時則相反
注意點:在排序的時候,待排序是陣列還是連結串列是影響我們選擇排序演算法的一個重要參考點
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
就寫到這裡吧!跑步去啦,還有一個堆排序待更新,,,,,,,,,,,,,,,