重新开张 && two pieces of fascinating code

本文介绍了两种高效的位运算技巧:bit_reverse用于二进制位反序排列,count_ones用于计算二进制位1的个数。这两种技巧通过巧妙的位操作实现了高效计算,相较于常规算法具有更高的效率。

写下这个题目,我的脸皮红得在发烧。我知道我很懒,但是远远低于Blog更新的平均周期仍然使我感到羞愧。然后是我在文章统计里看到了这个:

文章标题页面/聚合访问
英文版WindowsXP下中文名Chm文件无法打开的解决方法1662 /15
呵呵,发现了一个破解NTFS加密文件夹的另类方法4273 /14

两篇奇技淫巧文章占去了我一半的访问量,让我十分怀疑自己是否退化到了高中时的水平(那个时候最为痴迷的杂志是《电脑爱好者》)。因此,我想我应该写点符合一名CS的学生身份的东西。最近看了些面试题,有些东西还值得记录下来,如果我没有再次半途而废,这个系列会继续下去~~

--

先贴两段代码:

unsigned  int  bit_reverse(unsigned  int  n)
{
       n 
= ((n >> 1& 0x55555555| ((n << 1& 0xaaaaaaaa);
       n 
= ((n >> 2& 0x33333333| ((n << 2& 0xcccccccc);
       n 
= ((n >> 4& 0x0f0f0f0f| ((n << 4& 0xf0f0f0f0);
       n 
= ((n >> 8& 0x00ff00ff| ((n << 8& 0xff00ff00);
       n 
= ((n >> 16& 0x0000ffff| ((n << 16& 0xffff0000);
 
       
return n;
}

 
int  count_ones( int  n)
{
       n 
= (n & 0x55555555+ ((n & 0xaaaaaaaa>> 1);
       n 
= (n & 0x33333333+ ((n & 0xcccccccc>> 2);
       n 
= (n & 0x0f0f0f0f+ ((n & 0xf0f0f0f0>> 4);
       n 
= (n & 0x00ff00ff+ ((n & 0xff00ff00>> 8);
       n 
= (n & 0x0000ffff+ ((n & 0xffff0000>> 16);
 
       
return n;
}

 第一次看到是在C PUZZLES, Some interesting C problems,完全不懂它是在做什么,从函数名称上推断,bit_reverse是将一个unsigned int的二进制位反序排列,count_ones是计算一个unsigned int中二进制位1出现的次数。运行一下,的确结果是对的!今天在这里看到一贴回复,又演算了一下,方才明白其中的奥妙。

首先看bit_reverse的一个使用8bit的位串的简化示例,假定从左向右为每个位编号为1 2 3 4 5 6 7 8,

第一步:

((n >> 1) & 0x55) 即 (n >> 1) & 0101,0101),将位串上奇数位右移到偶数位上,得到0 1 0 3 0 5 0 7

((n << 1) & 0xaa) 即 ((n << 1) & 1010,1010),将位串上偶数位左移到奇数位上,得到2 0 4 0 6 0 8 0

两者相与,结果即得到奇数位和偶数位相互交换的位串:1 2 3 4 5 6 7 8 => 2 1 4 3 6 5 8 7

第二步:

分别将相邻的4位编为一组,左侧与右侧相互交换:2 1 4 3 6 5 8 7 => 4 3 2 1 8 7 6 5

第三步:

分别将相邻的8位编为一组,左侧与右侧相互交换:4 3 2 1 8 7 6 5 => 8 7 6 5 4 3 2 1.  ~End~

这段代码像是杂耍一样就完成工作了,呵呵。事实上,在Programming Pearls里Chapter 2里提到的字符串旋转的算法与此有异曲同工之妙。效率O(logn),比起阳春的O(n)算法要高得多。

再来看count_ones,依然使用8bit的位串的简化示例,这次假定位串值位1001,0110

第一步,将相邻的2位编为一组:

(n & 0x55) 即 (n & 0101,0101) = 0001,0100:计算出每一组中左侧位上1的个数,并保存在结果中该组对应的位置上;

((n & 0xaa) >> 1) 即 (n & 1010,1010) = 0100,0001:计算出每一组中右侧位上1的个数,并保存在结果中该组对应的位置上;

两者相加,得到该组中1的个数,并保存在结果中该组对应的位置上;

第二步,将相邻的4位编为一组,根据第一步中的算法,计算每组中1的个数,并保存在结果中该组对应的位置上:

(n & 0x33) + ((n & 0xcc) >> 2) 即 0001,0001 + 0001,0001 即 0010,0010;

第三步,将相邻的8位编为一组,根据第一步中的算法,计算每组中1的个数,并保存在结果中该组对应的位置上:

(n & 0x0f) + ((n & 0xf0) >> 4) 即 0000,0010 + 0000,0010 即 0000,0100(二进制4).  ~End~

如果上个算法像是杂耍,那这个算法就像是魔术了:-) (当然还有更加惊为天人鬼斧神工的,看Quake3中的快速平方根算法)。对于一般情况,这个算法的复杂度也是O(logn),相对于阳春的O(n)算法当然要高。但是在某些情况下,如果只有很少的位为1(考虑常见的标志位),那么还有下面的代码:  

int  CountBits(unsigned  int  x)
{
    
int count=0;
    
while(x)
    
{
        count
++;
        x 
= x&(x-1);
    }

    
return count;
}

 How does it work? Try to figure it out:-)

这只是关于位运算一点小trick,茶余饭后娱乐一下,搞嵌入式的大牛们就不必费心看过来了。有自虐倾向者请继续看向Algorithms for Programmers里的Chapter 1.

### Bag-of-Words 与 N-gram 的主要区别及适用场景 #### 主要区别 1. **基本概念** - Bag-of-Words(BOW)是一种基于词频的文本表示方法,将文本看作是一个无序的词集合,忽略词之间的顺序和语法结构[^2]。 - N-gram 是一种将文本切分成连续的 n 个词或字符片段的方法,能够保留部分词序信息。例如,对于句子 "I love natural language processing",其 2-gram(bi-gram)为 ["I love", "love natural", "natural language", "language processing"][^4]。 2. **词序信息** - BOW 不考虑词的顺序,仅统计每个词在文本中出现的频率。因此,BOW 无法捕捉到词与词之间的上下文关系。 - N-gram 能够捕捉到局部的词序信息,因为它是基于连续的 n 个词或字符进行建模。然而,随着 n 增大,计算复杂度和数据稀疏性问题也会加剧。 3. **特征维度** - BOW 的特征向量维度等于语料库中词汇表的大小。如果语料库较大,则特征向量可能非常稀疏。 - N-gram 的特征维度取决于 n 的大小以及语料库中所有可能的 n-gram 组合数。当 n 较大时,特征空间会迅速膨胀,导致维度灾难。 4. **稀疏性** - BOW 和 N-gram 都会产生稀疏的特征向量,但 N-gram 的稀疏性通常更严重,尤其是当 n 较大时。这是因为 n-gram 的组合数量随 n 的增长呈指数级增加。 5. **语义信息** - BOW 无法捕捉语义相似性,也无法区分同义词或近义词。例如,“car” 和 “automobile” 在 BOW 中被视为完全不同的词[^1]。 - N-gram 同样无法直接捕捉语义信息,但它通过保留局部词序信息,可以在一定程度上反映某些短语的语义特性。 --- #### 适用场景 1. **Bag-of-Words 的适用场景** - 文本分类任务:如垃圾邮件检测、情感分析等,其中全局词频信息比局部词序更重要[^3]。 - 主题建模:如 LDA(Latent Dirichlet Allocation),它依赖于词袋模型来提取文档的主题分布。 - 当数据集较大且计算资源有限时,BOW 是一种简单有效的选择。 2. **N-gram 的适用场景** - 语言模型:如预测下一个词的概率,N-gram 模型可以利用局部词序信息提高预测准确性[^4]。 - 短语匹配:在搜索引擎或问答系统中,N-gram 可以帮助识别用户输入中的关键短语。 - 当需要捕捉局部上下文信息时,N-gram 是一个更好的选择,尽管它可能导致更高的计算成本和数据稀疏性。 --- ### 示例代码 以下是一个简单的 Python 实现,展示如何生成 BOW 和 N-gram 特征向量: ```python from sklearn.feature_extraction.text import CountVectorizer # 示例文本 texts = ["I love natural language processing", "Natural language is fascinating"] # Bag-of-Words 特征提取 vectorizer_bow = CountVectorizer() bow_features = vectorizer_bow.fit_transform(texts) print("BOW Vocabulary:", vectorizer_bow.get_feature_names_out()) print("BOW Features:\n", bow_features.toarray()) # N-gram 特征提取 (bi-gram) vectorizer_ngram = CountVectorizer(ngram_range=(2, 2)) ngram_features = vectorizer_ngram.fit_transform(texts) print("\nN-gram Vocabulary:", vectorizer_ngram.get_feature_names_out()) print("N-gram Features:\n", ngram_features.toarray()) ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值