排序NB三人组

快速排序,堆排序,归并排序

1、快速排序

方法其实很简单:分别从初始序列“6  1  2 7  9  3  4  5 10  8”两端开始“探测”。先从右往左找一个小于6的数,再从左往右找一个大于6的数,然后交换他们。

这里可以用两个变量i和j,分别指向序列最左边和最右边。我们为这两个变量起个好听的名字“哨兵i”和“哨兵j”。刚开始的时候让哨兵i指向序列的最左边(即i=1)

指向数字6。让哨兵j指向序列的最右边(即j=10),指向数字8。

首先哨兵j开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵j先出动,这一点非常重要(请自己想一想为什么)。

哨兵j一步一步地向左挪动(即j--),直到找到一个小于6的数停下来。接下来哨兵i再一步一步向右挪动(即i++),直到找到一个数大于6的数停下来。

最后哨兵j停在了数字5面前,哨兵i停在了数字7面前。

再继续:

第二次交换结束,“探测”继续。哨兵j继续向左挪动,他发现了3(比基准数6要小,满足要求)之后又停了下来。

哨兵i继续向右移动,糟啦!此时哨兵i和哨兵j相遇了,哨兵i和哨兵j都走到3面前。说明此时“探测”结束。我们将基准数6和3进行交换。交换之后的序列如下。

到此第一轮“探测”真正结束。此时以基准数6为分界点,6左边的数都小于等于6,6右边的数都大于等于6。回顾一下刚才的过程,

其实哨兵j的使命就是要找小于基准数的数,而哨兵i的使命就是要找大于基准数的数,直到i和j碰头为止。

 OK,解释完毕。现在基准数6已经归位,它正好处在序列的第6位。此时我们已经将原来的序列,以6为分界点拆分成了两个序列,左边的序列是“3  1 2  5  4”,右边的序列是“9  7  10  8”。接下来还需要分别处理这两个序列。因为6左边和右边的序列目前都还是很混乱的。不过不要紧,我们已经掌握了方法,接下来只要模拟刚才的方法分别处理6左边和右边的序列即可。现在先来处理6左边的序列现吧。

 

 

left right

取一个数,从左边找一个数比该数大,从右边找比该

 

def partition(li,left,right):
    tmp = li[left]
    while left<right:
        while left<right and li[right]>= tmp:# 从右边找比tmp小的数
            right -=1 # 往左走一步
        li[left] = li[right] # 把右边的值写到左边
        print(li,'right')
        while left<right and li[left] <= tmp:# 从右边找比tmp大的数
            left += 1
        li[right] = li[left] # 把左边的值写到右边空位上
        print(li,'left')
    li[left] = tmp # tmp 归位
    # 此时数组以 tmp为分界线,左边比tmp小,右边的比tmp大
    return left
def quick_sort(li,left,right):
    if left<right:
        mid = partition(li,left,right)
        quick_sort(li,left,mid-1)
        quick_sort(li,mid+1,right)
li = [5,7,4,6,3,1,2,9,8]
quick_sort(li,0,len(li)-1)
print(li)

 

 

总共有logn层,每一层复杂度为n------>  时间复杂度O(nlogn)

 2、堆排序

序比快速排序的时

 

 

A是根节点

叶子节点 是没有子节点的点  b c h i p q k l m n 

树的深度,最深有几层,如图有4层

树的度 就是这个树最多的节点数

 

 什么是二叉树

满二叉树和完全二叉树

二叉树的存储方式:

 

9编号为0  左孩子节点8的编号为1;右孩子7的编号为2

 大根堆和小根堆

堆的概念:

 

堆是一个完全二叉树

堆中每一个节点的值都必须大于等于(或小于等于)其子树中每一个节点的值

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

 

其实我们的堆排序算法就是抓住了堆的这一特点,每次都取堆顶的元素,将其放在序列最后面,然后将剩余的元素重新调整为最大堆,依次类推,最终得到排序的序列。

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

首先根据该数组元素构建一个完全二叉树,得到

每次所有堆的最后一个放堆顶

 i  j hight都是索引值

堆建完之后堆顶是最大的元素

# 堆排序
def sift(li, low, high):
    """
    :param li: 列表
    :param low: 堆的根节点位置
    :param high: 堆的最后一个元素的位置
    :return:
    """
    i = low # i最开始指向根节点     父节点
    j = 2 * i + 1 # j开始是左孩子
    tmp = li[low] # 把堆顶存起来
    while j <= high: # 只要j位置有数
        if j + 1 <= high and li[j+1] > li[j]: # 如果右孩子有并且比较大
            j = j + 1  # j指向右孩子
        if li[j] > tmp: # 如果左孩子或者右孩子大于tmp
            li[i] = li[j] # 大的放堆顶
            i = j           # 往下看一层 变成新的父节点
            j = 2 * i + 1   # 新的子节点
        else:       # tmp更大,把tmp放到i的位置上
            li[i] = tmp     # 把tmp放到某一级领导位置上
            break
    else:
        li[i] = tmp  # 把tmp放到叶子节点上

