[Elementary-Algorithms/2]第n个丑数:改进一的学习理解

暴力解法就不看了,以及丑数百度百科
先看改进一的描述(文字来自原文):

在上面的暴力解法中,取模运算和除法运算很耗时。并且这些运算被循环执行了很多次。
我们可以转换一下思路,不再检查一个数是否仅含有235的因子,而是从这三个因子
中构造需要的整数。
我们从1开始,分别乘以235来生成整数。这样问题就变成如何依次生成丑数。我们
可以使用队列这种数据结构来解决这个问题。
队列从一侧放入元素,然后从另一侧取出元素。所以先放入的元素会先被取出。这一特性
被称为先进先出FIFO(First-In-First-Out)。
我们的思路是先把1作为唯一的元素放入队列,然后我们不断从队列另一侧取出元素,分
别乘以235,这样就得到了3个新的元素。然后把它们按照大小顺序放入队列。注意,
这样产生的整数有可能已经在队列中存在了。这种情况下,我们需要丢弃重复产生的元素。
另外新产生的整数还有可能小于队列尾部的某些元素,所以我们在插入时,需要保持它们
在队列中的大小顺序。

第n个丑数,随着n的不同,结果也不同,所以第n个丑数是无法立马得到的,必须通过计算。而暴力解法中,为了找到第1500个丑数,每个数都验证一遍,这样下来,要验证的数远不止1500个,而且验证一个数是否为丑数的开销也大。改进一的思想的改进在于去掉了验证步骤,并直接排除了第1500个丑数之前的所有非丑数,因为一个丑数分别乘以2、3、5,必定还是一个丑数,这样就不用验证了,若一个并不确定是否为丑数的数分别乘以2、3、5,得到的数也是不确定的,这时肯定得验证。当然,直接一下生成1500个丑数是不可能的(也许有这么强大的算法…),下面的截图很形象地描述了如何按阶段来生成丑数的,再结合文字描述,就很好理解这个过程了(截图来自原文):
这里写图片描述
这里要提一点,作者说用队列这个数据结构,我觉得不应该是队列,队列的定义是只能从一端插入元素,另一端取出元素,按这个特性是无法确保一个队列中的元素的唯一性和有序性。我觉得用一个支持从一端取元素,并且插入操作支持从任意位置并能确保元素唯一性和有序性的线性表就行了。


伪码也挺简单的,简单看下(来自原文):

1: function Get-Number(n) // 获取第n个丑数
2:  Q ← NIL // 初始化一个空队列
3:  Enqueue(Q, 1) // 将1插入该队列作为第一个元素
4:  while n > 0 do // 我们需要构造n个丑数
5:      x ← Dequeue(Q) // 从队列的一端取出一个元素
6:      Unique-Enqueue(Q, 2x) // 将构造的丑数2x按顺序且无重复地插入队列中
7:      Unique-Enqueue(Q, 3x) // 同理
8:      Unique-Enqueue(Q, 5x) // 同理
9:      n ← n − 1 // 执行完一个步骤,n递减1
10: return x // 当上面的循环结束后,x正好是取出的第n个丑数,直接返回就行了
11:function Unique-Enqueue(Q, x) // 确保元素插入的唯一性和有序性的操作
12: i ← 0
13: while i < |Q| ∧ Q[i] < x do // 这句的意思是,当i小于队列Q的长度,且队列中第i个元素Q[i]小于要插入的元素x
14:     i ← i +1 // 满足上面那句话,说明i这个位置不是x要插入的地方。当循环结束后,i为第一个>x的位置,
15: if i < |Q| ∧ x = Q[i] then // 这句的意思是,如果i小于队列Q的长度,且x与Q[i]相同
16: return // 相同,就不用插入x了
17: Insert(Q, i, x) // 否则就在i这个位置插入x

原文中没提供源码,我自己用C写了份。由于C语言没有原生的数据结构库,就自己简单实现了个支持从头结点取出元素,且元素可从任意位置插入并保持唯一性和有序性,这两个简单操作。算是一个有序正整数集吧,实现细节就不讲了,学过单链表的看下源码就清楚了,源码在另一篇博客点击查看。示例仅供参考:

/*
 * file:ugly_num.c
 * author:WangTaL
 * copyright:03/24/2018
*/
#include <stdio.h>
#include "set.h"

// 寻找第n个丑数。算法就是原文描述的那样
int wtlFindUglyNum(int n){
    wtlSET* set = wtlSet_Create();
    wtlSet_Insert(set, 1);

    int x;
    while (n > 0) {
        x = wtlSet_GetFirst(set);
        if (0 == x) return x;

        wtlSet_Insert(set, 2 * x);
        wtlSet_Insert(set, 3 * x);
        wtlSet_Insert(set, 5 * x);

        n--;
    }

    wtlSet_Destroy(&set);

    return x;
}

int main(int argc, char* argv[])
{
    unsigned long u_num = wtlFindUglyNum(1500); // 第1500个丑数:859963392
    printf("%d\n", u_num);

    return 0;
}

作者虽然是借这个问题来表达数据结构的威力,我觉得还是讲算法,我们直接用数组也可以实现这个过程的,只要实现那两个元素的取出和插入操作就行了。不过算法是离不开数据结构的,解决问题就是用更高效的算法和更合适的数据结构:)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值