排序/二分查找

排序

在这里插入图片描述

  • 冒泡排序、插入排序、选择排序这三种排序算法,它们的时间复杂度都是 O(n2),比较高,适合小规模数据的排序。今天,我讲两种时间复杂度为 O(nlogn) 的排序算法,归并排序和快速排序。这两种排序算法适合大规模的数据排序
  • 归并排序和快速排序都用到了分治思想,非常巧妙。我们可以借鉴这个思想,来解决非排序的问题,比如:如何在 O(n) 的时间复杂度内查找一个无序数组中的第 K 大元素?
  • 分治算法一般都是用递归来实现的。分治是一种解决问题的处理思想,递归是一种编程技巧,这两者并不冲突。

1. 冒泡排序
在这里插入图片描述

O(n^2)

def BubbleSort(alist):
    l=len(alist)-1
    sort=1  #用来表示一轮冒泡排序中是否有排过序,如果没有排过序,说明排序已经完成,不需要再进行下去
    while l>0 and sort==1: #最多需要排序n-1次,加上sort判断可能提前结束排序
        sort=0
        for i in range(l):
            if alist[i]>alist[i+1]:
                alist[i],alist[i+1]=alist[i+1],alist[i]
                sort=1
        l-=1
        print(alist)

2. 选择排序

  • 选择排序改进了冒泡排序,每次遍历列表只做一次交换。为了做到这一点,一个选择排序在他遍历时寻找最大的值,并在完成遍历后,将其放置在正确的位置。与冒泡排序一样,在第一次遍历后,最大的项在正确的地方。 第二遍后,下一个最大的就位。遍历 n-1 次排序 n 个项,因为最终项必须在第(n-1)次遍历之后。
    在这里插入图片描述

O(n^2),但交换数量减少,速度加快。

def selectionSort(alist):
    l=len(alist)-1
    while l>0:
        positionmax=0
        for i in range(l):
            if alist[positionmax]<alist[i]:
                positionmax=i
        alist[positionmax],alist[l]=alist[l],alist[positionmax]
        l-=1
        print(alist)

3. 插入排序

  • 插入排序,尽管仍然是 O(n^2),工作方式略有不同。它始终在列表的较低位置维护一个排序的子列表。然后将每个新项 “插入” 回先前的子列表,使得排序的子列表称为较大的一个项。展示了插入排序过程。 阴影项表示算法进行每次遍历时的有序子列表。
    在这里插入图片描述
def insertionSort(alist):
    l=len(alist)
    for i in range(1,l):
        insertvalue=alist[i]
        position=i-1
        while position>=0 and insertvalue<alist[position]:
            alist[position+1]=alist[position]
            position-=1
        alist[position+1]=insertvalue

4. 快速排序

  • 快速排序首先选择一个值,该值称为 枢轴值。虽然有很多不同的方法来选择枢轴值,我们将使用列表中的第一项。枢轴值的作用是帮助拆分列表。枢轴值属于最终排序列表(通常称为拆分点)的实际位置,将用于将列表划分为快速排序的后续调用。
    在这里插入图片描述在这里插入代码片

5. 归并排序
在这里插入图片描述

归并排序是一种 O(nlogn)算法。

def mergeSort(alist):
    print("Splitting ",alist)
    if len(alist)>1:
        mid = len(alist)//2
        lefthalf = alist[:mid]
        righthalf = alist[mid:]

        mergeSort(lefthalf)
        mergeSort(righthalf)

        i=0
        j=0
        k=0
        while i < len(lefthalf) and j < len(righthalf):
            if lefthalf[i] < righthalf[j]:
                alist[k]=lefthalf[i]
                i=i+1
            else:
                alist[k]=righthalf[j]
                j=j+1
            k=k+1

        while i < len(lefthalf):
            alist[k]=lefthalf[i]
            i=i+1
            k=k+1

        while j < len(righthalf):
            alist[k]=righthalf[j]
            j=j+1
            k=k+1
    print("Merging ",alist)

6. 编程实现 O(n) 时间复杂度内找到一组数据的第 K 大元素

#给定一个无序列表,求出第K大的元素,要求复杂度O(n)
def find_k(test_list,k):
    flag=test_list[0]
    test_list.pop(0)
    l_list=[i for i in test_list if i < flag]
    r_list=[i for i in test_list if i >= flag]
    
    #结果递归的基线条件
    if len(r_list)==k-1:
        return flag
    elif len(r_list)>k-1:
        return find_k(r_list,k)
    else:
        #因为test_list.pop(0)让test_list少了一个元素,所以下面需要+1
        gap=len(test_list)-len(l_list)+1
        k=k-gap
        return find_k(l_list,k)
 
if __name__ == '__main__':
    test_list = [5, 4, 3, 2, 1,10,20,100]
    res=find_k(test_list,1)
    print(res)
 

练习:

滑动窗口最大值
中文版:https://leetcode-cn.com/problems/sliding-window-maximum/

