IT蘑菇

记录学习每一点滴


  • 首页

  • 归档

  • 标签

  • 搜索

如何快速判断正整数是2的N次幂

发表于 2017-04-07   |   分类于 程序文档

这个问题可能很多面试的人都遇到过,很多人可能想利用循环来判断,代码可能如下所示:

public static boolean isPowOfTwo(int n){
    int temp = 0;
    for (int i = 1;; i++){
        temp = (int)Math.pow(2,i);
        if (temp >= n)
            break;
    }
    if (temp ==n) return true;
    else return false;
}

  上面的代码简单明了。但是,这样的方案效率比较低。我们仔细分析一下,正整数是2的n次幂他有什么规律?2º=1,2¹=2,2²=4,2³=8....这样看是没有什么规律的。但是如果将2的幂次方写成二进制形式后,很容易就会发现有以下两个特点:

1、2º=1 -> 0001,2¹=2 ->,2²=4 -> 0100,2³=8 -> 1000 二进制中只有一个1,并且1后面跟了n个0.
2、如果将这个数减去1后会发现,仅有的那个1会变为0,而原来的那个n个0会变为1;因此将原来的数与去减去1后的数字进行与运算后会发现为零((x & x-1)==0)。
原因:因为2ⁿ换算是二进制为10……0这样的形式,2ⁿ-1的二进制为0111…1,两个二进制求与结果为0,例如:16的二进制为10000;15=01111,两者相与的结果为0.计算如下:

10000 & 01111
-------
00000

所以可以用下面的算法实现

public static boolean isPowerOfTwo(int x){
    return x > 0 & (x & (x - 1)) ==0;
}

很简单的一行代码就实现了。细心的读者可能会问:2的n次幂二进制始终都是只有一个1,其它的位数都为0,是否可以判断给定的数转换为二进制来判断其中是否只有1个1来得出给定数是否为2的n次幂呢?答案是不能。因为Integer.MIN_VALUE的二进制只有1个1,但是Integer.MIN并不是2的n次幂,所以不能用上面的方法来实现

(完)

转载自:
https://www.iteblog.com/archives/716.html

原码, 反码, 补码 详解

发表于 2017-04-06   |   分类于 程序文档

原文链接:
http://www.cnblogs.com/zhangziqiu/archive/2011/03/30/ComputerCode.html
作者:张子秋
出处:http://www.cnblogs.com/zhangziqiu/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。


一. 机器数和真值

机器数

一个数在计算机中的二进制表示形式, 叫做这个数的机器数。机器数是带符号的,在计算机用一个数的最高位存放符号, 正数为0, 负数为1.
比如,十进制中的数 +3 ,计算机字长为8位,转换成二进制就是00000011。如果是 -3 ,就是 10000011 。
那么,这里的 00000011 和 10000011 就是机器数。

真值

因为第一位是符号位,所以机器数的形式值就不等于真正的数值。例如上面的有符号数 10000011,其最高位1代表负,其真正数值是 -3 而不是形式值131(10000011转换成十进制等于131)。所以,为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值。

例如:

0000 0001的真值 = +000 0001 = +1,
1000 0001的真值 = -000 0001 = -1

二. 原码, 反码, 补码的基础概念和计算方法

原码

原码就是符号位加上真值的绝对值,即用第一位表示符号,其余位表示值,比如如果是8位二进制:

[+1]原 = 0000 0001
[-1]原 = 1000 0001

因为第一位是符号位,所以8位二进制的取值范围就是:

[1111 1111,0111 1111]

即

[-127,127]

原码是人脑最容易理解和计算的表示方式。

反码

反码的表示方法是:
正数的反码是其本身
负数的反码是在其原码的基础上,符号位不变,其余各个位取反:

[+1] = [0000 0001]原 = [0000 0001]反
[-1] = [1000 0001]原 = [1111 1110]反

可见如果一个反码表示的是负数,人脑无法直观的看出来它的数值,通常要将其转换成原码再计算。

