位运算的使用

位运算的巧妙应用

题1:找出唯一成对的数

解题思路:

n ^ 1 ^ 1 = n, 一个数两次^同一个数,得到数本身。
n ^ 0 = n, 任何数异或0都是他自己。
将res ^数组下标 ^ 数组的value,最终得出的结果就为重复值。

方法1.另外开辟一个数组arr,遍历原来的数组,原来数组中的数就是arr中的下标, 若遇到一个数就将对应arr的下标位置对应的值加一就行。

然后拿新创建的数组判定其值大小,若遇到下标位置对应的值为2的时候,输出下标就行,

对于同一个以arr[]的值作为新创建的数组下标 ,这样进行判定,比较好理解,直观一点

#include<stdio.h>
#include <stdlib.h>
int main() {

	int i = 0;
	int arr[6] = { 1, 2, 4, 4, 7, 5 };
	int nums[10] = { 0 };
	int size = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < size; i++) {
		nums[arr[i]]++;
		if (nums[arr[i]] == 2)
			printf("成对出现的数字式: %d\n", arr[i]);
	}
	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述

方法2.首先也是最暴力的方法就是一个循环嵌套一个循环, 一个一个去找 时间复杂度为O(n²)

依次将前一个元素与后面的元素进行比较,只往后面进行比较,看二值是否相等即可

#include <stdio.h>
#include <stdlib.h>
int main() {
	int arr[] = { 1, 2, 3, 2, 5, 6, 8 };
	int size = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < size; i++) {
		for (int j = i + 1; j < size; j++) {
			if (arr[i] == arr[j]) {
				printf("成对出现的数字是: %d\n", arr[i]); //arr[i];
			}
		}
	}
	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述
前两种方法是最容易想到的,但是他们的效率都是相对比较低

方法3: 使用异或来找出唯一成对的数, 这种算法的效率应该是最高的了, 而且不需要辅助空间

先来说一下异或的规则 :
a^a = 0
a ^ 0 = a
a ^ n ^ n = a
思路是将数组中数字全部异或起来, 那么就会把相同的数给变成0了,
我们可以再次将该数与数组之间的数给异或起来
那么不重复的数都会变成0,而重复的数不会变成0
时间复杂度为O(n)

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

 int main() {
	     int i, s1 = 0, s2 = 0;
	     int a[11] = { 1, 2, 3, 4, 4, 5, 6, 7, 8, 9 };
	     int size = sizeof(a) / sizeof(a[0]);
	      for (i = 0; i < size; i++)  {
		 s1 = s1^a[i];
        }
		 for (i = 1; i < size - 1; i++) {
			 s2 = s2^i;
    }
	  printf("重复的数字是:%d\n", s1^s2);
 	  system("pause"); 
	  return 0;
 }

运行结果:
在这里插入图片描述
总结:
s1 = (1000个数 ^ ) ^ 重复数
s2 = 1000个数 ^
s1^s2 = (1000个数 ^ ) ^ 重复数 ^ (1000个数 ^ ) = 0 ^ 重复数 = 重复数

题2:二进制中1的个数

解法一:
解题思路:
将7的二进制数的每一位与1做与运算,如果结果为1移到该位的值,则该位数字为1,否则为0。
需循环32次,将1依次移到指定位数与9的二进制数做与运算,如果结果为1移到该位的值则计数。

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

int main() {
	int n = 7;
	int cout = 0;
	while (n != 0) {
		n = (n - 1) & n;
		cout += 1;
	}
	printf("%d\n",cout);
	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述

题3:是否为2的整数次方

解题思路 :
2的整数次方,2 ^ 0, 2 ^ 1, 2 ^ 2, 也就是说,将该数转化为2进制表示,只有一个1出现
一个整数,若是2的n次方,有没有想过对这个整数的2进制进行考虑,比如12,它的二进制为:1100
2 10
4 100
13 1101
16 10000
32 100000
64 1000 0000
从上面的举例我们发现,凡是2的N次方的整数,其二进制码只有一个1。
假设A为要证明的整数,B等于A - 1,我们假设A为2的N次方数,那么A&B = 0,这很好证明。那是不是满足A&B = 0就能证明A是2的N次方数呢?
假设一个数的二进制为1000000000000000(这里为int型:两个字节),那这个数减去1则变为0111111111111111。我们知道,在计算机中,数都是以其二进制的补码放置的,
最高位为1代表负数,最高位为0代表正数。上面两个数中,
1000000000000000为负数,0111111111111111为正数,这两个数相与为0,但1000000000000000并不是2的N次方(2的N次方为正数)。

因此,倘若一个数为2的N次方,那么该数应满足大于0且该数和该数减一后的值相与等于0时才为2的N次方:
具体验证如下:

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

void judge_two_power(int n) {
	if ((n > 0) && (n & (n - 1)) == 0) {
		printf("该数是2的次方\n");
	} else {
		printf("该数不是2的次方\n");
	}
}

int main() {
	judge_two_power(16);
	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述

题4:将二进制整数的奇偶位互换

例如9(1010),进行变换之后变为6(0101)

第一步:先取到奇数位和偶数位 n & 0xaaaaaaaa (0x10101010101010101010101010101010) 取奇数位 n & 0x55555555(0x01010101010101010101010101010101) 取偶数位

第二步:进行奇数位和偶数位的位置挪一下 偶数位应该右移一位,奇数位则应该左移一位。(n & 0x55555555) << 1) (n & 0xaaaaaaaa) >> 1)

第三步:经过上述操作之后将两个部分合并起来 ((n & 0xaaaaaaaa) >> 1) | ((n & 0x55555555) << 1)