英文版:https://leetcode.com/problems/sliding-window-maximum/

  • 给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口 k 内的数字。滑动窗口每次只向右移动一位。
    返回滑动窗口最大值。

示例:
输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]

解释:
滑动窗口的位置 最大值
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7

注意:
你可以假设 k 总是有效的,1 ≤ k ≤ 输入数组的大小,且输入数组不为空。

参考:https://www.jianshu.com/p/a37a8e1e5a23

def maxSlidingWindow(self, nums, k):
    from collections import deque
    """
    :type nums: List[int]
    :type k: int
    :rtype: List[int]
    """
    length = len(nums)
    res = []
    q= deque()
    for idx ,v in enumerate(nums):
        #检查如果左边界超出窗口就弹出队列头
        if len(q)>0 and q[0]<=idx -k:
            t =q.popleft()
        #循环检查队尾是否大于当前元素
        while  len(q)>0  and  nums[q[-1]]< v:
            q.pop()
        q.append(idx)
        if (idx>=k-1):
            res.append(nums[q[0]])
    return res

二分查找

编程

  • 有序列表对于我们的比较是很有用的。在顺序查找中,当我们与第一个项进行比较时,如果第一个项不是我们要查找的,则最多还有 n-1 个项目。 二分查找从中间项开始,而不是按顺序查找列表。 如果该项是我们正在寻找的项,我们就完成了查找。 如果它不是,我们可以使用列表的有序性质来消除剩余项的一半。如果我们正在查找的项大于中间项,就可以消除中间项以及比中间项小的一半元素。如果该项在列表中,肯定在大的那半部分。
  • 然后我们可以用大的半部分重复这个过程。从中间项开始,将其与我们正在寻找的内容进行比较。再次,我们找到元素或将列表分成两半,消除可能的搜索空间的另一部分。
  • 二分查找是 O(logn),顺序查找是O(n)
    1. 实现一个有序数组的二分查找算法
def binarySearch(alist, item):
        first = 0
        last = len(alist)-1
        found = False

        while first<=last and not found:
            midpoint = (first + last)//2
            if alist[midpoint] == item:
                found = True
            else:
                if item < alist[midpoint]:
                    last = midpoint-1
                else:
                    first = midpoint+1

        return found

递归调用二分查找函数:

def binarySearch(alist, item):
        if len(alist) == 0:
            return False
        else:
            midpoint = len(alist)//2
            if alist[midpoint]==item:
              return True
            else:
              if item<alist[midpoint]:
                return binarySearch(alist[:midpoint],item)
              else:
                return binarySearch(alist[midpoint+1:],item)

2. 实现模糊二分查找算法(比如大于等于给定值的第一个元素)

def fuzzybinarySearch(alist, item):
        first = 0
        last = len(alist)-1
        found = False

        while first<=last and not found:
            midpoint = (first + last)//2
            if alist[midpoint] == item or (alist[midpoint]>item and alist[midpoint-1] <item):
                found = True
                position=midpoint
            elif alist[first] > item:
                found = True
                position=first
            else:
                if item < alist[midpoint]:
                    last = midpoint-1
                else:
                    first = midpoint+1

        return position
def fuzzybinarySearch(alist, item):
    start = 0
    end = len(alist)-1
    mid = (start + end +1) // 2  #向上取整
    while not end == mid:
        if alist[mid] > item:
            end = mid
            mid = (start + end +1) // 2
        elif alist[mid] < item:
            start = mid
            mid = (start + end +1) // 2
        else:
            return mid
    return mid

练习:

Sqrt(x) (x 的平方根)

英文版:https://leetcode.com/problems/sqrtx/

中文版:https://leetcode-cn.com/problems/sqrtx/

实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

示例 1:
输入: 4
输出: 2
示例 2:
输入: 8
输出: 2
说明: 8 的平方根是 2.82842…,
由于返回类型是整数,小数部分将被舍去。

  1. 二分查找
    根据 ( n − 1 ) 2 &gt; = 0 − &gt; n &lt; = ( n + 2 ) / 2 (\sqrt{n} \quad-1)^2&gt;=0\quad-&gt;\sqrt{n}&lt;=(n+2)/2 (n 1)2>=0>n <=(n+2)/2
    n的平方根值有一个上限,得到我们需要查找的范围。

算法与模糊二分查找算法基本相同

class Solution:
    def mySqrt(self, x):
        if x <= 1:
            return x
        start = 1
        end = x//2 +1
        mid = (start + end) // 2
        while not start == mid:
            if mid ** 2 > x:
                end = mid
                mid = (start + end) // 2
            elif mid ** 2 < x:
                start = mid
                mid = (start + end) // 2
            else:
                return mid
        return mid
  1. 牛顿迭代法
class Solution:
    def mySqrt(self, x):
        """
        :type x: int
        :rtype: int
        """
        if x <= 1:
            return x
        r = x
        while r > x / r:
            r = (r + x / r) // 2
        return int(r)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值