在这个C代码中abs()的用途是什么

时间:2012-01-24 12:24:23

标签: c

我有一段代码可以找出大小为n的数组中的重复元素,其中元素满足1 <= arr[i] <= n,代码如下:

#include<stdio.h>
#include<stdlib.h>

void printTwoElements(int arr[], int size)
{
    int i;
    printf("\n The repeating element is");

    for(i = 0; i < size; i++)
    {
        if(arr[abs(arr[i])-1] > 0)
        {
            arr[abs(arr[i])-1] = -arr[abs(arr[i])-1];
        }
        else
        {
            printf(" %d ", abs(arr[i]));
        }
    }
}

/* Driver program to test above function */
int main()
{
    int arr[] = {7, 3, 4, 5, 5, 6, 2};
    int  n = sizeof(arr)/sizeof(arr[0]);
    printTwoElements(arr, n);
    return 0;
}

我想知道在这个给定的代码中使用abs()吗?

4 个答案:

答案 0 :(得分:4)

在算法过程中,一些数组条目被设置为负值作为标记。因此,当条目用作数组的索引时,必须采用条目的绝对值。

希望不破坏任何东西:

该算法要求n - 元素数组的数组条目都在1和n之间。 如果任何条目大于n或小于-n或0,则它将访问无效地址,如果任何元素为负,则标记逻辑将失败。

算法的逻辑是:

for each array element e:
    if the value at (e-1) is positive, e has not yet been seen,
                   negate the value at (e-1) to mark e as seen
    otherwise, e has already been seen, so print it

因此,由于在运行算法的过程中数组条目变为负数,因此必须采用绝对值来获得有效的索引。

让我们按照修改示例的算法来看看它是如何工作的:

before: arr = { 7, 3, 4, 5, 5, 3, 2}
i == 0: arr[0] = 7
        arr[7-1] is 2 > 0 ~> negate
        arr = { 7, 3, 4, 5, 5, 3, -2}
i == 1: arr[1] = 3
        arr[3-1] is 4 > 0 ~> negate
        arr = { 7, 3, -4, 5, 5, 3, -2}
i == 2: arr[2] is -4 ~> abs for indexing
        arr[4-1] is 5 > 0 ~> negate
        arr = { 7, 3, -4,-5, 5, 3, -2}
i == 3: arr[3] is -5 ~> abs for indexing
        arr[5-1] is 5 > 0 ~> negate
        arr = { 7, 3, -4, -5, -5, 3, -2}
i == 4: arr[4] is -5 ~> abs for indexing
        arr[5-1] is -5 < 0 ~> print abs(-5) as duplicate
i == 5: arr[5] is 3
        arr[3-1] is -4 < 0 ~> print abs(3) as duplicate
i == 6: arr[6] is -2 ~> abs for indexing
        arr[2-1] is 3 > 0 ~> negate
        arr = { 7, -3, -4, -5, -5, 3, -2}

        indices of positive entries: 0, 5 ~> 1 and 6 not in original array
        indices of negative entries: 1, 2, 3, 4, 6 ~> 2, 3, 4, 5, 7 in original array

答案 1 :(得分:1)

如果你已经开始用最直接的方法来解决这个问题,给定额外的O(n)空间,你可能会想到将“已经遇到”的标志存储在一个单独的(辅助)数组中以便以后查找它们: / p>

// pseudocode (zero based indexing ignored for now)
for each value in array
   if (already_encountered[value] == true)
       print "found a duplicate of " + value
   else
       already_encountered[value] = true      

你的算法更进一步。鉴于整数(可能)是32位,并且您只需要存储有限(小)范围的值,每个数组成员实际上都有足够的空闲位来存储“已经遇到的” “旗帜。

这意味着您可以删除上面给出的辅助already_encountered数组,并使用此额外空间来存储无额外内存分配的标记。

使用一些比特,你可以选择将这个值存储在最高位(留下31位来存储你的数字)。每当你想检查标志是否设置时,你需要提取这个最高位并检查它,然后在打印出值之前最后清除该位:

// pseudocode (zero based indexing ignored)
for each value in array
{
   // this value might already have a flag previously encoded
   // for some other element, so remove it before continuing
   plain_value = remove_flag(value)

   // check if the flag is set for the actual value,
   // if not, set it now
   if (is_flag_set(array[plain_value]) == true)
       print "found a duplicate of " + plain_value
   else
       array[plain_value] = set_flag(array[plain_value])
}

唯一要做的就是定义set_flagis_flag_setremove_flag函数。

在你的情况下,算法通过否定值来“设置标志”,通过检查值是否为负来“测试标志”,并使用绝对值“删除标志” (因此abs函数)。

这可以安全地实现,因为有符号整数使用单个位来存储它们的符号信息,允许转换保持原始值不变(只要它足够小)。

这将为您留下最终的C代码:

void printTwoElements(int arr[], int size)
{
    int i;
    printf("\n The repeating element is");

    for(i = 0; i < size; i++)
    {
        // remove the flag
        int plain_value = abs(arr[i]);

        // is flag set?
        if(arr[plain_value-1] < 0)
        {
            printf(" %d ", plain_value);
        }
        else
        {
            // set the flag by negating
            arr[plain_value-1] = -arr[plain_value-1];                
        }
    }
}

答案 2 :(得分:0)

它试图确保程序在遍历元素时从不尝试负数组索引。

答案 3 :(得分:0)

abs()是C中的绝对值函数。它确保您的数组索引不是负数。