一种复杂度为O(n)的排序算法:位操作应用之位排序

2023-05-16

  ——位运算应用篇(3)

摘要

本篇仍然关注位操作的应用,通过前面的两篇文章(《非常给力:位运算求组合》《简单、易懂:位运算之求集合的所有子集》),我们已经略见了位操作之强大威力。如果说那两篇文章中讲解的应用比较偏僻,那么本篇介绍的是位操作在广为人知的排序算法中的应用——位排序。本篇讲述了位排序的两种实现方法,在描述上尽量做到简单、通俗、易懂

本篇内容并不新颖,如果您关注过大量数据处理或者看过各大牛X公司的面试系列或者读过《编程珠玑》,那么应该对位排序非常熟悉,即便是这样,本篇作者仍然希望你挤出一些你的宝贵时间来看完后面的内容,因为作者非常希望您能提一些意见,或者更正一些错误。

引例

在切入正题之前,我们来看一个引例:有一个数组,乱序存放了某个班级的所有学生的学号,已知该班级的学生数目不超过50人,并且学号范围是0到49(不一定连续,因为有些同学可能中途被开除了,作者本人就是导致当年那个大学班级的学号不连续的第一人,详情见《有感于“要像狗一样活下去”》),现在请你对这个数组排序,要求时间复杂度必须小于O(nlog2(n))

上面的引例并不属于大量数据处理问题,不是用来做位排序的好例子,但是它却是个引子,因为其时间复杂度的特殊要求。后面我们将看到位排序实际上适合处理大量数据排序问题。

最初的尝试

一看到这样的题目,我们的大方向是明确的,那就是排序呗,于是我们开始尝试各种主流的排序算法,看看能否满足要求。最简单、最基础、也是最容易实现的冒泡排序、插入排序、选择排序时间复杂度为均O(n2),不满足要求。高级的排序算法如快速排序、堆排序、归并排序、希尔排序等时间复杂度为O(nlog2(n)),仍然达不到时间复杂度小于O(nlog2(n))的要求。事实上绝大多数数据结构教程上都说了最好的排序算法时间复杂度是O(nlog2(n))。没错,确实如此,因为它们讲解的是通用的排序算法,而这里的引例有几个特殊条件可供我们使用:

1、  待排序的元素的最大范围已知(从0到49)

2、  待排序的元素不重复,因为不存在两个学号相等的学生(非常隐蔽的、但是非常重要的条件)

3、  待排序数据基本充满整个数据范围(只有少数人会被开除)(这一点也是非常重要的,后面我会解释)

4、  小于O(nlog2(n))的时间复杂度(与其说这是可以利用的条件,还不如说这是孙悟空头上的紧箍咒)

既然常用的排序算法不能满足要求,那么我们就得想其他方法了。我们都知道算法的时间复杂度和空间复杂度是一对矛盾体,通常可以采取以空间换时间的方法来降低算法的时间复杂度。后面讲的函数(sort_1)是我大一时想出来的,当时是为了解决数据结构上的课后习题,还记得当初为自己的这个实现激动了许久。在给出程序之前,先说一下我当时的思路:1、用一个标记数组记录每一个数是否存在于待排序数组中,如果存在而标记1,否则标记0;2、扫描标记数组,并根据它重新填充原来的待排序数组。我的程序看起来是这样的

using namespace std;
#define NUMBER_RANGE 50
#define ARRAY_SIZE 10

int a[ARRAY_SIZE] ={1,13,3,42,7,23,5,37,26,10};
void sort_1(int a[],int N) //N是a中待排序元素个数
{
	int i,j;
	int* p = new int[NUMBER_RANGE];  //这里浪费了40*4=160byte
	memset(p,0,NUMBER_RANGE*sizeof(int));
	for (i = 0;i<N;i++)
	{
		p[a[i]] = 1;
	}
	for (i = 0,j=0;i<NUMBER_RANGE;i++)
	{
		if (p[i]==1)
		{
			a[j++] = i;
		}
	}
}

