算法新解(一)

一.前言

  • 1.算法的威力 最小可用ID

    题目:在系统中找到最小可用ID分配给新用户.
    暴力解法:
    嵌套循环,外部循环为从0开始的整数,每次加一,内层循环遍历已有系统中的所有ID,直到找到系统中不存在的ID。

function Min-Free(A)
    x <-- 0
    loop
        if x ∉ A then
            return x
        else
            x <-- x + 1
其中,∉定义如下:
function(x, X)
    for i <-- 1 to len(X) do
        if x = X[i] then
            return False
        else
            return True

则代码的复杂度为O(n^2)

  • 改进一

    考虑当最小ID存在时,则在n个非负整数[0,n),必存在某一个x不在其中,因此可以建立一个长度为n+1的数组,来标记这些整数。

function Min-Free(A)
    F <-- [False, False,...,False] where len(F) = n+1
    for x ∈ A do
        if x < n then
            F[x] <-- True
    for i <-- [0,n] do
        if F[i] = Fales then7
            return i

上述代码中,A为现有的ID系统数组,初始化数组中所有元素为False需要O(n)的时间,遍历A中的所有元素,只要小于n就标记为True,需要O(n)时间,最后线性查找标志数组中第一个值为False的位置。故复杂度为O(n)。

  • 改进二 分而治之
                minfree(A) = search(A, 0, len(A)-1)
        search(A,l,u) = l :                  A = ∅
                        search(A'', m+1,u):  len(A')=m-l+1
                        search(A', l, m):    其他
其中有:
        m = (l + u) / 2
        A' = {x ∈ A ∩ x <= m}
        A'' = {x ∈ A ∩ x > m}

l和u分别为查找上下界。
举例:
A = {0,1,2,3,4,5,6,7,9,10}
①执行search(A,0,9),m=5,则A’={0,1,2,3,4,5},A”={6,7,9,10},此时len(A’)=5-0+1=6,因此执行search(A”,6,10);
②m=8,则A’={6,7},A”={9,10},此时len(A’)≠8-6+1,因此执行search(A’,6,8);
③m=7,则A’={6,7},A”=∅,此时len(A’)=7-6+1=2,因此执行search(A”,8,8);
④此时A为空,因此最小ID为8。
该方法相较于方法一更加节省空间,因为方法一需要维护一个长度为n+1的标志数组,当n很大时,空间上的性能代价很高。而方法二中,第一次需要O(n)次比较来划分子序列A’和A”,第二次仅需要比较O(n/2)次……因此总的的时间复杂度为O(n+n/2+n/4…)=O(2n)=O(n)

  • 2.数据结构的威力 丑数
    题目:寻找第1500个丑数,丑数即只包含2、3、5这三个素因子的自然数,如60、21等。
    暴力解法:
function Get-Number(n)
    x <-- 1
    i <-- 0
    loop
        if Valid(x) then
            i <-- i+1
            if i=n then
                return i
        x <-- x + 1

function Valid(x)
    while x mod 2 = 0 do
        x <-- x/2
    while x mod 3 = 0 do
        x <-- x/3
    while x mod 5 = 0 do
        x <-- x/5
    if x = 1 then
        return True
    else
        return False 
  • 改进一:构造性解法
    不再检查一个数是否只包含2、3、5三个素因子,而是用这三个素因子构造需要的整数。队列从一侧放入元素,然后从另一侧取出元素,所以先放入的元素会先被取出,称为FIFO(First In First Out)先进先出。
    思路:把1作为唯一的元素放入队列,然后不断从队列的另一侧取出元素,分别乘以2、3、5,这样就得到了3个新元素,然后把它们按照大小顺序放入队列(注意丢弃重复产生的元素)
1)初始状态,队列仅含有唯一元素1,计算1*2,1*3,1*5,此时队列变为{1,2,3,5};
(2)计算2*2,2*3,2*5新产生的元素4610按顺序插入队列,此时队列变为{1,2,3,4,5,6,10};
(3)计算3*2,3*3,3*5产生元素6,9,15,由于6重复,故舍弃,队列变为{1,2,3,4,5,6,9,10,15};
...

算法复杂度O(1+2+3+…+n)=O(n^2)

  • 改进二:使用多个队列
    三个队列初始化Q2={2},Q3={3},Q5={5},每次从这三个队列的头部选择最小的一个元素x并取出,进行下面的检查:
    ①如果x是从Q2中取出的,则将2x加入Q2,将3x加入Q3,将5x加入Q5;
    ②如果x是从Q3中取出的,则只将3x加入Q3,将5x加入Q5,而不需要将2x加入Q2,因为2x已经在Q3中(相当于从Q2中取出的元素乘以3);
    ③如果x是从Q5中取出的,则只将5x加入到Q5;
function Get-Number(n)
    if n=1 then
        return 1
    else
        Q2 <-- {2}
        Q3 <-- {3}
        Q5 <-- {5}
        while n > 1 do
            x <-- min(Head(Q2), Head(Q3), Head(Q5))
            if x = Head(Q2) then
                Dequeue(Q2)
                Enqueue(Q2, 2x)
                Enqueue(Q3, 3x)
                Enqueue(Q5, 5x)
            else if x = Head(Q3) then
                Dequeue(Q3)
                Enqueue(Q3, 3x)
                Enqueue(Q5, 5x)
            else
                Dequeue(Q5)
                Enqueue(Q5, 3x)
            n <-- n-1
        return x

算法循环n次,每次循环都从队列中取出最小的一个元素,需要常数时间,接着根据取出取出元素所在的队列,产生1~3个新元素放入队列,这一步也是常数时间,因此复杂度为O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值