หนึ่งในคำถามที่ยุ่งยากมากที่ถามในการสัมภาษณ์
สลับค่าของตัวแปรทั้งสองชอบและa=10b=15
โดยทั่วไปในการสลับค่าตัวแปรสองค่าเราต้องการตัวแปรที่ 3 เช่น:
temp=a;
a=b;
b=temp;
ตอนนี้ข้อกำหนดคือสลับค่าของสองตัวแปรโดยไม่ต้องใช้ตัวแปรที่ 3
หนึ่งในคำถามที่ยุ่งยากมากที่ถามในการสัมภาษณ์
สลับค่าของตัวแปรทั้งสองชอบและa=10b=15
โดยทั่วไปในการสลับค่าตัวแปรสองค่าเราต้องการตัวแปรที่ 3 เช่น:
temp=a;
a=b;
b=temp;
ตอนนี้ข้อกำหนดคือสลับค่าของสองตัวแปรโดยไม่ต้องใช้ตัวแปรที่ 3
คำตอบ:
ใช้อัลกอริทึมการแลกเปลี่ยน xor
void xorSwap (int* x, int* y) {
if (x != y) { //ensure that memory locations are different
*x ^= *y;
*y ^= *x;
*x ^= *y;
}
}
ทำไมต้องทดสอบ?
การทดสอบคือเพื่อให้แน่ใจว่า x และ y มีตำแหน่งหน่วยความจำต่างกัน (แทนที่จะเป็นค่าที่ต่างกัน) นี่เป็นเพราะ(p xor p) = 0และถ้าทั้ง x และ y ใช้ตำแหน่งหน่วยความจำเดียวกันเมื่อตั้งค่าเป็น 0 ทั้งสองจะถูกตั้งค่าเป็น 0 เมื่อทั้ง * x และ * y เป็น 0 การดำเนินการ xor อื่น ๆ ทั้งหมดบน * x และ * y จะเท่ากัน 0 (เหมือนกัน) ซึ่งหมายความว่าฟังก์ชันจะตั้งค่าทั้ง * x และ * y ให้เป็น 0
หากมีค่าเดียวกัน แต่ไม่ใช่ตำแหน่งหน่วยความจำเดียวกันทุกอย่างจะทำงานตามที่คาดไว้
*x = 0011
*y = 0011
//Note, x and y do not share an address. x != y
*x = *x xor *y //*x = 0011 xor 0011
//So *x is 0000
*y = *x xor *y //*y = 0000 xor 0011
//So *y is 0011
*x = *x xor *y //*x = 0000 xor 0011
//So *x is 0011
สิ่งนี้ควรใช้หรือไม่?
ในกรณีทั่วไปไม่ คอมไพเลอร์จะปรับตัวแปรชั่วคราวให้เหมาะสมที่สุดและเนื่องจากการแลกเปลี่ยนเป็นขั้นตอนทั่วไปจึงควรส่งออกรหัสเครื่องที่เหมาะสมที่สุดสำหรับแพลตฟอร์มของคุณ
ยกตัวอย่างเช่นโปรแกรมทดสอบด่วนที่เขียนด้วยภาษา C
#include <stdlib.h>
#include <math.h>
#define USE_XOR
void xorSwap(int* x, int *y){
if ( x != y ){
*x ^= *y;
*y ^= *x;
*x ^= *y;
}
}
void tempSwap(int* x, int* y){
int t;
t = *y;
*y = *x;
*x = t;
}
int main(int argc, char* argv[]){
int x = 4;
int y = 5;
int z = pow(2,28);
while ( z-- ){
# ifdef USE_XOR
xorSwap(&x,&y);
# else
tempSwap(&x, &y);
# endif
}
return x + y;
}
รวบรวมโดยใช้:
gcc -Os main.c -o swap
ใช้เวอร์ชัน xor
real 0m2.068s
user 0m2.048s
sys 0m0.000s
โดยที่เวอร์ชันที่มีตัวแปรชั่วคราวใช้เวลา:
real 0m0.543s
user 0m0.540s
sys 0m0.000s
รูปแบบทั่วไปคือ:
A = A operation B
B = A inverse-operation B
A = A inverse-operation B
อย่างไรก็ตามคุณอาจต้องระวังการล้นและไม่ใช่การดำเนินการทั้งหมดที่มีการผกผันที่กำหนดไว้อย่างดีสำหรับค่าทั้งหมดที่มีการกำหนดการดำเนินการ เช่น * และ / ทำงานจนกว่า A หรือ B จะเป็น 0
xor เป็นที่ชื่นชอบเป็นพิเศษเนื่องจากมีการกำหนดไว้สำหรับ ints ทั้งหมดและเป็นค่าผกผัน
a = a + b
b = a - b // b = a
a = a - b
a+bล้น?
int, unsigned short... ) ก็ยังคงใช้งานได้ในท้ายที่สุดแม้จะล้นเพราะถ้า a + b ล้นออกมา a - b ก็จะน้อยเกินไป ด้วยประเภทจำนวนเต็มพื้นฐานเหล่านี้ค่าจะเป็นแบบโรลโอเวอร์
unsignedประเภทจำนวนเต็มนั้นใช้ได้และใช้ได้เสมอ ในประเภทที่มีการลงนามมันจะเรียกใช้พฤติกรรมที่ไม่ได้กำหนดบนโอเวอร์โฟลว์
ไม่มีใครได้แนะนำให้ใช้std::swapแต่
std::swap(a, b);
ฉันไม่ได้ใช้ตัวแปรชั่วคราวใด ๆ และขึ้นอยู่กับประเภทaและbการนำไปใช้งานอาจมี specalization ที่ไม่มีเช่นกัน การดำเนินการควรเขียนโดยรู้ว่า 'เคล็ดลับ' เหมาะสมหรือไม่ ไม่มีจุดที่จะพยายามเดาครั้งที่สอง
โดยทั่วไปแล้วฉันอาจต้องการทำสิ่งนี้เนื่องจากจะใช้ได้กับประเภทคลาสที่ทำให้ ADL สามารถหาโอเวอร์โหลดที่ดีกว่าได้ถ้าเป็นไปได้
using std::swap;
swap(a, b);
แน่นอนว่าปฏิกิริยาของผู้สัมภาษณ์ต่อคำตอบนี้อาจพูดได้มากเกี่ยวกับตำแหน่งที่ว่าง
std::ใช้งานที่กำหนดโดยswapผู้ใช้สำหรับประเภทที่ผู้ใช้กำหนดเองก็มีให้เรียกใช้เช่นกัน (นี่คือความหมายของ "มันจะใช้ได้กับประเภทคลาสที่ทำให้ ADL สามารถหาโอเวอร์โหลดที่ดีกว่าได้ถ้าเป็นไปได้")
std::swapใช้หน่วยความจำชั่วคราวเพิ่มเติมในการใช้งานไม่ใช่หรือ cplusplus.com/reference/utility/swap
ตามที่ระบุไว้แล้วโดย manu อัลกอริทึม XOR เป็นวิธีที่ได้รับความนิยมซึ่งใช้ได้กับค่าจำนวนเต็มทั้งหมด (ซึ่งรวมถึงตัวชี้ด้วยโชคและการร่าย) เพื่อความสมบูรณ์ฉันอยากจะพูดถึงอัลกอริทึมที่มีประสิทธิภาพน้อยกว่าอีกอย่างหนึ่งโดยมีการบวก / ลบ:
A = A + B
B = A - B
A = A - B
ที่นี่คุณต้องระวังน้ำล้น / น้ำล้น แต่อย่างอื่นก็ใช้ได้ดีเหมือนกัน คุณอาจลองใช้การลอยตัว / คู่ในกรณีที่ XOR ไม่ได้รับอนุญาต
std::swap()? แน่นอนว่าคุณสามารถแปลงพอยน์เตอร์เป็นจำนวนเต็มและย้อนกลับได้อีกครั้ง (สมมติว่ามีจำนวนเต็มมากพอซึ่งไม่รับประกัน) แต่นั่นไม่ใช่สิ่งที่คุณแนะนำ คุณบอกเป็นนัยว่าพอยน์เตอร์เป็นจำนวนเต็มไม่ใช่ว่าสามารถแปลงเป็นจำนวนเต็มได้ ใช่คำตอบที่ยอมรับจะใช้กับพอยน์เตอร์ไม่ได้ คำตอบที่ชาร์ลส์เบลีย์ใช้std::swapเป็นจริงที่ถูกต้องเพียงอย่างใดอย่างหนึ่ง
std::swapการนำไปใช้โดยไม่ใช้ตัวแปรที่สาม
คำถามโง่ ๆ สมควรได้รับคำตอบที่เหมาะสม:
void sw2ap(int& a, int& b) {
register int temp = a; // !
a = b;
b = temp;
}
การใช้registerคำหลักที่ดีเท่านั้น
การสลับตัวเลขสองตัวโดยใช้ตัวแปรที่สามจะเป็นเช่นนี้
int temp;
int a=10;
int b=20;
temp = a;
a = b;
b = temp;
printf ("Value of a", %a);
printf ("Value of b", %b);
การสลับตัวเลขสองตัวโดยไม่ใช้ตัวแปรที่สาม
int a = 10;
int b = 20;
a = a+b;
b = a-b;
a = a-b;
printf ("value of a=", %a);
printf ("value of b=", %b);
#include<iostream.h>
#include<conio.h>
void main()
{
int a,b;
clrscr();
cout<<"\n==========Vikas==========";
cout<<"\n\nEnter the two no=:";
cin>>a>>b;
cout<<"\na"<<a<<"\nb"<<b;
a=a+b;
b=a-b;
a=a-b;
cout<<"\n\na="<<a<<"\nb="<<b;
getch();
}
cout << "Enter the two no=:"ฉันคาดหวังว่าจะได้อ่านcout << "Now enter the two no in reverse order:"
a+bหรือa-bมากเกินไป นอกจากนี้ยังvoid main()ไม่ถูกต้องและ<conio.h>ไม่ได้มาตรฐาน
เนื่องจากโซลูชันดั้งเดิมคือ:
temp = x; y = x; x = temp;
คุณสามารถทำให้มันเป็นสองซับโดยใช้:
temp = x; y = y + temp -(x=y);
จากนั้นทำให้เป็นซับเดียวโดยใช้:
x = x + y -(y=x);
yถูกอ่านและเขียนด้วยนิพจน์เดียวกันโดยไม่มีจุดลำดับการแทรกแซง
หากคุณเปลี่ยนคำถามเล็กน้อยเพื่อถามเกี่ยวกับการลงทะเบียนแอสเซมบลี 2 ตัวแทนที่จะเป็นตัวแปรคุณสามารถใช้การxchgดำเนินการเป็นตัวเลือกหนึ่งและการดำเนินการสแต็กเป็นอีกตัวหนึ่งได้
xchgคุณกำลังดำเนินการหมายถึง? คำถามไม่ได้ระบุสถาปัตยกรรมของ CPU
พิจารณาa=10, b=15:
การใช้การบวกและการลบ
a = a + b //a=25
b = a - b //b=10
a = a - b //a=15
การใช้การหารและการคูณ
a = a * b //a=150
b = a / b //b=10
a = a / b //a=15
a=10และb=1.5.
#include <iostream>
using namespace std;
int main(void)
{
int a,b;
cout<<"Enter a integer" <<endl;
cin>>a;
cout<<"\n Enter b integer"<<endl;
cin>>b;
a = a^b;
b = a^b;
a = a^b;
cout<<" a= "<<a <<" b="<<b<<endl;
return 0;
}
อัปเดต:ในสิ่งนี้เรากำลังรับข้อมูลจำนวนเต็มสองจำนวนจากผู้ใช้ จากนั้นเรากำลังใช้การดำเนินการXOR แบบบิตเพื่อสลับมัน
บอกว่าเรามีสองจำนวนเต็มa=4และb=9แล้ว:
a=a^b --> 13=4^9
b=a^b --> 4=13^9
a=a^b --> 9=13^9
นี่เป็นอีกหนึ่งวิธีแก้ปัญหา แต่มีความเสี่ยงเดียว
รหัส:
#include <iostream>
#include <conio.h>
void main()
{
int a =10 , b =45;
*(&a+1 ) = a;
a =b;
b =*(&a +1);
}
ค่าใด ๆ ในสถานที่ที่ +1 จะถูกลบล้าง
*(&a+1) ไม่ถูกต้อง ไม่ได้มาตรฐาน (และคุณไม่ได้ใช้ด้วยซ้ำ) bvoid main()<conio.h>
แน่นอน c ++ std::swapคำตอบที่ควรจะเป็น
อย่างไรก็ตามยังไม่มีตัวแปรที่สามในการใช้งานต่อไปนี้swap:
template <typename T>
void swap (T &a, T &b) {
std::pair<T &, T &>(a, b) = std::make_pair(b, a);
}
หรือเป็นซับเดียว:
std::make_pair(std::ref(a), std::ref(b)) = std::make_pair(b, a);
#include <stdio.h>
int main()
{
int a, b;
printf("Enter A :");
scanf("%d",&a);
printf("Enter B :");
scanf("%d",&b);
a ^= b;
b ^= a;
a ^= b;
printf("\nValue of A=%d B=%d ",a,b);
return 1;
}
นั่นคืออัลกอริทึมการแลกเปลี่ยน XOR ที่ถูกต้อง
void xorSwap (int* x, int* y) {
if (x != y) { //ensure that memory locations are different
if (*x != *y) { //ensure that values are different
*x ^= *y;
*y ^= *x;
*x ^= *y;
}
}
}
คุณต้องตรวจสอบให้แน่ใจว่าตำแหน่งหน่วยความจำแตกต่างกันและค่าจริงก็แตกต่างกันเนื่องจาก A XOR A = 0
คุณสามารถทำได้ .... ด้วยวิธีง่ายๆ ... ภายในบรรทัดเดียว Logic
#include <stdio.h>
int main()
{
int a, b;
printf("Enter A :");
scanf("%d",&a);
printf("Enter B :");
scanf("%d",&b);
int a = 1,b = 2;
a=a^b^(b=a);
printf("\nValue of A=%d B=%d ",a,b);
return 1;
}
หรือ
#include <stdio.h>
int main()
{
int a, b;
printf("Enter A :");
scanf("%d",&a);
printf("Enter B :");
scanf("%d",&b);
int a = 1,b = 2;
a=a+b-(b=a);
printf("\nValue of A=%d B=%d ",a,b);
return 1;
}
bมีทั้งอ่านและแก้ไขภายในนิพจน์เดียวกันโดยไม่มีจุดลำดับการแทรกแซง
public void swapnumber(int a,int b){
a = a+b-(b=a);
System.out.println("a = "+a +" b= "+b);
}
คำตอบที่ดีที่สุดคือการใช้ XOR และการใช้งานในบรรทัดเดียวจะดีกว่า
(x ^= y), (y ^= x), (x ^= y);
x, y เป็นตัวแปรและเครื่องหมายจุลภาคระหว่างทั้งสองจะแนะนำจุดลำดับดังนั้นจึงไม่ขึ้นอยู่กับคอมไพเลอร์ ไชโย!
มาดูตัวอย่าง c ง่ายๆในการสลับตัวเลขสองตัวโดยไม่ต้องใช้ตัวแปรตัวที่สาม
โปรแกรม 1:
#include<stdio.h>
#include<conio.h>
main()
{
int a=10, b=20;
clrscr();
printf("Before swap a=%d b=%d",a,b);
a=a+b;//a=30 (10+20)
b=a-b;//b=10 (30-20)
a=a-b;//a=20 (30-10)
printf("\nAfter swap a=%d b=%d",a,b);
getch();
}
เอาท์พุต:
ก่อนทำการแลกเปลี่ยน a = 10 b = 20 หลังจากการแลกเปลี่ยน a = 20 b = 10
โปรแกรม 2: การใช้ * และ /
มาดูอีกตัวอย่างหนึ่งในการสลับตัวเลขสองตัวโดยใช้ * และ /
#include<stdio.h>
#include<conio.h>
main()
{
int a=10, b=20;
clrscr();
printf("Before swap a=%d b=%d",a,b);
a=a*b;//a=200 (10*20)
b=a/b;//b=10 (200/20)
a=a/b;//a=20 (200/10)
printf("\nAfter swap a=%d b=%d",a,b);
getch();
}
เอาท์พุต:
ก่อนทำการแลกเปลี่ยน a = 10 b = 20 หลังจากการแลกเปลี่ยน a = 20 b = 10
โปรแกรม 3: การใช้ตัวดำเนินการ XOR แบบบิต:
ตัวดำเนินการ XOR แบบบิตสามารถใช้เพื่อสลับสองตัวแปร XOR ของตัวเลขสองตัว x และ y จะส่งกลับตัวเลขที่มีบิตทั้งหมดเป็น 1 โดยที่บิตของ x และ y ต่างกัน ตัวอย่างเช่น XOR ของ 10 (ใน Binary 1010) และ 5 (ใน Binary 0101) คือ 1111 และ XOR ของ 7 (0111) และ 5 (0101) คือ (0010)
#include <stdio.h>
int main()
{
int x = 10, y = 5;
// Code to swap 'x' (1010) and 'y' (0101)
x = x ^ y; // x now becomes 15 (1111)
y = x ^ y; // y becomes 10 (1010)
x = x ^ y; // x becomes 5 (0101)
printf("After Swapping: x = %d, y = %d", x, y);
return 0;
เอาท์พุต:
หลังจากการสลับ: x = 5, y = 10
โปรแกรม 4:
ยังไม่มีใครแนะนำให้ใช้ std :: swap
std::swap(a, b);
ฉันไม่ได้ใช้ตัวแปรชั่วคราวใด ๆ และขึ้นอยู่กับประเภทของ a และ b การใช้งานอาจมีความเชี่ยวชาญที่ไม่มีเช่นกัน การดำเนินการควรเขียนโดยรู้ว่า 'เคล็ดลับ' เหมาะสมหรือไม่
ปัญหาเกี่ยวกับวิธีการข้างต้น:
1) วิธีการตามการคูณและการหารใช้ไม่ได้หากตัวเลขตัวใดตัวหนึ่งเป็น 0 เนื่องจากผลคูณกลายเป็น 0 โดยไม่คำนึงถึงจำนวนอื่น
2) การแก้ปัญหาทางคณิตศาสตร์ทั้งสองอาจทำให้เกิดการล้นทางคณิตศาสตร์ ถ้า x และ y ใหญ่เกินไปการบวกและการคูณอาจอยู่นอกช่วงจำนวนเต็ม
3) เมื่อเราใช้พอยน์เตอร์กับตัวแปรและทำการสลับฟังก์ชันวิธีการทั้งหมดข้างต้นจะล้มเหลวเมื่อพอยน์เตอร์ทั้งสองชี้ไปที่ตัวแปรเดียวกัน ลองมาดูว่าจะเกิดอะไรขึ้นในกรณีนี้หากทั้งสองชี้ไปที่ตัวแปรเดียวกัน
// วิธีการใช้ Bitwise XOR
x = x ^ x; // x becomes 0
x = x ^ x; // x remains 0
x = x ^ x; // x remains 0
// วิธีการคำนวณตามเลขคณิต
x = x + x; // x becomes 2x
x = x – x; // x becomes 0
x = x – x; // x remains 0
ให้เราดูโปรแกรมต่อไปนี้
#include <stdio.h>
void swap(int *xp, int *yp)
{
*xp = *xp ^ *yp;
*yp = *xp ^ *yp;
*xp = *xp ^ *yp;
}
int main()
{
int x = 10;
swap(&x, &x);
printf("After swap(&x, &x): x = %d", x);
return 0;
}
เอาท์พุต :
หลังจากการแลกเปลี่ยน (& x, & x): x = 0
อาจต้องใช้การสลับตัวแปรกับตัวมันเองในอัลกอริทึมมาตรฐานหลายแบบ ตัวอย่างเช่นดูการใช้งาน QuickSort ซึ่งเราอาจสลับตัวแปรกับตัวมันเอง ปัญหาข้างต้นสามารถหลีกเลี่ยงได้โดยการวางเงื่อนไขก่อนการแลกเปลี่ยน
#include <stdio.h>
void swap(int *xp, int *yp)
{
if (xp == yp) // Check if the two addresses are same
return;
*xp = *xp + *yp;
*yp = *xp - *yp;
*xp = *xp - *yp;
}
int main()
{
int x = 10;
swap(&x, &x);
printf("After swap(&x, &x): x = %d", x);
return 0;
}
เอาท์พุต :
หลังจากการแลกเปลี่ยน (& x, & x): x = 10
อาจจะไม่ตรงประเด็น แต่ถ้าคุณรู้ว่าคุณกำลังสลับตัวแปรเดียวระหว่างค่าที่ต่างกันสองค่าคุณอาจสามารถใช้ตรรกะของอาร์เรย์ได้ ทุกครั้งที่เรียกใช้โค้ดบรรทัดนี้โค้ดจะสลับค่าระหว่าง 1 ถึง 2
n = [2, 1][n - 1]
คุณสามารถทำได้:
std::tie(x, y) = std::make_pair(y, x);
หรือใช้ make_tuple เมื่อสลับตัวแปรมากกว่าสองตัวแปร:
std::tie(x, y, z) = std::make_tuple(y, z, x);
แต่ฉันไม่แน่ใจว่าภายใน std :: tie ใช้ตัวแปรชั่วคราวหรือไม่!
ในจาวาสคริปต์:
function swapInPlace(obj) {
obj.x ^= obj.y
obj.y ^= obj.x
obj.x ^= obj.y
}
function swap(obj) {
let temp = obj.x
obj.x = obj.y
obj.y = temp
}
ระวังเวลาดำเนินการของทั้งสองตัวเลือก
โดยการเรียกใช้รหัสนี้ฉันวัดมัน
console.time('swapInPlace')
swapInPlace({x:1, y:2})
console.timeEnd('swapInPlace') // swapInPlace: 0.056884765625ms
console.time('swap')
swap({x:3, y:6})
console.timeEnd('swap') // swap: 0.01416015625ms
อย่างที่คุณเห็น (และตามที่หลาย ๆ คนกล่าวไว้) การสลับตำแหน่ง (xor) ใช้เวลานานกว่าตัวเลือกอื่น ๆ โดยใช้ตัวแปร temp
R ขาดการมอบหมายงานพร้อมกันตามที่เสนอโดย Edsger W. Dijkstra ในA Discipline of Programming , 1976, ch.4, p.29 สิ่งนี้จะช่วยให้สามารถแก้ปัญหาที่สวยงามได้:
a, b <- b, a # swap
a, b, c <- c, a, b # rotate right
a = a + b - (b=a);
มันง่ายมาก แต่อาจมีคำเตือน
b=a a + b
โซลูชันบรรทัดเดียวสำหรับการแลกเปลี่ยนสองค่าในภาษา c
a=(b=(a=a+b,a-b),a-b);
second_value -= first_value;
first_value += second_value;
second_value -= first_value;
second_value *= -1;