sort_1中定义了一个标记数组(int* p = newint[Range];)并包含了两个for循环,第一个循环循环N次,其作用是扫描待排序数组a中的每个元素,并把标记数组的相应元素赋值为1,见p[a[i]]=1;第二个for循环循环Range次(Range代表了该班级学生的学号范围),其作用是扫描刚才的标记数组,并且每扫描到一个1就把其对应的下标(等于某个学生的学号)存到原来的数组a中去,当这个for循环结束后,数组a就保存了排序后的学号。

上面的算法复杂度由两个for循环中的最大者决定,而Range大于等于N,因此其复杂度由第二个for循环决定的,因此其复杂度为O(Range)。由于前面的条件3可知:待排序数据基本充满整个数据范围,因此Range略大于N(程序中a数组大小为10,这是我为了少打字而偷懒了),故该算法的时间复杂度可认为是O(N)。我们为了达到降低时间复杂度的目的,却付出了Range*sizeof(int)个字节的空间(int* p =new int[Range];)可以说这个程序是功过参半,那么是否有必要减少内存空间的消耗呢?如果需要,又该如何做到减少内存的消耗呢?这将是本篇后面讨论的主题。

少耗内存的必要性

前面我给的引例只是对某个班级的学生的学号排序,因此其待排序的元素个数相对较少(最多50),然而假设要排序的对象是全中国人民的省份证号码呢?那将有13亿个数字,这还是非常保守之估计(相信还有很多“黑人”,在我家那里把没有户口是人称为黑人,通常是超生的孩子没有户口,因此也没有土地,我小的时候就属于“黑人”,由于没分到土地,每次吃饭都得挨说,当初因为超生了我,我爸差点失去了教师公职,后来爸要给我取名“伍超生”,要不是我妈阻拦,估计它就是我现在的名字了。那个时候很多超生的人其名字都跟超生有关,比如我家隔壁的孩子由于是超生而被罚款860元,因此它的小名就是八百六,幸好当年罚款金额不是250)。废话扯多了,如果待排序是中国人的身份证号码,那么上面的程序将改成:int* p = new int[1300000000];这究竟要耗多大的内存呢?笔算一下吧:1300000000*4 = 5200000000byte = 4960M = 4.8G。

O(N)的算法还有啥用?

既然前面那个O(N)的方法在待排序数据量巨大时,其消耗的内存也很巨大,那么它还有啥用?答案是它仍然非常有用,前提是我们能想法减少它消耗的内存。那么能否减少它消耗的内存呢?答案是肯定的,很快,我就想到了各种基本数据类型占用的内存不一样,一个int要耗4byte,而一个char只耗1byte,如果将上面的程序中的标识数组由int型改成char类型,其消耗的内存立马减少四分之三,对于对13亿中华儿女的省份证排序这样的问题,其消耗的内存由4.8G立马降到1.2G,这个改进是非常可观的,然而它仍然不能让我们满意,要知道人类的欲望是无限的。

基于位标识的排序:位排序

我们再次回到之前的引例,由于标识数组中的每个元素只需要标识其下标对应的值是否存在,因此它取值只能是两个:存在或不存在。而我们知道一个二进制位也对应了两种状态:0和1。如果我们能用一个二进制bit位来标识某个元素是否存在,那么对于13亿个数据来说,只需要13亿个比特,而13亿个bit约等于155M内存。这比使用char数组来作为标记又少耗用了八分之七的内存,并且155M内存是目前大多数PC能够承受的。我们将这种基于bit位标识的排序算法称为“位排序”或者“位图排序”,给它取个洋名:BitSort。

使用bitset来实现位排序

前面我的文章中提到过一个标准类:bitset,它是C++标准库提供的,你可以直接使用,它其实就是bit的集合,它提供许多许多接口让你能方便地对该集合中的bit进行操作(但是bitset没有迭代器,因此一般认为它不是容器)。关于bitset的更多知识,请看MSDN或者其他文档,这里我们只需要使用它的三个接口:创建bitset对象、给某个bit赋值和查询某一个bit的值。这里对这三个接口做一个简单介绍:

创建bitset对象:bitset提供了三个构造函数,我们只用最简单的一个,其形式是bitset<N> bits;它创建一个包含N个bit的bitset对象,并给每个bit初始化为0。关于另外两个构造函数请看MSDN