总结: return ((N & 0xaaaaaaaa) >>1 ) | ((n & 0x55555555) << 1)

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

int  swap(int n) {
	return ((n & 0xaaaaaaaa) >> 1) | ((n & 0x55555555) << 1);

}

int main() {
	int ret = swap(9);
	printf("交换之后的结果是: %d\n",ret);
	system("pause");
	return 0;
}

运行结果
在这里插入图片描述
那么上题中0x55555555, 0xaaaaaaaa…等究竟是什么?

使用了0xaaaaaaaa, 0x55555555等十分奇怪的字符,下面介绍对该部分字符进行介绍.

在C / C++中,0x为十六进制的前缀标识,0位八进制的前缀标识,十进制没有前缀标识。

因此,那些奇怪的字符为整数的十六进制表示。有那么多的整数,为何在涉及位操作程序中会出现这些整数呢。

因为这些整数的二进制形式很特殊,可以借助Windows系统自带的计算器,快捷计算出该整数的二进制形式

0xaaaaaaaa = 1010 1010 1010 1010 1010 1010 1010 1010 (偶数位为1,奇数位为0)

0x55555555 = 1010 1010 1010 1010 1010 1010 1010 1010 (偶数位为0,奇数位为1)

0x33333333 = 0011 0011 0011 0011 0011 0011 0011 0011 (1和0每隔两位交替出现)

0xcccccccc = 1100 1100 1100 1100 1100 1100 1100 1100 (0和1每隔两位交替出现)

0x0f0f0f0f = 0000 1111 0000 1111 0000 1111 0000 1111 (1和0每隔四位交替出现)

0xf0f0f0f0 = 1111 0000 1111 0000 1111 0000 1111 0000 (0和1每隔四位交替出现)

题5:0~1间浮点数的二进制表示

解题思路:
浮点数的二进制表示为乘2挪整,如下图

def float_bin(f) :
res = [‘0.’]
while f != 0 :
f = f * 2
res.append(str(int(f)))
f -= int(f)
return ‘’.join(res)
print(float_bin(0.625))
待整理

题6:找到数组中只出现1次的数字,并返回该数

方法一:利用创建一个新的数组,然后以原来数组作为新数组的下标依次进行循环判定,看最后的结果是否等于1,那么就可以证明该数组中的某一个数字只出现一次

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

int main() {
	int arr[] = { 1, 2, 3, 2, 4, 1, 4 };
	int nums[1023] = { 0 };
	int size = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < size;i++){
		nums[arr[i]]++;
	}
	for (int i = 0; i < size; i++){
		if (nums[i] == 1) {
		
			printf("只出现1次的数字是: %d \n",i);
		}
	}

	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述

方法二:利用异或放入基本原理 ,将每一个数字进行异或,相同的数字变为0,该方法高效

异或原理:
a^0=a:
a^a=0:
a ^n ^n=a:

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

int main() {
	int arr[] = { 1, 2, 4, 2, 5, 4, 1 };
	int size = sizeof(arr) / sizeof(arr[0]);
	int ret = 0;
	for (int i = 0; i < size;i++) {
		ret ^= arr[i];
	}
	printf("%d\n",ret);
	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述

题目7: 找到出现一次的两个数字

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

int main() {

	int arr[] = { 1, 3, 1, 6, 3, 5 };
	int ret = 0;
	int size = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < size;i++) {
		ret ^= arr[i];
	}
	ret = ret & (~ret) + 1; //找到了一个最低位是1的二进制位
	int nums1 = 0;
	int nums2 = 0;
	for (int i = 0; i < size; i++) {
		if ((arr[i] & ret) == 1){
			nums1 ^= arr[i];
		} else {
			nums2 ^= arr[i];
		}
	}
	printf("nums1 = %dnums2 =  %d\n", nums1, nums2);
	system("pause");
	return 0;
}
#include <stdio.h>
#include <stdlib.h>

int main() {

	int arr[] = { 1, 3, 1, 6, 3, 5 };
	int ret = 0;
	int size = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < size; i++) {
		ret ^= arr[i];
	}

	int pos = 0;//扎到pos位置为1的位置
	for (int i = 0; i < 32; i++) {
		if ((ret & (1 << pos)) != 0){
			break;
		}
	}

	int nums1 = 0;
	int nums2 = 0;
	for (int i = 0; i < size; i++) {
		if ((arr[i] & (1 << pos)) == 1){
			nums1 ^= arr[i];
		}
		else {
			nums2 ^= arr[i];
		}
	}
printf("nums1 = %dnums2 =  %d\n", nums1, nums2);
	system("pause");
	return 0;
}

运行结果:

在这里插入图片描述
总结
1.异或运算相当于不进位的加法运算,1 + 1为0,0 + 0为0,1 + 0为1,所以异或相同为0,不同为1
n ^ 0 = n; nxx = n; x^x = 0
2.判断二进制数某位是否为1:1 << 某位 & n = 1 << 某位

3.(n - 1) & n 从低位依次往高位消除n(二进制表示)中的1。

4.n & 0xaaaaaaaa 只保留2进制偶数位;0xaaaaaaaa == 0b10101010 10101010 10101010 10101010

n & 0x55555555 只保留2进制奇数位; 0x55555555 == 0b01010101 01010101 01010101 01010101

求大小写的转化,可以使用异或的方法进行计算.

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

int main() {

	while (1) {
		char ch = getchar();
		if (ch > 'a' && ch < 'z' || ch > 'A' && ch < 'Z') {
			ch ^= 'a' - 'A';
		}
		putchar(ch);
	}
	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值