补码

补码的表示方法是:正整数的二进制补码与其二进制原码相同,负整数的二进制补码,先求与该负数相对应的正整数的二进制代码,然后所有位取反加1,不够位数时左边补1,例如:

[+1] = [0000 0001]原 = [0000 0001]反 = [0000 0001]补
[-1] = [1000 0001]原 = [1111 1110]反 = [1111 1111]补

对于负数,补码表示方式也是人脑无法直观看出其数值的,通常也需要转换成原码再计算其数值。

三. 为何要使用原码, 反码和补码

现在我们知道了计算机可以有三种编码方式表示一个数. 对于正数因为三种编码方式的结果都相同:

[+1] = [00000001]原 = [00000001]反 = [00000001]补

所以不需要过多解释. 但是对于负数:

[-1] = [10000001]原 = [11111110]反 = [11111111]补

可见原码, 反码和补码是完全不同的. 既然原码才是被人脑直接识别并用于计算表示方式, 为何还会有反码和补码呢?

首先, 因为人脑可以知道第一位是符号位, 在计算的时候我们会根据符号位, 选择对真值区域的加减. (真值的概念在本文最开头). 但是对于计算机, 加减乘数已经是最基础的运算, 要设计的尽量简单. 计算机辨别"符号位"显然会让计算机的基础电路设计变得十分复杂! 于是人们想出了将符号位也参与运算的方法. 我们知道, 根据运算法则减去一个正数等于加上一个负数, 即: 1-1 = 1 + (-1) = 0 , 所以机器可以只有加法而没有减法, 这样计算机运算的设计就更简单了.

于是人们开始探索 将符号位参与运算, 并且只保留加法的方法. 首先来看原码:

计算十进制的表达式: 1-1=0

1 - 1 = 1 + (-1) = [00000001]原 + [10000001]原 = [10000010]原 = -2

如果用原码表示, 让符号位也参与计算, 显然对于减法来说, 结果是不正确的.这也就是为何计算机内部不使用原码表示一个数.

为了解决原码做减法的问题, 出现了反码:

计算十进制的表达式: 1-1=0

1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原= [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0

发现用反码计算减法, 结果的真值部分是正确的. 而唯一的问题其实就出现在"0"这个特殊的数值上. 虽然人们理解上+0和-0是一样的, 但是0带符号是没有任何意义的. 而且会有[0000 0000]原和[1000 0000]原两个编码表示0.

于是补码的出现, 解决了0的符号以及两个编码的问题:

1-1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]补 + [1111 1111]补 = [0000 0000]补=[0000 0000]原

这样0用[0000 0000]表示, 而以前出现问题的-0则不存在了.而且可以用[1000 0000]表示-128:

(-1) + (-127) = [1000 0001]原 + [1111 1111]原 = [1111 1111]补 + [1000 0001]补 = [1000 0000]补

-1-127的结果应该是-128, 在用补码运算的结果中, [1000 0000]补 就是-128. 但是注意因为实际上是使用以前的-0的补码来表示-128, 所以-128并没有原码和反码表示.(对-128的补码表示[1000 0000]补算出来的原码是[0000 0000]原, 这是不正确的)

使用补码, 不仅仅修复了0的符号以及存在两个编码的问题, 而且还能够多表示一个最低数. 这就是为什么8位二进制, 使用原码或反码表示的范围为[-127, +127], 而使用补码表示的范围为[-128, 127].

因为机器使用补码, 所以对于编程中常用到的32位int类型, 可以表示范围是: [-231, 231-1] 因为第一位表示的是符号位.而使用补码表示时又可以多保存一个最小值.

Android小笔记(一些方法)

发表于 2017-04-02   |   分类于 Android , 程序文档

本篇不作任何阅读以及用处,只作索引!


AndroidManifest.xml

