十大排序

一、十大排序总结

https://www.cnblogs.com/guoyaohua/p/8600214.html总结得很好。

1、原地排序:快速排序、堆排序、插入排序、冒泡排序、希尔排序、直接选择排序

2、非原地排序:归并排序、计数排序、基数排序、桶排序

3、对有序的序列排序:冒泡、直接插入

4、对无序的序列排序:快排

5、对大数据排序:O(nlog(n))都比较好,快排、堆排、归并【归并稳定、快排和堆排不稳定】

6、

7、常见的快速排序、归并排序、堆排序、冒泡排序等属于比较排序

计数排序、基数排序、桶排序则属于非比较排序

8、算法复杂度与初始状态无关的排序算法有:选择排序、堆排、归并、基数(一龟选友)

元素总比较次数与初始状态无关的有:选择排序基数排序

元素总移动次数与初始状态无关的有:归并排序基数排序

9、稳定的排序:冒泡、插入、归并、计数、桶、基数

【稳定性:相同的数字其索引位置也有序,即且在原本的列表中 出现在 之前,在排序过的列表中 也将会是在 之前。】

不稳定的排序:选择、希尔、快排、堆排(一好东西需要快速 选择share

二、冒泡排序:

比较相邻的两项,交换顺序排错的项。每对列表实行一次遍历,就有一个最大项排在了正确的位置。第二次遍历开始时,最大的数据项已经归位。

1、未改进的冒泡排序:两个循环

def bubbleSort(nums):
    for num in range(len(nums)-1,0,-1):
        for i in range(num):
            if nums[i]>nums[i+1]:
                temp=nums[i+1]
                nums[i+1]=nums[i]
                nums[i]=temp

2、改进的冒泡排序:

只要在某一次排完序之后顺序是正确的,那么就停止排序。【设置一个标记,如果顺序正确就停止遍历】

def bubbleSort(nums):
    n=len(nums)-1
    exit=True
    while n>0 and exit:
        exit=False
        for i in range(n):
            if nums[i]>nums[i+1]:
                exit=True
                temp=nums[i+1]
                nums[i+1]=nums[i]
                nums[i]=temp
        n-=1

三、选择排序

就是最普通那种思路,找到最大的往最右换,接着找到次大的再往右换。

def selectionSort(nums):
    n=len(nums)-1
    for i in range(n,0,-1):
        maxIndex=0
        for j in range(i+1):
            if nums[j]>nums[maxIndex]:
                maxIndex=j
        temp=nums[i]
        nums[i]=nums[maxIndex]
        nums[maxIndex]=temp

四、插入排序

从前往后来遍历数,新的数据和前面的已经排好的子表进行比较插入到正确的位置。

def insertSort(nums):
    for i,num in enumerate(nums):
        position=i     
        while position>0 and nums[position-1]>num:
            nums[position]=nums[position-1]
            position=position-1
        nums[position]=num
def insertsort(arr):
    if not arr:
        return arr
    for i in range(len(arr)):
        for j in range(i):
            if arr[j] > arr[i]:
                arr[i],arr[j] = arr[j],arr[i]
    return arr


if __name__ =='__main__':
    arr = [43,4,12,4,6,9]

 

五、希尔排序

希尔排序是简单插入排序的改进,shell排序是相当于把一个数组中的所有元素分成几部分来排序;先把几个小部分的元素排序好,让元素大概有个顺序,最后再全面使用插入排序。一般最后一次排序都是和上面的插入排序一样的;

对于下标为【0】【3】【6】【9】的数据即【5】【12】【9】【7】来分割,

【5】【12】【9】【7】为一组,【16】【3】【17】为一组,【20】【8】【19】为一组。三组进分别行直接插入排序进行交换,结果为

【5】【7】【9】【12】,【3】【16】【17】,【8】【19】【20】,三组数据是在原本的位置即【5】【3】【8】【7】【16】【19】【9】【17】【20】【12】

 

代码:

def shell_sort(lists):
    if not lists:
        return lists
    n = len(lists)
    gap = n // 2
    while gap > 0:
        ###以下是插入排序
        for i in range(gap, n):
            temp = lists[i]
            preIndex = i - gap
            while preIndex >= 0 and lists[preIndex] > temp:
                lists[preIndex + gap] = lists[preIndex]
                preIndex -= gap
            lists[preIndex + gap] = temp

        gap //= 2
    return lists


if __name__ == '__main__':
    arr = [3, 5, 15, 26, 2, 27, 4, 19, 36, 50, 12]
    res = shell_sort(arr)
    print(res)

 

六、快速排序

快速排序的基本思想是,通过一轮的排序将序列分割成独立的两部分,其中一部分序列的关键字(这里主要用值来表示)均比另一部分关键字小。继续递归对长度较短的序列进行同样的分割,最后到达整体有序。在排序过程中,由于已经分开的两部分的元素不需要进行比较,故减少了比较次数,降低了排序时间。

平均时间:O(nlogn)

平均空间:O(nlogn)

最好时间:O(nlogn)

 

 
最坏时间:O(n2) 最坏空间:O(n)

 最坏情况的发生是在数组有序且为完全逆序时,此时快排退化成冒泡;

解决方法:随机取主元或者取中位数,三路快排(解决大量重复数据)

def quick_sort(arr,l,r):
    if l < r:
        q = partition(arr,l,r)
        quick_sort(arr,l,q-1)
        quick_sort(arr,q+1,r)
def partition(arr,l,r):
    x = arr[r]
    i = l-1
    for j in range(l,r):
        if arr[j] < x:
            i += 1
            arr[i],arr[j] = arr[j],arr[i]
    arr[i+1] , arr[r] = arr[r] ,arr[i+1]
    return i+1

 随机选择主元,二路快排

import random
def quick_sort(arr , l , r):
    if len(arr) <= 1:
        return arr
    if l < r:
        p = partition(arr, l , r)
        quick_sort(arr, l , p - 1)
        quick_sort(arr, p + 1 , r)
def partition(arr,l,r):
    if l < r:
        ###randint是生成l<=index<=r的随机整数
        ##随机生成主元
        index = random.randint(l,r)
        x = arr[index]
        arr[r],arr[index] = arr[index],arr[r]
        i = l - 1
        for j in range(l,r):
            if arr[j] < x:
                i += 1
                arr[i] , arr[j] = arr[j] , arr[i]
        arr[i+1],arr[r] = arr[r],arr[i+1]
        return i + 1
arr = [1,3,4,6,2,7]
quick_sort(arr , 0 , len(arr)-1)
print(arr)
            

 

双路快排和三路快排:

三路快排优点:解决了近乎有序的数组和有大量重复数组的元素排序问题

 

三路快排的代码:

import random
#三路排序
def partition(arr,pivot,l,r):
    if not arr:
        return arr
    small ,index , big = l-1 , l , r+1
    while index != big:
        if arr[index] < pivot:
            small += 1
            arr[small] , arr[index] = arr[index] , arr[small]
            index += 1
        elif arr[index] == pivot:
            index += 1
        else:
            big -= 1
            arr[big] , arr[index] = arr[index] , arr[big]
    return small,big
#快排,随机选取主元
def quick_sort(arr,l,r):
    if len(arr) <= 1:
        return arr
    pivot = random.sample(arr,1)[0]
    if l < r:
        l1,r1 = partition(arr,pivot,l,r)
        quick_sort(arr,l,l1)
        quick_sort(arr,r1,r)
    return arr

if __name__=='__main__':
    arr = [9,0,1,3,4,3,1]
    res = quick_sort(arr,0,len(arr)-1)
    print(res)

if __name__=='__main__':
    arr = [9,0,1,3,4,3,1]
    res = quick_sort(arr,0,len(arr)-1)
    print(res)

 

七、堆排序

http://www.cnblogs.com/0zcl/p/6737944.html

先建立堆,然后排序。

满足堆的性质:子结点的键值或索引总是小于(或者大于)它的父节点。

7.1 算法描述

    调整堆;

    循环(条件:堆不为空){

      取出堆顶元素;

      将最后一个元素移动到堆顶位置;

      调整使之再次成为堆;

    }

例子:

给定一个列表array=[16,7,3,20,17,8],对其进行堆排序。

 一、将列表中的数值构建成一个完全二叉树:

 

二、调整:初始化大顶堆,即将列表中的最大值放在堆顶。并且构建成堆。即父>=子。 初始化大顶堆时 是从最后一个有子节点开始往上调整最大堆。

三、交换:堆顶元素和堆尾【最后一个元素】交换,此时可以砍掉堆尾元素了,因为最大值已经排好序。而堆顶元素(最大数)与堆最后一个数交换后,需再次调整成大顶堆,此时是从上往下调整的。

四、重复步骤二、三,直到结束。即【重新调整剩余的堆并交换堆顶和堆尾的数【除了20之外的数】】。

(1)调整(2)交换

(1)调整(2)交换

(1)调整(2)交换

 

(1)调整(2)交换

代码

#堆排序算法
#全局变量
n = 0
def heap_sort(arr):
    global n
    n = len(arr)
    if n <= 1 :
        return arr
    #  1、构建一个最大堆
    buildMaxHeap(arr)
    # 2、循环将堆首(最大值)与末位交换,然后再重新调整最大堆
    while n > 0:
        arr[0] , arr[n-1] = arr[n-1] , arr[0]
        n -= 1
        adjustHeap(arr,0)

    return arr

#建立最大堆
def buildMaxHeap(arr):
    global n
    for i in range(n//2-1,-1,-1):
        adjustHeap(arr,i)

#调整成为最大堆
def adjustHeap(arr,i):
    global n
    maxIndex = i
    #如果有左子树,且左子树大于父节点,则将最大指针指向左子树
    if i * 2 < n and arr[i * 2] > arr[maxIndex]:
        maxIndex = i * 2
    # 如果有右子树,且右子树大于父节点,则将最大指针指向右子树
    if i * 2 + 1 < n and arr[i*2 + 1] > arr[maxIndex]:
        maxIndex = i * 2 + 1
    # 如果父节点不是最大值,则将父节点与最大值交换,并且递归调整与父节点交换的位置。
    if maxIndex != i:
        arr[maxIndex] , arr[i] = arr[i] , arr[maxIndex]
        adjustHeap(arr,maxIndex)

if __name__ == '__main__':
    #自己建堆
    arr2 = [3, 5, 15, 26, 2, 27, 4, 19, 36, 50, 12]
    heap_sort(arr2)
    print(arr2)  #[2, 3, 4, 5, 12, 15, 19, 26, 27, 36, 50]

 

 

八、归并排序

  • 把长度为n的输入序列分成两个长度为n/2的子序列;
  • 对这两个子序列分别采用归并排序;
  • 将两个排序好的子序列合并成一个最终的排序序列。
#归并排序
def merge(left,right):
    if not left:
        return right
    if not right:
        return left
    arr = []
    i,j = 0,0
    while i < len(left) and j < len(right):
        if left[i] >= right[j]:
            arr.append(right[j])
            j += 1
        else:
            arr.append(left[i])
            i += 1
    arr += left[i:]
    arr += right[j:]
    return arr

#合并两个子序列
def merge_sort(arr):
    if not arr or len(arr) == 1:
        return arr
    mid = len(arr) // 2
    left = merge_sort(arr[:mid])
    right = merge_sort(arr[mid:])
    return merge(left,right)

if __name__=="__main__":
    arr = [3, 5, 15, 26, 2, 27, 4, 19, 36, 50, 12]
    res = merge_sort(arr)
    print(res)

 

排序中topK那点事(转)

算法必学:经典的 Top K 问题

问题描述:有 N (N>1000000)个数,求出其中的前K个最小的数(又被称作topK问题)。

 

这类问题似乎是备受面试官的青睐,相信面试过互联网公司的同学都会遇到这来问题。下面由浅入深,分析一下这类问题。

 

思路1:【全部排序】

  最基本的思路,将N个数进行完全排序,从中选出排在前K的元素即为所求。有了这个思路,我们可以选择相应的排序算法进行处理,快速排序,堆排序和归并排序都能达到O(NlogN)的时间复杂度。当然,这样的答案也是无缘offer的。

 

思路2:【只对K部分排序,冒泡或选择】

可以采用数据池的思想,选择其中前K个数作为数据池,后面的N-K个数与这K个数进行比较,若小于其中的任何一个数,则进行替换。这种思路的算法复杂度是O(N*K),当答出这种算法时,似乎离offer很近了。

 

有没有算法复杂度更低的方法呢?

 

从思路2可以想到,剩余的N-K个数与前面K个数比较的时候,是顺序比较的,算法复杂度是K。怎么在这方面做文章呢? 采用的数据结构是堆。

思路3:【K部分堆排序】

大根堆维护一个大小为K的数组,目前该大根堆中的元素是排名前K的数,其中根是最大的数。此后,每次从原数组中取一个元素与根进行比较,如小于根的元素,则将根元素替换并进行堆调整(下沉),即保证大根堆中的元素仍然是排名前K的数,且根元素仍然最大;否则不予处理,取下一个数组元素继续该过程。该算法的时间复杂度是O(N*logK),一般来说企业中都采用该策略处理topK问题,因为该算法不需要一次将原数组中的内容全部加载到内存中,而这正是海量数据处理必然会面临的一个关卡。如果能写出代码,offer基本搞定。

 

class Solution:
    def __init__(self):
        self.n = 0
    def heap_sort(self,arr):
        self.n = len(arr)
        self.buildHeap(arr)
        return arr
    def buildHeap(self,arr):
        for i in range(self.n//2-1,-1,-1):
            self.adjustHeap(arr,i)
    def adjustHeap(self,arr,i):
        maxIndex = i
        if i * 2 < self.n and arr[i*2] > arr[i]:
            maxIndex = i * 2
        if i * 2 + 1 < self.n and arr[i*2 +1] > arr[i]:
            maxIndex = i * 2 + 1
        if maxIndex != i:
            arr[i] , arr[maxIndex] = arr[maxIndex] , arr[i]
            self.adjustHeap(arr,maxIndex)
    def GetLeastNumbers_Solution(self, tinput, k):
        # write code here
        if not tinput or k <= 0 or k > len(tinput):
            return []
        if len(tinput) == k:
            return sorted(tinput)
        arr = self.heap_sort(tinput[:k])
        for i in range(k,len(tinput)):
            if tinput[i] < arr[0]:
                arr[0],tinput[i] = tinput[i],arr[0]
                self.heap_sort(arr)
        return sorted(arr)

 

还有没有更简单的算法呢?答案是肯定的。

 

思路4:【不排序---快排】

利用快速排序的分划函数找到分划位置K,则其前面的内容即为所求。该算法是一种非常有效的处理方式,时间复杂度是O(N)(证明可以参考算法导论书籍)。对于能一次加载到内存中的数组,该策略非常优秀。如果能完整写出代码,那么相信面试官会对你刮目相看的。

优点:

每次经过划分,如果中间值等于 K ,那么其左边的数就是 Top K 的数据;
当然,如果不等于,只要递归处理左边或者右边的数即可

该方法的时间复杂度是 O(n) ,简单分析就是第一次划分时遍历数组需要花费 n,而往后每一次都折半(当然不是准确地折半),粗略地计算就是 n + n/2 + n/4 +... < 2n,因此显然时间复杂度是 O(n)

对比第一个方法显然快了不少,随着数据量的增大,两个方法的时间差距会越来越大

缺点

虽然时间复杂度是 O(n) ,但是缺点也很明显,最主要的就是内存问题,在海量数据的情况下,我们很有可能没办法一次性将数据全部加载入内存,这个时候这个方法就无法完成使命了

还有一点就是这种思路需要我们修改输入的数组,这也是值得考虑的一点

下面,给出思路4的Python代码:

def partition(L, left, right):
    """
    将L[left:right]进行一次快速排序的partition,返回分割点
   :param L: 数据List
    :param left: 排序起始位置
   :param right: 排序终止位置
   :return: 分割点
    """
    if left < right:
        print left
        key = L[left]
        low = left
        high = right
        while low < high:
            while low < high and L[high] >= key:
                high = high - 1
            L[low] = L[high]
            while low < high and L[low] <= key:
                low = low + 1
            L[high] = L[low]
        L[low] = key
    return low

def topK(L, K):
    """
    求L中的前K个最小值
   :param L: 数据List
    :param K: 最小值的数目
    """
    if len(L) < K:
        pass
    low = 0
    high = len(L) - 1
    j = partition(L, low, high)
    while j != K: # 划分位置不是K则继续处理
      if K > j: #k在分划点后面部分
         low = j + 1
        else:
            high = j           # K在分划点前面部分
      j = partition(L, low, high)

解决思路4缺点. 利用分布式思想处理海量数据

面对海量数据,我们就可以放分布式的方向去思考了

我们可以将数据分散在多台机器中,然后每台机器并行计算各自的 TopK 数据,最后汇总,再计算得到最终的 TopK 数据

这种数据分片的分布式思想在面试中非常值得一提,在实际项目中也十分常见

扩展问题:

1.如果需要找出N个数中最大的K个不同的浮点数呢?比如,含有10个浮点数的数组(1.5,1.5,2.5,3.5,3.5,5,0,- 1.5,3.5)中最大的3个不同的浮点数是(5,3.5,2.5)。
     解答:四种思路都可以。

2. 如果是找第k到第m(0<k<=m<=n)大的数呢?
       解答:可以用小根堆来先求出m个最大的,然后从中输出k到m个。
3. 在搜索引擎中,网络上的每个网页都有“权威性”权重,如page rank。如果我们需要寻找权重最大的K个网页,而网页的权重会不断地更新,那么算法要如何变动以达到快速更新(incremental update)并及时返回权重最大的K个网页?
   解答:(解法三)用堆排序当每一个网页权重更新的时候,更新堆。
 举一反三:查找最小的K个元素
    解答:最直观的方法是用快速排序或堆排序先排好,在取前K小的数据。最好的办法是利用解法四和解法三的原理进行查找。

posted on 2018-04-28 20:54  吱吱了了  阅读(1393)  评论(0编辑  收藏  举报

导航