给某个bit赋值:set(size_tpos, bool val = true);


查询某个bit的值:bool test(size_t pos) const;  

我们用bitset对之前引例中的程序进行改造后,它看起来是这样的:

#define NUMBER_RANGE 50
#define ARRAY_SIZE 10

int a[ARRAY_SIZE] ={1,13,3,42,7,23,5,37,26,10};
void sort_2(int a[],int N)//N是a中待排序元素个数
{
	int i,j;
	//int* p = new int[NUMBER_RANGE];
	//memset(p,0,NUMBER_RANGE*sizeof(int));
	bitset<NUMBER_RANGE> bits;
	for (i = 0;i<N;i++)
	{
		//p[a[i]] = 1;
		bits.set(a[i]);
	}
	for (i = 0,j=0;i<NUMBER_RANGE;i++)
	{
		if (bits.test(i)/*p[i]==1*/)
		{
			a[j++] = i;
		}
	}
}

在上面的sort_2函数中,我用bitset的相关接口取代了sort_1函数中被注释的部分,其他结构保持不变,其实现的结果跟sort_1一样,都是:

我说这个版本要优于之前的版本,原因是它吃的内存要少很多。上面程序中我定义的bitset对象bits包含50个bit(NUMBER_RANGE=50),我用cout<<sizeof(bits)测试过,其输出8,即它只吃了8byte的内存,或许你要问,8byte不是等于8*8=64bit么,这不等于50bit啊。这原因就是操作系统分配内存时以字节为单位,也就是说分配的内存必须是byte的整数倍,按照这个思路,似乎只需要7byte =56bit就够了,那为什么又是8byte呢,这又是因为标准库在实现bitset时以一个字为基本单位分配内存的(不是字节),也就是说分配的内存大小必定是4byte的整数倍。这里我们虽然浪费了十几个bit的空间,但是比起之前用int类型的数组或者char类型的数组来作为标记,这个程序节约了很多内存的(50个int数据消耗内存200byte,而50个char数据消耗内存50byte)。

使用bitset实现的位排序并不完美

上面利用bitset实现的位排序看起来十分完美,假如用它来对13亿中国人的身份证排序,它只需要消耗155M内存,比用int型数组的4.8G好许多倍。于是我兴冲冲地想要用上面那个版本的程序来完成对13亿人的身份证进行排序,然而我却吃了当头一棒。我的程序很简单,就是把上面的NUMBER_RANGE改成1300000000。当程序运行时却出现了如下异常:



凭直觉我觉得是bitset<NUMBER_RANGE> bits;在作怪,我怀疑是由于分配内存失败而导致的,我查看了bitset的头文件发现其中申明的异常只有out_of_range,overflow_error,invalid_argument。凭直觉我觉得呢我能捕捉到overflow_error异常,然而遗憾的是我没有捕捉到,但是我可以肯定的是,当我把NUMBER_RANGE改小之后不会出现上面的异常。那么是bitset类的问题还是我的系统问题呢?我由测试了int*p= new char[1300000000/8];发现该内存分配成功,也就是说,我的系统动态地分配包含13亿个bit的数据是可以的。那出现上面的异常应该就是bitset类的问题了,但是为什么bitset无法创建一个包含bit数较多的对象,至今我没彻底弄清楚。既然bitset无法帮我们完成要对13亿人的身份证排序的功能,那么我们只好自己实现一个bitset了。

自写bitset类

接下来,我将自己实现一个类似bitset的类,当然它非常简单,它只做三件事:创建对象,给某个bit赋值和查询某个bit的状态,这个类看起来是这样的:(先代码,后说明)

#include "iostream"
using namespace std;
#define BITSPERCHAR 8        //每个字节占8bit
#define SHIFT 3  //用于移位,计算第pos个bit属于第几个字节,见set和test函数
#define MASK 0x07  //7,用于确定第pos个bit属于某个字节的第几个bit

