整数除法不使用/或*运算符

时间:2015-12-29 07:29:51

标签: algorithm

我正在阅读算法和数据结构教科书,并提出了这个问题:

  

1-28。编写一个函数来执行整数除法而不使用   或者是/或*运算符。找到一种快速的方法。

我们怎样才能想出一个快速的方法呢?

7 个答案:

答案 0 :(得分:8)

我喜欢这个解决方案:https://stackoverflow.com/a/34506599/1008519,但我觉得有点难以推理(特别是| - 部分)。这个解决方案在我的头脑中更有意义:

var divide = function (dividend, divisor) {
  // Handle 0 divisor
  if (divisor === 0) {
    return NaN;
  }

  // Handle negative numbers
  var isNegative = false;
  if (dividend < 0) {
    // Change sign
    dividend = ~dividend+1;
    isNegative = !isNegative;
  }

  if (divisor < 0) {
    // Change sign
    divisor = ~divisor+1;
    isNegative = !isNegative;
  }

  /**
   * Main algorithm
   */

  var result = 1;
  var denominator = divisor;
  // Double denominator value with bitwise shift until bigger than dividend
  while (dividend > denominator) {
    denominator <<= 1;
    result <<= 1;
  }

  // Subtract divisor value until denominator is smaller than dividend
  while (denominator > dividend) {
    denominator -= divisor;
    result -= 1;
  }

  // If one of dividend or divisor was negative, change sign of result
  if (isNegative) {
    result = ~result+1;
  }

  return result;
}
  1. 我们将结果初始化为1(因为我们要将分母加倍,直到它大于股息)
  2. 将我们的分母(按位移)加倍,直到它大于被除数
  3. 既然我们知道我们的分母比我们的股息更大,我们可以减去我们的除数,直到它低于我们的股息
  4. 返回结果,因为分母现在使用除数
  5. 尽可能接近结果

    以下是一些测试运行:

    console.log(divide(-16, 3)); // -5
    console.log(divide(16, 3)); // 5
    console.log(divide(16, 33)); // 0
    console.log(divide(16, 0)); // NaN
    console.log(divide(384, 15)); // 25
    

    以下是解决方案的要点:https://gist.github.com/mlunoe/e34f14cff4d5c57dd90a5626266c4130

答案 1 :(得分:2)

通常,当算法教科书说 fast 时,它们意味着计算复杂性。也就是说,每位输入的操作次数。一般来说,它们不关心常量,所以如果输入 n 位,无论每位需要两次操作还是每位操作一百次,我们都说算法需要O(< em> n )时间。这是因为如果我们有一个在O( n ^ 2 )时间运行的算法(多项式......在这种情况下, square 时间),我们想象一个O( n )算法,与我们的算法相比,每位执行100次运算,每次运算可执行1次运算,一旦输入大小为100位,多项式算法开始运行的速度非常快(与我们相比)其他算法)。从本质上讲,您可以设想两行,y=100xy=x^2。你的老师可能会让你在代数中练习(也许是微积分?),你必须说明哪一个更大,因为 x 接近无穷大。这实际上是微积分中分歧/收敛的一个关键概念,如果你已经在数学中已经到了那里。无论如何,使用小代数,您可以想象我们的图表在x=100处交叉,而y=x^2对于 x 大于100的所有点都更大。

就大多数教科书而言,O( nlgn )或更好被认为是“快”。解决这个问题的一个非常糟糕的算法的例子如下:

crappyMultiplicationAlg(int a, int b)
    int product = 0
    for (b>0)
        product = product + a
        b = b-1
    return product

该算法基本上使用“b”作为计数器,并且每次b倒计时只是在某个变量上添加“a”。为了计算算法的“快速”程度(根据算法复杂度),我们计算不同组件将采用多少次运行。在这种情况下,我们只有一个for循环和一些初始化(在这种情况下可忽略不计,忽略它)。 for循环运行了多少次?你可能会说“嘿,伙计!它只运行'b'次!这甚至可能不是一半输入。那是比O更好 n )时间!“

这里的诀窍是,我们关注存储方面输入的大小 ...我们都(应该)知道要存储一个 n 位整数,我们需要lg n 位。换句话说,如果我们有 x 位,我们可以将任何(无符号)数存储到(2 ^ x)-1。因此,如果我们使用标准的4字节整数,那么这个数字最多可以达到2 ^ 32 - 1,如果我的记忆能够正常使用,这个数字可以达到数十亿。如果您不相信我,请使用10,000,000这样的数字运行此算法,看看需要多长时间。还是不相信?使用long使用1,000,000,000之类的数字。

由于你没有请求算法的帮助,我会把它留给你作为一个家庭作业练习(不是试图成为一个混蛋,我是一个极端的极客和爱算法问题)。如果您需要帮助,请随时提出!我已经意外地输入了一些提示,因为我最初没有正确地阅读你的问题。