活动注册需要放在 <application> 标签内,通过 <activity> 标签对活动进行注册。

  • android:name 来指定具体注册的活动,**填写具体包名下的某活动文件名称。由于 <manifest>
    已完成指定故此名称处只需要填写“.**”(一个点替代前面的一长串包名+活动名)
  • android:lable参数可以对指定活动中的标题栏进行命名。如果是给主活动指定 lable
    ,还会同时成为启动器(Launcher)中应用程序显示的名称。
<activity android:name="**"><activity>

AndroidManifest.xml文件中,在 <activity> 标签内,有以下 <intent-filter> 标签,用于配置程序主活动:

<intent-filter>
     <action android:name="android.intent.action.MAIN"/>
     <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>

onCreate

onCreate的方法是在Activity创建时被系统调用,是一个Activity生命周期的开始。

public void onCreate (Bundle savedInstanceState){
       super.onCreate(savedInstanceState);
       setContentView(R.layout.**);
}

setContentView(R.layout.**)

用 setContentView() 方法给当前的活动加载一个布局,而在 setContentView() 方法中,我们一般会传入一个布局文件的id。项目中添加的任何资源都会在R文件中生成一个id,因此之前的布局文件**.xml已经添加到R文件中了。

findViewById(R.id.**)

获取 xml 布局文件中的具体元素/ widget 控件(如:Button、TextView 等),传入 R.id. ,来获得的实例,这个值则是在xml中通过android:id属性指定的。

setOnClickListener()

经常碰到代码,比如

public class MainActivity extends AppCompatActivity {
      private Button mButton;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                
            }
        });
    }
}

//onClicklistener是一个接口,不能实例化,这就是一个匿名内部类,这个类实现onClickListener
//然后被new 了,无形中传了一个对象进去,这个对象给了button/TextView中的mOnClicklistener,就是这家伙调用了onClick方法
//当然要是Activity实现了这个接口,就可以setonClickListener(this)
//this是当前类的一个对象
//传进去一个Activity对象给monclickListener,就是一个接口引用指向Activity对象,不过接口引用只能调用自己的方法

  • 该方法解释原文出处

makeText()

public static Toast makeText (Context context, int resId, int duration)

context:使用上下文。通常您的应用程序或Activity对象。
resId:使用string资源的id去调用,或者可以使用格式化的文本。
duration:显示信息时间的长短。可以是length_short或length_long

onCreateOptionsMenu(Menu menu)

public boolean onCreateOptionsMenu(Menu menu) {
    /**
     * 此方法用于初始化菜单,其中menu参数就是即将要显示的Menu实例。 返回true则显示该menu,false 则不显示;
     * (只会在第一次初始化菜单时调用) Inflate the menu; this adds items to the action bar
     * if it is present.
     */
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
}

初识Java(类与对象)

发表于 2017-03-30   |   分类于 Java , 程序文档

C语言

C的格式

在这边依然得扯扯我们曾经的C语言,以下为C语言的主函数与子函数:

#include <stdio.h>    //导入C语言的输入输出函数库
int main()            //主函数
{                     //函数体开始(一个作用域)
    语句;             //程序语句
}                     //函数体结束

void max()            //子函数
{
    语句;
}

一个C源文件中可以包含1个主函数与无数个子函数。
首先以上的代码很好理解,第一句是导入一些必要的函数库。然后第二行开始使用的是 main()方法,我们都知道 main()方法是函数执行的入口,作用范围是函数体 {},最后一个函数体结束的大括弧就是函数的出口,退出结束的意思。任何程序的执行都是从主函数开始,主函数结束。

main()方法 一切程序运行时候使用的主要方法,程序的运行从主方法进,主方法出。

Java中的方法

相信大家在看上述内容的时候发现,明明说好了不是函数吗?主函数、子函数,怎么说着说着就变成 main()方法了?
没错!如果你有所察觉,那么就代表你已经知道了,Java中的方法相当C语言的函数,Java语言提供了大家非常多的方法实现各种各样的功能,我们知道C语言以及C++语言主要研究的是算法,学过C的同学都知道有些算法可能过于复杂,不过在Java中各位就不必考虑这些问题了,因为当你需要使用到某一方法的时候,调用该方法来完成(实现)就可以了。

