天天看点

【算法】算法的艺术(五)

利用位运算求整数的原码或补码

   利用位运算求任意整数的原码或补码。

  实例解析:

  整数在内存中本来就是用补码存放的,若要求出补码,只需求出内存中的每一位二进制数即可。而原码,若是负数,则需要将补码减1然后取反(最高位不取反)。

  程序代码:

#include "stdio.h"
int main()
{int  n, i;
 char k;
 clrscr();
 scanf("%d",&n);
 printf("要转成什么?\n");
 printf("1、原码\n");
 printf("2、补码\n");
 do{
  k = getch();
   }while(k != ‘1’ && k != ‘2’);
 if(k == ‘1’ && n<0){         //若是求原码且n为负数
    n--;
    n ^= ~0;                    //取反
    n |= 1<<sizeof(int)*8 - 1;    //最高位还原为1
 }
 for(i = sizeof(int)*8 - 1; i >= 0; i--){
    printf("%d", n>>i&1);
 }
 printf("\n");
 getch();
 return 0;
}      

字符串逆置

   编写一个函数,可将字符串逆置。

  函数的功能是将字符串逆置(头变成尾,尾变成头),而不是把字符串倒序输出。例如:字符串本来是ABCDE,逆置后内存中的字符串变为EDCBA。

  要将字符串逆置,需要将第一个字符与最后一个字符交换,第二个与倒数第二个交换……,设字符串有效字符个数为n,则这样的交换要进行n/2次。

#include <string.h>
  void reverse(char *p)
  {int  n, i, t;
   n = strlen(p);
   for(i = 0; i <= n/2; i++){
     t = p[i];
     p[i] = p[n–i-1];
     p[n–i-1] = t;
   }
  }      

用递归法逆序输出字符串

   编写一个函数,可将字符串逆序输出。

  逆序输出,仅仅是倒着输出字符串,并不是要改变内存中字符串的存储状态。

  可以这样设计:假设字符串为“abcd”,函数先留下第一个字符’a’,等待稍后输出,而将其余字符“bcd”交给下一个“自己”处理,待下一个自己逆序输出“bcd”后再输出字符’a’。

  结束递归的条件是:给定的字符串是空串。

void  reverse(char *str)
  { if(*str){                  //若第一个字符不是空字符
       reverse(str+1);       //调用自己逆序输出下一个字符之后的字符
       printf("%c",*str);    //输出第一个字符。
    }
  }      

用递归法对数组排序

   编写一个函数,实现用递归法对整型数组排序。

  用递归法排序不需要考虑所有的数,在函数中只需要把最大(最小)的数找出并交换到最前面,剩下数的排序由下一个“自己”去完成。当待排序的数据只剩一个时结束递归。

void sort(int *p, int n)  //p用来存储数组首地址,n为数据个数
  {int i, t, k;
   if(n >= 2){             //两个以上的数才需要排序
      k = 0;                //最前面的数当临时擂主,它的序号:0
      for(i = 1; i < n; i++)
        if(p[i] > p[k])    //若某一数大于擂主
        k = i;             //记录新擂主的序号
      t = *p;                //本行及下面两行:将最大数交换到最前面
      *p = p[k];
      p[k] = t;
      sort(p+1, n-1);     //剩下数的排序工作调用“自己”完成
   }
  }      

  附主函数:

int main()
  {int i, a[10] = {3,7,1,9,0,6,2,4,8,5};
   clrscr();
   sort(a, 10);
   for(i = 0; i <= 9; i++)
     printf("%3d", a[i]);
   getch();
   return 0;
  }      

向主调函数中的局部变量存数据

   主函数中输入一个字符串,并定义了一个局部变量num用来统计字符串中字母的个数,请在被调函数中统计字母的个数并由被调函数存入num中。

  (1) 被调函数要统计字母个数,须知道字符串的存放位置,故需将数组地址传递给被调函数。

  (2) num是主调函数的局部变量,被调函数中不能直接访问,若要把统计结果存入num中,必须通过指针变量间接访问,因此又需要传递num的地址。

int main()
  {void count(char*, int*);
   char s[80];
   int num;
   clrscr();
   gets(s);
   count(s, &num);
   printf(“%d\n”, num);
   getch();
   return 0;
  }
  void count(char *str, int *p)
  {int i;
   *p = 0;    //相当于num = 0
   for(    ; *str != ’\0’; str++)
     if(*str>=65 && *str<=90 || *str>=97 && *str<=122)
       (*p)++;
  }      

   编写一个函数,可用来求两个整数的平方差和平方和。

  题目要求平方差和平方和两个值,却只允许编写“一个”函数,而一个函数只能返回一个值,所以必须借助于全局变量或指针变量完成。可以采用以下四种方法来实现:

  设两个全局变量分别用来存储平方差和平方和,被调函数直接向全局变量存值,函数不需要返回值

  设一个全局变量,一个局部变量。被调函数将一个值直接存入全局变量,另一个值返回,由主调函数将返回值存入局部变量。

  设两个局部变量,被调函数通过指针变量将值存入局部变量。

  设两个局部变量,一个通过指针变量赋值,另一个通过返回值赋值。

  对于全局变量,最好不用,这样我们只能在3、4两种方法中选一种,鉴于方法3对两个变量的处理方法一致,编程相对简单,故我们选用方法3。

void sum_sub(int x, int y, int *p1, int *p2)
  {*p1 = x*x + y*y;
   *p2 = x*x - y*y;
  }
  int main()
  {void sum_sub(int, int, int*, int*);
   int  a, b, sum, sub;
   scanf(“%d%d”, &a, &b);
   sum_sub(a, b, &sum, &sub);
   printf(“%d,%d\n”, sum, sub);
   getch();
   return 0;
  }      

  本题借助于指针变量使得被调函数计算出的两个值存入了主调函数的两个变量中,但是这两个值不是返回的,而是在被调函数中存入的。

利用位运算对字母进行大小写转换

   编程,用位运算实现字母大小写的转换:若是大写,转为小写,若为小写,则转为大写。

  大写字母和小写字母的区别是:大写字母二进制ASCII码的第6位(最右边为第0位)是0,而小写字母则为1,例如’A’的ASCII码是 01000001,’a’的ASCII码则是 01100001。要实现大写变为小写,只需把此位的0改为1,反之,则把1改为0。

  将某位由0变1可使用位操作“|”,将某位由1变0则用“&”。

void change(char *p)
  {if(*p >= 65 && *p <= 90)
     *p |= 32;
   else if(*p >= 97 && *p <= 122)  //不能没有else,也不能没有if
     *p &= ~32;
  }
  附:主函数
  int main()
  {char s[20];
   int i;
   clrscr();
   gets(s);
   for(i = 0; i < strlen(s); i++)
     change(s+i);                 //s+i相当于&s[i]
   printf(“%s\n”, s);
   getch();
   return 0;
  }      

  利用位操作实现大小写转换比用c += 32或c -= 32执行速度更快(只需要改变内存中的1位),这也正是ASCII码表中将小写字母排在97之后而不是紧接着大写字母的原因。

  本题还有一种更简单方法:直接与32异或。

void change(char *p)
  {if(*p >= 65 && *p <= 90 || *p >= 97 && *p <= 122)
     *p ^= 32;
  }      

继续阅读