编辑:我偶然做了一个糟糕的乘法算法。 真正可怕的划分算法(我被骗)的一个例子是:

AbsolutelyTerribleDivisionAlg(int a, int b)
    int quotient = 0
    while crappyMultiplicationAlg(int b, int quotient) < a
        quotient = quotient + 1
    return quotient

这个算法很糟糕,原因很多,其中最重要的是使用我糟糕的乘法算法(即使在相对“驯服”的运行中,它也会被称为不止一次 )。即使我们被允许使用*运算符,这仍然是一个非常糟糕的算法,很大程度上是由于我糟糕的算法中使用了相同的机制。

PS我的两个algs中可能存在一个或两个围栏错误...我发布它们的概念清晰度而不是正确性。然而,无论它们在进行乘法或除法方面有多准确,都不要使用它们。他们会给你的笔记本电脑疱疹,然后让它燃烧起来,让你感到悲伤。

答案 2 :(得分:1)

我不知道你的意思是什么......这似乎是测试你的思维过程的一个基本问题。

一个简单的函数可以使用一个计数器,并从除数中减去除数,直到它变为0.这是O(n)过程。

int divide(int n, int d){
    int c = 0;
    while(1){
        n -= d;
        if(n >= 0)
            c++;
        else
            break;
    }
    return c;
}

另一种方法是使用shift运算符,它应该在log(n)步骤中执行。

    int divide(int n, int d){
    if(d <= 0)
        return -1;
    int k = d;
    int i, c, index=1;
    c = 0;
    while(n > d){
        d <<= 1;
        index <<= 1;
    }
    while(1){
        if(k > n)
            return c;
        if(n >= d){
            c |= index;
            n -= d;                
        }
        index >>= 1;
        d >>= 1;
    }
    return c;
}

这就像我们在高中数学中所做的那样整数除法。

PS:如果你需要更好的解释,我会的。请在评论中发帖。

编辑:编辑了Erobrere评论的代码。

答案 3 :(得分:1)

执行除法的最简单方法是连续减法:只要b保持正数,就从a减去a。商是执行的减法数。

这可能非常慢,因为您将执行q减法和测试。

使用a=28b=3

28-3-3-3-3-3-3-3-3-3=1

商为9,其余为1

想到的下一个想法是一次性减去b几次。我们可以尝试2b4b8b ...因为这些数字很容易通过添加进行计算。只要b的倍数不超过a,我们就可以尽可能使用。

在该示例中,2³.3是可能的最大倍数

28>=2³.3

所以我们一次性减去8次3,得到

28-2³.3=4

现在我们继续使用较低的倍数21来减少剩余部分

4-2².3<0
4-2.3 <0
4-1.3 =1

然后我们的商为2³+1=9,其余为1

您可以检查,b的每个倍数仅尝试一次,并且总尝试次数等于达到a所需的倍数。这个数字只是写q所需的位数,它比q本身小得多。

答案 4 :(得分:0)

这不是最快的解决方案,但我认为它足够可读并且可以正常工作:

print(df1)
names  ids     P1    P2      t
0        1  0.349  0.12  0.008
1        2  0.349  0.12  0.008
2        3  0.349  0.20  0.209
3        4  0.349  0.20  0.209

print(ids)
[[1, 2], [3, 4]]

它显示以下结果:

def weird_div(dividend, divisor):
    if divisor == 0:
        return None

    dend = abs(dividend)
    dsor = abs(divisor)
    result = 0
    
    # This is the core algorithm, the rest is just for ensuring it works with negatives and 0
    while dend >= dsor:
        dend -= dsor
        result += 1
    
    # Let's handle negative numbers too
    if (dividend < 0 and divisor > 0) or (dividend > 0 and divisor < 0):
        return -result
    else:
        return result

# Let's test it:
print("49 divided by 7 is {}".format(weird_div(49,7)))
print("100 divided by 7 is {} (Discards the remainder) ".format(weird_div(100,7)))
print("-49 divided by 7 is {}".format(weird_div(-49,7)))     
print("49 divided by -7 is {}".format(weird_div(49,-7)))   
print("-49 divided by -7 is {}".format(weird_div(-49,-7)))   
print("0 divided by 7 is {}".format(weird_div(0,7)))         
print("49 divided by 0 is {}".format(weird_div(49,0)))

答案 5 :(得分:-1)

unsigned bitdiv (unsigned a, unsigned d)
{
unsigned res,c;

for (c=d; c <= a; c <<=1) {;}

for (res=0;(c>>=1) >= d; ) {
        res <<= 1;
        if ( a >= c) { res++; a -= c; }
        }
return res;
}

答案 6 :(得分:-2)

伪代码:

count = 0
while (dividend >= divisor) 
    dividend -= divisor
    count++
//Get count, your answer
相关问题