def heap_sort(li):
    n = len(li)
    # //除法不管操作数为何种数值类型,总是会舍去小数部分,返回数字序列中比真正的商小的最接近的数字。
    # range(start, stop[, step])
    # 比如 range(5,-1,-1)  [5,4,3,2,1,0] 倒叙

    # 创建堆
    for i in range((n-2)//2, -1, -1):
        # i表示建堆的时候调整的部分的根的下标
        print(i)
        sift(li, i, n-1)

    # 建堆完成了---挨个出数
    for i in range(n-1, -1, -1):
        # i 指向当前堆的最后一个元素
        li[0], li[i] = li[i], li[0]
        sift(li, 0, i - 1) # i-1是新的high

li = [i for i in range(100)]
import random

random.shuffle(li)  # 打乱顺序
print(li)

heap_sort(li)
print(li)

 -----------

堆排序参考

 

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

 

 堆排序--python内置模块

import heapq

# python 堆排序内置模块
import heapq  # q-->queue优先队列
import random
li = list(range(100))
random.shuffle(li)
print(li)
heapq.heapify(li) # 建堆
n = len(li)
for i in range(n):
    print(heapq.heappop(li),end=',')

堆排序-----topk问题 

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

思路3:大根堆

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

# 堆排序
def sift(li, low, high):
    """
    :param li: 列表
    :param low: 堆的根节点位置
    :param high: 堆的最后一个元素的位置
    :return:
    """
    i = low # i最开始指向根节点
    j = 2 * i + 1 # j开始是左孩子
    tmp = li[low] # 把堆顶存起来
    while j <= high: # 只要j位置有数
        if j + 1 <= high and li[j+1] < li[j]: # 如果右孩子有并且比较大
            j = j + 1  # j指向右孩子
        if li[j] < tmp: # 如果左孩子或者右孩子大于tmp
            li[i] = li[j] # 大的放堆顶
            i = j           # 往下看一层
            j = 2 * i + 1
        else:       # tmp更大,把tmp放到i的位置上
            li[i] = tmp     # 把tmp放到某一级领导位置上
            break
    else:
        li[i] = tmp  # 把tmp放到叶子节点上

def topk(li,k):
    heap = li[0:k]
    for i in range((k-2)//2,-1,-1):
        sift(heap,i,k-1)
    # 1.建堆
    for i in range(k,len(li)-1):
        if li[i]>heap[0]:
            heap[0] = li[i]
            sift(heap,0,k-1)
    # 2.遍历
    for i in range(k-1,-1,-1):
        heap[0],heap[i] = heap[i],heap[0]
        sift(heap,0,i-1)

    # 3.出数
    return heap

li = [i for i in range(100)]
import random

random.shuffle(li)  # 打乱顺序
# 取出前10个数
print(topk(li,10))

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

思路4:快速排序

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

归并排序

时间复杂度:O(nlogn)

空间复杂度:O(n)

假设两段有序的情况下

def merge(li,low,mid,high):
    i = low
    j = mid + 1
    ltmp = []
    while i<=mid and j<=high:
        if li[i]<li[j]:
            ltmp.append(li[i])
            i+=1
        else:
            ltmp.append(li[j])
            j+=1
   # while 执行完,肯定有一部分没数了
    while i<=mid:
        ltmp.append(li[i])
        i+=1
    while j<=high:
        ltmp.append(li[j])
        j+=1
    li[low:high+1]=ltmp

# 归并操作,前提是列表分两段,两段分别有序
li = [2,4,5,7,1,3,6,8]
merge(li,0,3,7)
print(li)

首先弄清楚递归的概念

# 递归的数据结构式栈先进后出
def calc(n):
    v = int(n/2)
    print(v)
    if v > 0:
        calc(v)
    print(n)

calc(10)

5
2
1
0
1
2
5
10

当遇到递归结束即然后执行递归后面的程序

print((0+1)//2) # 0
print((1+2)//2) # 1

归并排序代码:

def merge(li,low,mid,high):
    i = low
    j = mid + 1
    ltmp = []
    while i<=mid and j<=high:
        if li[i]<li[j]:
            ltmp.append(li[i])
            i+=1
        else:
            ltmp.append(li[j])
            j+=1
   # while 执行完,肯定有一部分没数了
    while i<=mid:
        ltmp.append(li[i])
        i+=1
    while j<=high:
        ltmp.append(li[j])
        j+=1
    li[low:high+1]=ltmp
    print(li)


def merge_sort(li,low,high):
    if low<high:#至少有两个元素
        mid = (low+high)//2
        # 例如len(li)==10
        # mid==5
        # 第一个merge_sort(li,0,5)
        merge_sort(li,low,mid)     # 左边排好序


        # 第二个merge_sort(li,6,10)
        merge_sort(li,mid+1,high)  # 右边排好序
        #print(li[low:high + 1])
        # 当遇到low=high递归结束然后执行递归后面的程序

        print(low, mid, high)
        merge(li,low,mid,high)     # 做归并处理


li = list(range(10))
import random
random.shuffle(li)
print(li,'初始值')
merge_sort(li,0,len(li)-1)
print(li,'最终值')

NB三人组小结

 

posted on 2019-01-05 16:13  foremost  阅读(188)  评论(0编辑  收藏  举报