template<unsigned N=0>
class SimpleBitSet
{
    public:
        SimpleBitSet()
        {
            bits = new char[N/BITSPERCHAR + 1];
            memset(bits,0,N/BITSPERCHAR + 1);
        }    
        SimpleBitSet<N>& set(unsigned pos,bool value = true)
        {
            if(value)
            {
                bits[pos>>SHIFT] |=(1<<(pos & MASK));
            }
            else
            {
                bits[pos>>SHIFT] &= ~(1<<(pos & MASK));
            }  
			return *this;
        }    
        bool test(unsigned pos)const
        {
            return (bits[pos>>SHIFT] & (1<<(pos & MASK))) ==0? false:true;
        }    
    private:
        char* bits;
};

说明:上面的模板类是参考了《编程珠玑》的源码而简单实现的。需要特别说明的是,私有数据成员bits数组定义成char类型的,我只所以这么做是考虑到平台移植性,因为如果定义成int的话,不同平台下可能分配的内存大小不一样,因为c/c++标准并没有规定一个int类型数据用多少bit来表示(大多数编译平台int都占32位,然后可能会是64位),但是c/c++标准规定任何平台下,char都占8位。

用自写bitset类完成排序

接下来,我用自己写的bitset类:重写sort_2函数,只需要把其中的bitset<NUMBER_RANGE>bits;改成SimpleBitSet<NUMBER_RANGE>bits;具体代码见下:

#define NUMBER_RANGE 50
#define ARRAY_SIZE 10

int a[ARRAY_SIZE] ={1,13,3,42,7,23,5,37,26,10};
void sort_3(int a[],int N) //N是a中待排序元素个数
{
	int i,j;
	//int* p = new int[NUMBER_RANGE];
	//memset(p,0,NUMBER_RANGE*sizeof(int));
	//bitset<NUMBER_RANGE> bits;
SimpleBitSet<NUMBER_RANGE> bits;
	for (i = 0;i<N;i++)
	{
		//p[a[i]] = 1;
		bits.set(a[i]);
	}
	for (i = 0,j=0;i<NUMBER_RANGE;i++)
	{
		if (bits.test(i)/*p[i]==1*/)
		{
			a[j++] = i;
		}
	}
}

分析

下表是我分别对sort_2(使用bitset)和sort_3(使用SimpleBitSet)函数进行测试的结果:

数据量

使用bitset

使用SimpleBitSet

100000   十万

0seconds

0seconds

1000000   百万

0.063seconds

0.022seconds

10000000   千万

0.672seconds

0.235seconds

100000000  一亿

异常

2.375seconds

200000000  两亿

异常

4.797seconds

300000000  三亿

异常

7.14seconds

400000000  四亿

异常

9.579seconds

 

通过上面的测试可以得出如下结论:

1、使用bitset和SimpleBitSet的排序的时间复杂度都为O(N),时间成线性增长

2、自己写的SimpleBitSet虽然功能简单,但是它效率比标准库的bitset类要高至少1倍!

3、自己写的SimpleBitSet能使用更大数据范围。

备注:上面的测试环境是:联想Y450,intel  P7350 2.0GHz,2G内存,xp系统,Dev-C++4.9.9.0编译器。上面的测试数据不包含读写文件的时间。

总结

通过前面的描述,我们知道位排序的应用有一定的限制,首先要能确定待排序的数据范围,只有这样才知道要定义多少个bit来做标识;其次待排序数据不能有重复,因为用一个bit无法记录某个数据重复出现了多少次;最后待排序的数据尽量充满其数据范围,否则内存浪费太大(有时候可以通过数据处理来使得待排序数据聚集在某个区间内)。另外本文还得出另外一个经验:如果标准库提供的类无法满足我们的要求,那么可以自己仿造写一个,由于标准库考虑到通用性,其功能通常较多,实现较复杂,效率反而没有自己写的功能简单的类好使。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

