排序算法有很多,从时间复杂度比较高的冒泡排序,插入排序,到复杂度低的快速排序,归并排序等,可以说很多很多,冒泡排序,插入排序这种比较好理解,不做详细介绍就给一个简单的例子吧!!!如下。本文主要讨论一下复杂度低的快速排序,归并排序
插入排序:
这里看一下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就是一个列表,它占用的就是额外申请的空间。
这里就给一个C++版本的归并排序把:
void Helper(vector<int>& nums,int left,int right,vector<int> temp){
if (left<right){
int mid = int((left+right)/2);
Helper(nums,left,mid,temp);
Helper(nums,mid+1,right,temp);
Merge(nums,left,mid,right,temp);
}
}
void Merge(vector<int>&nums,int left,int mid,int right,vector<int> temp){
int i =left;
int j =mid+1;
while (i<=mid&&j<=right){
if(nums[i]<=nums[j]){
temp.push_back(nums[i++]);
}
else{
temp.push_back(nums[j++]);
}
}
while(i<=mid){
temp.push_back(nums[i++]);
}
while(j<=right){
temp.push_back(nums[j++]);
}
int t = 0;
while(left<=right){
nums[left++] = temp[t++];
}
}
int main(vector<int>& nums) {
if (nums.size()>1){
vector<int> temp;
Helper(nums,0,nums.size()-1,temp);
}
}
快速排序
核心思想就是选取一个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个,有一个没有通过,即没通过的那一个用例对应的链表非常长,结果导致超时,为什么快速排序会比归并排序慢?
其实当待排序是数组时快排一般是比归并快的,但当是链表时则相反,至于为什么可以参考:
https://stackoverflow.com/questions/7629904/why-is-mergesort-better-for-linked-lists
简单来说就是数组是连续存放的,而链表是分散存储在整个内存中,快排的SubSort(上面帖子中的partitioning)主要访问的就是前面和后面的连续数组元素,访问速度更快,而当是链表时这些优点就会消失。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
一:归并排序的思想就是用已经排好序的数组不断去两两合并,直到合并成一个有序数组,当是数组排序是占用空间复杂度是 O(N),链表时可以降为O(1)
二:快速排序思想就是选一个元素作为分界线,大于该元素的放在左面,小于放在右面形成两个序列,然后分别在两个序列递归 上面的做法
三:当待排序是数组时快排一般是比归并排序快,但当待排序是链表时则相反
注意点:在排序的时候,待排序是数组还是链表是影响我们选择排序算法的一个重要参考点
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
就写到这里吧!跑步去啦,还有一个堆排序待更新,,,,,,,,,,,,,,,