算法与方法

什么是算法?
我个人简单的理解就是具有实现某一功能或者说实现某一方法的作用。
什么是方法?
具有实现各种功能的方法,方法中包含算法,而我们实际面向对象程序设计中只需要调用对应方法来完成某一项功能即可,也有可能调用各种方法来共同实现。
所以说从C语言转到Java,我们非常快的理解了Java的方法。

类
-
什么是类?
一般人,一般书都会先解释什么是类,但我并不认为需要先给你解释。

方法-->类

由于我们先知道了方法,所以现在要做的就是方法转类。假如现有如下9个方法:

吃、减、喝、求余、玩、乐、加、乘、除

看到以上9个方法,请问各位第一印象如何?是不是觉得很乱?
如果你有这种感觉,就说明了你已经了解了什么东西。故此,我们现状要对这九个方法进行归类,归类后的结果如下:

吃喝玩乐 归为一类
加减乘除求余 归为一类

这样看上去会不会比之前的好看一点了?如果你有这种感觉,你就会发现,当方法一多起来的时候,你就会觉得很乱,你就会需要迫切给这些方法进行归类。


class

归类之前,先把这些假设的例子,写成真正的方法形式吧,再进行归类操作。反正不差那几笔!方法名字我就用对应的英文名来取

public class Life{
    eat(){
    ……
    }
    drink(){
    ……
    }
    play(){
    ……
    }
    happy(){
    ……
    }
}

public class Math{
    addition(){
    ……
    }
    subtraction(){
    ……
    }
    multiplication(){
    ……
    }
    division(){
    ……
    }
    remainder(){
    ……
    }
}

好了,我已经将他们分类好了,你是不是会觉得很奇怪?为什么又多出来了那么多东西呢?
实际上可以用最简单的视图来看待上述的分类,实际上是分类就是在方法的外边加上一个类,就如下面所示:

class {

}

方法就放在上面的class的作用域里面,作用域就是指 {} 之间的区域
那么之前的分类好的代码又是怎么回事呢?为什么多出来那么多东西?实际上格式如下:

修饰符 class 类名 {

}

于是你又得问什么是修饰符?
答:我只能先告诉你,上述的修饰符 public 的意思是“公共的”,有公共的,自然就有私有的,甚至还有其他更多种类的。
首先在C语言里面,我们在主函数中调用一个子函数,比如开头部分的,我在 int main() 方法里的某一处地方要求最大值,而刚好有一个子函数是来处理最大值的,它就是max(),那我只需要在 int main() 方法中写出 max() 自然就调用了该函数。
那么同样的道理,java里面必须要给方法分类,没有类他是错误的!,所以说任何的方法都要进行归类,没有找到自己的家怎么行?那么有了家就有了什么?大家好好想想,是不是“门”和“窗”?如果你想到了就代表你已经入门了,算一个合格的Java新星。
有门和窗的话,那要怎么进行调用?方法如下(在A类中调用B类中的方法)

public class A{
    B volu;   //volu是变量,B是数据类型
    volu = new B();    //给变量volu赋值
}
public class B{
    eat(){
    ……
    }
}

你会觉得很奇怪为什么将B类的类名拿来做数据类型,其实它叫做对象。然后给这个叫做“B对象”的变量volu赋值,WTF?奇怪,new B();是什么意思?正如你所看到的字面意思!
new 就是新建一个的意思,B()是不是有点像方法的感觉?其实没错,他就是一个方法,只不过是个特殊的方法,而这个方法的作用就是开辟一个属于“B类”的栈内存。假如A类里面有主方法,那么我们知道程序是从主方法开始,主方法结束。不会去执行其他的类或者方法,而且一个源文件里面只有一个是主方法,其他的方法将不被执行,所以,我们需要在主方法中调用其他类的方法,就需要先给其他的类开辟一个内存空间,让那个类活起来!
于是现在终于可以调用了,但是具体的调用形式分具体的情况而定,如果eat()方法只是输出一句话,A类包含主方法,则可以之家以如下格式调用:

