《编程之美》简单算法小结


第一题: 1 的数目

给定一个十进制正整数N, 写下从1开始,到N的所有整数,然后数一下其中出现所有“1”的个数,即求f(N)。


例如:
N=2, 写下1,2,。 出现1个1;

N=13, 我们写下:1,2,3,4,5,6,7,8,9,10,11,12,13,出现1个的个数是6.


求解函数f(N),即返回1 到N之间出现的 1 的个数,如f(13)=6.


ULONGLONG COuntInAteger(ULONGLONG n)    //求解一个数(如11)中1的个数
{
ULONGLONG iNum = 0;
while(n!=0)
{
	iNum=iNum+(n%10==1)?1:0;
	n=n/10;
}
return iNum;


}


ULONGLONG f(ULONGULONG n)              //用for循环逐个求解
{
ULONGLONG iCount=0;
for(ULONGLONG i=1;i <= n; i++)
	{
		iCount=iCount+COuntInAteger(i);
	}

return iCount;

}


第二题: 求二进制数中1 的个数

对于一个字节(8bit)的无符号整型变量,求其二进制表示中“1”的个数,要求算法的执行效率尽可能高。


int Count(BYTE v)
{
int num=0;
while(v)
  {
    if(v % 2 ==1)
     {
       num++;
     }
    v=v/2;
}
return num;

}


int Count(BYTE v)
{
int num=0;
while(v)
  {
    v=v&(v-1);
    num++;
   }
return num;
}


第三题: 最大公约数问题

写一个程序,求两个正整数的最大公约数。如果两个正整数都很大,有什么简单的方法吗?



求最大公约数是一个很基本的问题。早在公元前300年左右,欧几里得就在他的著作《几何原本》中给出了高效的解法——辗转相除法。辗转相除法使用到的原理很聪明也很简单,假设用f(x, y)表示x,y的最大公约数,取k= x/y,b = x%y,则x = ky + b,如果一个数能够同时整除x和y,则必能同时整除b和y;而能够同时整除b和y的数也必能同时整除x和y,即x和y的公约数与b和y的公约数是相同的,其最大公约数也是相同的,则有f(x, y)=f(y, y % x)(y > 0),如此便可把原问题转化为求两个更小数的最大公约数,直到其中一个数为0,剩下的另外一个数就是两者最大的公约数。辗转相除法更详细的证明可以在很多的初等数论相关书籍中找到,或者读者也可以试着证明一下。
示例如下:
f(42, 30)=f(30, 12)= f(12, 6)=f(6, 0)= 6
【解法一】
最简单的实现,就是直接用代码来实现辗转相除法。从上面的描述中,我们知道,利用递归就能够很轻松地把这个问题完成。
具体代码如下:
int gcd(int x, int y)
{
    return (!y) ?  x  :  gcd( y,  x%y ) ;
}


【解法二】


在解法一中,我们用到了取模运算。但对于大整数而言,取模运算(其中用到除法)是非常昂贵的开销,将成为整个算法的瓶颈。有没有办法能够不用取模运算呢?

采用类似前面辗转相除法的分析,如果一个数能够同时整除x和y,则必能同时整除x-y和y;而能够同时整x-y和y的数也必能同时整除x和y,即x和y的公约数与x-y和y的公约数是相同的,其最大公约数也是相同的,即f(x, y)= f(x-y, y),那么就可以不再需要进行大整数的取模运算,而转换成简单得多的大整数的减法。

在实际操作中,如果x<y,可以先交换(x, y)(因为(x, y)=(y, x)),从而避免求一个正数和一个负数的最大公约数情况的出现。一直迭代下去,直到其中一个数为0。
示例如下:
f(42, 30)=f(30, 12)=f(12, 18)= f(18, 12)= f(12, 6)= f(6, 6)= f(6, 0)= 6

解法二的具体代码如下:
代码清单2-15

BigInt gcd(BigInt x, BigInt y)
{
    if(x < y)
        return gcd(y, x);
    if(y == 0)
        return x;
    else
   	return gcd(x - y, y);
}


第四题: 求数组的子数组之和的最大值

写一个程序,求两个正整数的最大公约数。如果两个正整数都很大,有什么简单的方法吗?


一个有N个整数元素的一维数组( A[0], A[1], ... , A[n-2], A[n-1]),子数组之和的最大值是什么?(要求子数组的元素是连续的)

例子:有数组( -2, 5, 3, -6, 4, -8, 6),则其子数组之和的最大值为8,其对应的数组为(5,3)


解法一:采用直接法,记Sum[i...j],为数组A中从第i到第j之间所有数之和,算出所有Sum,取其最大,代码如下,时间复杂度O(N2):

int maxSum1(int *A, int n)
{
    int max = -1;
    int i, j, sum;
    
    for(i = 0; i < n; i++)
    {
          sum = 0;
          for(j = i; j < n; j++)
          {
                sum += A[j];
                if(sum > max )
                       max = sum;
          }
    }
    
    return max;
}


第五题  求二叉树中叶子节点的最大距离


struct Node  
{  
    Node* pLeft;    //左子树  
    Node* pRight;   //右子树  
    int nMaxLeft;   //左子树中的最长距离  
    int nMaxRight;  //右子树中的最长距离  
    char chValue;   //该节点的值  
};  
  
int nMaxLen = 0;  
  
//寻找树中最长的两段距离  
void findMaxLen(Node* pRoot)  
{  
    //遍历到叶子节点,则返回  
    if (pRoot == NULL)  
    {  
        return;  
    }  
  
    //如果左子树为空,那么该节点的左边最长距离为0  
    if (pRoot->pLeft == NULL)  
    {  
        pRoot->nMaxLeft = 0;  
    }  
  
    //如果右子树为空,那么该节点的右边最长距离为0  
    if (pRoot->pRight == NULL)  
    {  
        pRoot->nMaxRight = 0;  
    }  
      
    //如果左子树不为空,递归寻找左子树最长距离  
    if (pRoot->pLeft != NULL)  
    {  
        findMaxLen(pRoot->pLeft);  
    }  
      
    //如果右子树不为空,递归寻找右子树最长距离  
    if (pRoot->pRight != NULL)  
    {  
        findMaxLen(pRoot->pRight);  
    }  
      
    //计算左子树最长节点距离  
    if (pRoot->pLeft != NULL)  
    {  
        int nTemp;  
        if (pRoot->pLeft->nMaxLeft > pRoot->pLeft->nMaxRight)  
        {  
            nTemp = pRoot->pLeft->nMaxLeft;  
        }  
        else  
            nTemp = pRoot->pLeft->nMaxRight;  
        pRoot->nMaxLeft = nTemp+1;  
    }  
      
    //计算右子树最长节点距离  
    if (pRoot->pRight != NULL)  
    {  
        int nTemp;  
        if (pRoot->pRight->nMaxLeft > pRoot->pRight->nMaxRight)  
        {  
            nTemp = pRoot->pRight->nMaxLeft;  
        }  
        else  
            nTemp = pRoot->pRight->nMaxRight;  
        pRoot->nMaxRight = nTemp+1;  
    }  
  
    //更新最长距离  
    if (pRoot->nMaxLeft+pRoot->nMaxRight > nMaxLen)  
    {  
        nMaxLen = pRoot->nMaxLeft+pRoot->nMaxRight;  
    }  
  
}  



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值