排序大法--------快速排序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就是一个列表,它占用的就是额外申请的空间。

这里就给一个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)

二:快速排序思想就是选一个元素作为分界线,大于该元素的放在左面,小于放在右面形成两个序列,然后分别在两个序列递归          上面的做法

三:当待排序是数组时快排一般是比归并排序快,但当待排序是链表时则相反

注意点:在排序的时候,待排序是数组还是链表是影响我们选择排序算法的一个重要参考点

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------

就写到这里吧!跑步去啦,还有一个堆排序待更新,,,,,,,,,,,,,,,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值