一种复杂度为O(n)的排序算法:位操作应用之位排序 的相关文章

  • 剑指 Offer 53 - I. 在排序数组中查找数字 I

    剑指 Offer 53 I 在排序数组中查找数字 I 题目 题目链接 具体代码 题目 题目链接 https leetcode cn com problems zai pai xu shu zu zhong cha zhao shu zi l
  • 桶排序 (详细图解)

    一 桶排序 桶排序 Bucket sort 或所谓的箱排序 是一个排序算法 工作的原理是将数组分到有限数量的桶里 每个桶再个别排序 有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序 最后依次把各个桶中的记录列出来记得到有序序列
  • 转-各种排序动图

    1 快速排序 介绍 快速排序是由东尼 霍尔所发展的一种排序算法 在平均状况下 排序 n 个项目要 n log n 次比较 在最坏状况下则需要 n2 次比较 但这种状况并不常见 事实上 快速排序通常明显比其他 n log n 算法更快 因为它
  • 2. 合并两个有序数组

    2 合并两个有序数组 题目描述 解题思路 代码 题目描述 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2 另有两个整数 m 和 n 分别表示 nums1 和 nums2 中的元素数目 请你 合并 nums2 到 nums
  • 数组实例解析3(杨辉三角)

    根据用户输入的行数n输出对应行数的杨辉三角 具体如下 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1 public class ArrayTraingleTest public static void
  • C语言库函数——快排函数qsort()

    目录 一 函数原型 二 函数介绍 三 函数使用 常见写法 比较函数 四 函数实例 1 int型数组 2 double型数组 3 char型数组 4 字符串 5 结构体 一级结构 二级结构 一 函数原型 void qsort void bas
  • R语言基本函数的学习(持续更新)

    目录 前言 Tidyverse包 arrange 函数 head 函数 filter 函数 select 函数
  • 大数据量的冒泡排序 (计次数)

    题目描述 给定一个包含从0到n 1各一次的数组 若使用冒泡排序将其排为升序 问其中需要进行多少次交换 输入 测试数据有多组 每组由两行组成 第一行包含正整数n n lt 5000 下一行包含从0到n 1的n个整数的序列 输出 对于每组测试数
  • ​7.1 项目1 学生通讯录管理:文本文件增删改查(C++版本)(自顶向下设计+断点调试) (A)​

    C 自学精简教程 目录 必读 作业目标 这个作业中 你需要综合运用之前文章中的知识 来解决一个相对完整的应用程序 作业描述 1 在这个作业中你需要在文本文件中存储学生通讯录的信息 并在程序启动的时候加载这些数据到内存中 2 在程序运行过程中
  • 【数据结构】排序(直接插入、折半插入、希尔、冒泡、快速、直接选择、堆、归并、基数排序)

    一 什么是排序 排序 将一组杂乱无章的数据按一定规律顺次排列起来 即 将无序序列的数据节点包含多个数据域 那么排序往往是针对其中某个域而言 二 排序方法的分类 1 按数据存储介质可分为 内部排序 数据量不大 数据在内存 无需内外存交换数据
  • shell的排序

    目录 一 冒泡排序 1 定义 2 基本思想 3 算法思路 4 算法逻辑图 5 示例1 将指定数组重新排序 6 示例2 写一个函数 输入任何数组都可以进行排序 二 直接选择排序 1 直接选择排序的逻辑图 2 示例 将指定数组重新排序 三 反转
  • 数据结构——哈希排序

    哈希排序 就是用空间换取时间的一种排序方式 空间利用率达O n 算法思想 如果一个元素序列a里没有重复的元素 而我们需要找最大值或者前几个最大值时 怎么办呢 1 将这个a序列排序 然后直接选出目标值 2 开辟一个b数组 a里的每一个元素对应
  • 九种常见排序的比较和实现

    首先排序算法大的可以分为 关键字比较 非关键字比较 关键字比较 关键字比较就是通过关键字之间的比较和移动 从而使整个序列有序 而关键字比较的算法 又可以像下面这样划分 对于排序算法之间的比较 无异于时间复杂度和空间复杂度 看下面这张表格 由
  • 全面分析冒泡排序过程

    冒泡排序也是一种简单直观的排序算法 其思想是 它重复地走访过要排序的数列 一次比较两个元素 如果他们的顺序错误就把他们交换过来 走访数列的工作是重复地进行直到没有再需要交换 也就是说该数列已经排序完成 这个算法的名字由来是因为越小的元素会经
  • C#冒泡排序算法

    冒泡排序实现原理 冒泡排序是一种简单的排序算法 其原理如下 从待排序的数组的第一个元素开始 依次比较相邻的两个元素 如果前面的元素大于后面的元素 升序排序 则交换这两个元素的位置 使较大的元素 冒泡 到右侧 继续比较下一对相邻元素 重复步骤
  • C语言冒泡排序和选择排序

    一 冒泡排序法 假设从小到大排序 例一数组 int arr 2 1 34 5 arr 0 先跟相邻的arr 1 比较大小 如果比它大则交换两个数值位置 大的数值放在后面 然后比较arr 1 和arr 2 的大小 以此类推 直至第n 2个和第
  • 快速排序算法详解(原理,时间复杂度,实现代码)

    快速排序算法详解 原理 实现和时间复杂度 快速排序是对冒泡排序的一种改进 由 C A R Hoare Charles Antony Richard Hoare 东尼 霍尔 在 1962 年提出 快速排序的基本思想是 通过一趟排序将要排序的数
  • 插入排序超详解释,一看就懂

    目录 一 插入排序的相关概念 1 基本思想 2 基本操作 有序插入 二 插入排序的种类 三 直接插入排序 1 直接插入排序的过程 顺序查找法查找插入位置 2 使用 哨兵 直接插入排序 四 直接插入排序算法描述 五 折半插入排序 1 查找插入
  • 分治-归并排序

    文章目录 315 计算右侧小于当前元素的个数 1 题目 2 算法原理 3 代码实现 493 翻转对
  • 八大排序(希尔排序)

    上篇文章我们来看了看插入排序是怎么实现的 本章内容就是在插入排序的基础上完成希尔排序 希尔排序是一个比较强大的排序 我们希尔排序的时间复杂度是比较难算的 这里直接给出的结论就是时间复杂度就是O N 1 3 比较难算的原因就是我们每一次的次数