public class A{
    public static void main(String[] args){
        B volu;
        volu = new B();
        volu.eat();    //B类的eat()方法
    }
}
public class B{
    eat(){
        System.out.println("你好!");
    }
}

结果就是在控制台上显示“你好”。而上述格式中,A里面的public static void main(String[] args){} 便是Java中的主方法的表示方法,程序的起始入口,程序从这进从这结束。在主方法中调用其他类的方法就如上所示。他们在同一个 .java源文件中!


  • 本篇文章由“IT蘑菇”原创编辑,未经作者允许,谢绝转载!

初识Java篇(数据结构)

发表于 2017-03-30   |   分类于 Java , 程序文档

学过C语言的同学,大家都对算法有一定的了解,也对基本的数据结构也有所了解。现在也将通过学习Java来对方法进行一定的了解!

稍微复习一下:

数据类型

整型:byte、short、int、long
浮点型:float、double
逻辑型(布尔型):boolean
字符型:char

变量

变量声明格式:
int i,j,k;    //表示同时声明了3个变量
变量初始化:
int i=0;    //表示i是int型变量且初始值为0

数据类型转换

不同的数据类型有分为“默认类型转换”与“强制类型转换”两种。这里主要说明的是“强制类型转换”如下:

int a=15;
int b=9;
float h;
……
h=(float)a/b;

强制类型的转换方法很简单,就是在要转的表达式或者变量之前加上一个英文括号,里面填上对应的数据类型就好。

运算符与表达式

算术运算符:+、-、*、/、%、++、--。
关系运算符:>、<、>=、<=、==、!=。
逻辑运算符:!、&&、||、&、|。
位运算符:  >>、<<、>>>、&、|、^、~。
赋值运算符:=、扩展赋值运算符,如+=、/=等。
条件运算符(三目运算符):? :。
……

流程控制

if 条件语句:

if(条件表达式){
语句1;
}
else{
语句2;
}

多条 if 语句:

if(条件表达式){
    语句1;
}
else if{
    语句2;
}
else if{
    语句n;
}
else{
    语句n+1;
}

switch 选择语句:

switch(表达式){
    case 常量表达式1:
         语句1;
         break;
    case 常量表达式2:
         语句2;
         break;
          ……
    case 常量表达式n:
         语句n;
         break;
    defaule:
         语句n+1;
}

循环结构

while 循环:

while(条件表达式){
循环体
}

do-while 循环:

do{
循环体
}
while(条件表达式);

for 循环:(重点)

for(表达式1,表达式2,表达式3){
循环体
}

循环中的跳出语句:

break;    //结束整个循环
continue;    //结束当前循环,只跳过1次循环,一般用if语句判断

数组

一维数组:

数据类型[] 数组名;            //声明一维数组
数组名 = new 数据类型[个数];        //分配内存给数组

下面举例,给数组定义:

int[] x;    //声明名称为x的 int 型数组
x = new int[10];    //x数组中包含有10个元素,并为这10个元素分配内存空间

语句合并:

数据类型[] 数组名 = new 数据类型[个数];

对此上面举例的例子可以写为:

int[] x = new int[10];

多维数组:
格式如下:

数据类型[][] 数组名;
数组名 = new 数据类型[行数][列数];

举个例子:

int[][] a;    //声明整型数组a
a = new int[3][4];    //分配一块内存空间,供3行4列整型数组a使用

回顾的差不多了,是否觉得和C语言相比有种似曾相识的感觉呢?

123456
⇞⇞móōōō♂

⇞⇞móōōō♂

期待着未来的自己感谢现在的自己

29 文章
13 分类
42 标签
友情链接
hluglk
© 2021 IT蘑菇  闽ICP备17005404号-1
Typecho
主题 - NexT.Pisces