随机推荐

  • 时间管理也要区分“投资行为”与“消费行为”(转载)

    10 年前甲和乙是本科的同学 xff0c 在社会工作5 年后 xff0c 不约而同积蓄了30 万元人民币 5 年前 xff0c 他们都花掉了这30 万元 甲去通州购买了一套房 乙去买了一辆 奥迪 5 年后的今天 xff1a 甲的房子 xff
  • ZooKeeper的选举机制详解

    1 xff09 半数机制 xff1a 集群中半数以上机器存活 xff0c 集群可用 所以 Z ookeeper适合安装奇数台服务器 2 xff09 Zookeeper虽然在配置文件中并没有指定M aster和 S lave 但是 xff0c
  • 写了个幷查集的模板类...

    下面的概念介绍主要参考了 xff1a http www cnblogs com cherish yimi archive 2009 10 11 1580839 html xff0c 根据这个介绍 xff0c 自己写了个稍微通用一点的模板 x
  • PKU_1611(幷查集解法)

    题目来源 xff1a http poj org problem id 61 1611 采用幷查集的思路 xff0c 将同一个组的的学生合并为一个集合 xff0c 最后看那些学生跟student0属于同一个集合即可 下面是我的AC代码 xff
  • POJ2524(并查集)

    题目来源 xff1a http poj org problem id 61 2524 Description There are so many different religions in the world today that it
  • POJ_2236(并查集)

    题目 xff1a Description An earthquake takes place in Southeast Asia The ACM Asia Cooperated Medical team have set up a wire
  • 技术管理中的几个问题

    前几天跟朋友聊天时 xff0c 朋友说他刚刚从一家知名软件公司面试出来 xff0c 朋友去面试的是一家公司的技术管理岗位 xff0c 所以在面试的时候被问及的问题也偏重于技术管理方面的问题 xff0c 在与朋友的聊天中将这几个问题归纳了一下
  • 业务建模

    业务建模是OOAD的重要组成部分 xff0c 简单的说 xff0c 业务建模就对业务领域问题进行结构化的描述 这个描述将会直接指导最终生成的软件 xff0c 业务模型是否具有扩展性 xff0c 业务模型是否能够正确的反映需求 xff0c 都
  • 读书笔记之《软件工程思想》

    读书笔记 xff1a 林锐博士的 软件工程思想 首先申明 xff1a 由于才疏学浅 xff0c 很多感悟或许是不准确的 xff0c 甚至是错误的 但是我仍然坚持写下这篇读书心得 xff0c 原因有二 xff1a 首先 xff0c 想向大家推
  • C++开发常用工具(开发,辅助,编辑,建模,版本控制等)

    开发环境 xff1e Turbo c DOS时代c语言开发的经典工具 xff0c 目前适合两类人使用 xff1a c语言beginner xff08 尤其是学生一族 xff09 xff0c 具有怀旧情节的专业人士 xff1a xff09 x
  • 在Linux和Windows下搭建CVS服务器与CVS客户端的详细配置指南

    此文虽看上去写的很详细 xff0c 但有些地方却还模糊 xff0c 该简洁的地方没有简洁 xff0c 读者很容易迷糊 再留下一个ubuntu系统下配置cvs的文章 xff0c 比较简洁 http www yuanma org data 20
  • 高质量编程之编译警告级别

    前 言 作为程序员不但要会编程 xff0c 还要编好程 xff0c 即编写高质量的程序 评价程序质量的指标有很多 正确性 可靠性 有效性 可扩展性 可维护性 xff0c 用于保证软件质量的方法和技巧也非常多 本篇只讲述在编码阶段 xff0c
  • SSO - CAS-5.3.x服务端一些常规配置(登出操作后跳转制定页面;增加多个用户名密码)

    登出操作后跳转制定页面 首先跳转cas登出url时 xff0c 要加上 service 61 你的制定的页面 xff0c 如下 xff1a http localhost 8080 cas logout service 61 https ww
  • 大 学 十 年

    大 学 十 年 林锐 xff0c 1999年岁末 写此文使我很为难 xff0c 一是担心读者误以为我轻浮得现在就开始写自传 xff0c 二是担心朋友们误以为我得了绝症而早早留下遗作 不论是落俗套还是不落俗套地评价 xff0c 我在大学十年里
  • C/C++刁钻问题各个击破之序言

    是程序员都会写C C 43 43 程序 这是不是就说明C C 43 43 比较容易掌握呢 xff1f 非也 xff01 相比其他编程语言来说C C 43 43 要庞大得多 复杂得多 xff0c 要想用好C C 43 43 不是易事 我用C编
  • C/C++刁钻问题各个击破之位运算及其应用实例(1)

    位运算及其应用实例 1 摘要 位运算是C C 43 43 中的基本运算之一 xff0c 即便是这样 xff0c 它对大多数程序员来说是一个比较陌生的运算 大多数程序员很少使用位运算 本篇先简要介绍基本的位运算操作符及其用法 何时使用 xff
  • c_c++刁钻问题各个击破之位运算及其实例(2)

    位运算及其应用实例 2 摘要 上一篇 C C 43 43 刁钻问题各个击破之位运算及其应用实例 1 受到不少网友的关注 xff0c 这对我鼓励极大 xff0c 首先感谢这些网友 xff0c 特别要感谢几位回复的热心网友 上一篇中我对位运算进
  • 一步一步求解约瑟夫(Joseph)问题

    约瑟夫 Joseph 问题的解决方法 问题描述 最初Joseph问题是这样的 xff1a 有n个人 分别编号1 2 n 围成一圈 xff0c 这些人轮流数数 编号为1的人开始 xff0c 从1开始数 xff0c 当数到m的人就会被处决 接着
  • 给力!简单!易懂!位运算之求集合的所有子集

    摘要 刚刚完成一篇利用位运算高效地 巧妙地来解决求组合的博文 xff1a 非常给力 xff1a 位运算求组合 巧合的是 xff0c 我在 数据结构算法与应用 一书中看到一道课后题是 xff1a 用递归实现求一个集合的所有子集 受到题目的要求
  • 一种复杂度为O(n)的排序算法:位操作应用之位排序

    位运算应用篇 3 摘要 本篇仍然关注位操作的应用 xff0c 通过前面的两篇文章 非常给力 xff1a 位运算求组合 xff0c 简单 易懂 xff1a 位运算之求集合的所有子集 xff0c 我们已经略见了位操作之强大威力 如果说那两篇文章