有什麼像a=a+b;b=a-b;a=a-b;這樣的演算法或者知識?

之前假期有一次沒事幹,在親戚的電腦上看了點傳智的視頻,裡面提到了這個交換演算法,後來到了學校我發現這種東西在大學幾乎沒人知道,請問還有什麼類似的知識嗎?


這種東西沒有任何意義,你直接搞一個臨時變數直接交換,比你那個快多了

int t = a;
a = b;
b = t;

這才是正確做法(或者使用swap

我看到了題目有演算法兩個字( ̄_, ̄ )我的內心毫無波動,向題主丟出了陳碩 (用異或來交換兩個變數是錯誤的)與 RednaxelaFX (又一面試題,又一偽命題)


首先,這個會吃aliasing問題,也就是說如果你寫一個通用的swap函數或者宏這麼干,在a和b是同一個變數的時候就傻逼了。

其次,如果是C/C++的話,這個溢出會undefined behavior,換成三個異或倒是不會。

這種東西知道了,覺得有趣一下就行了。實際生活里你需要的幾乎總是std::swap。


來一客《Hacker』s Delight 2nd Edition》,爽歪歪。


調試代碼的難度是編寫代碼的兩倍.根據定義,如果你在編寫代碼時聰明用盡,你就無法調試你的代碼了.

- Brian W. Kernighan, P. J. Plauger


這就好比某個籃球運動員,苦練閉眼投籃,練到了閉眼投籃和正常投籃動作差不多的準確率。

然並卵。

無論是想進nba球星,還是成為職業球員賺錢養家,還是打進國家隊為國爭光,這個閉眼投籃的技能都沒有任何用處,反而有害。

因為這只是個雜耍技能,而不是打籃球比賽的技能。


這東西甚至連奇技淫巧都算不上,缺少安全性,哪怕最簡單的

a^=b

b^=a

a^=b

也要靠譜的多。

工程上正統的做法是std::swap(a,b);

或者說展開為

template&
void swap(T a, T b){
char temp[sizeof(T)];
memcpy(temp,a,sizeof(T));
memcpy(a,b,sizeof(T));
memcpy(b,temp,sizeof(T));
}


大學不教就對了……

這不是知識 (knowledge),這叫花招 (trick),都不能算技巧 (technique)。鍛煉頭腦還行,可別拿來當做正確的解決方法啊!

寫好代碼,人人有責


Python大法好,當然速度慢點。

a, b = b, a


生產環境玩弄這種小技巧一般會被拖出去亂棍打死。最簡單的:a+b溢出了呢?


譚浩強書里有很多類似的,比如 i+++i++ 是多少。


我就直接跟你說吧,什麼優化的不是靠這種東西的,

比如前幾天那個for循環求1到100的和,開了優化編譯器直接給你返回個5050。還有,小學時候不是有一個什麼有幾個桶然後量幾升水的問題嗎,實際中其實只要再拿一個合適的桶就好了,就為了倒點水又不是什麼世紀性難題,當然簡單粗暴有效的就是應該選的,何必折騰來折騰去的。。。。

你那個a,b的事兒,不知道你高中數學有沒有遇見「設一個變數」的問題,我不去設那個變數依然能算,不過麻煩很多,也許在某些情況下你的方法更好,但是多數情況下我們都會選擇去設那個變數,更直觀(也就是類似於考慮了可維護性)。其實都是一個道理啦~


傳智害人不淺


請老老實實地用std::swap(a,b);


很多回答都需要去扭轉自己的錯誤認識。

這也說明了我們以前用過的教材是多麼爛,各種奇技淫巧就像朋友圈謠言一樣多麼不靠譜,深入思考一下所見所聞是多麼重要。


以前喜歡用寫條件語句 結果會經常搞亂優先順序,然後就要打很多括弧 不過這樣寫能快十幾個時鐘吧。。。

哦 還有上面有人說那個^^^要是用地址的話要寫判斷語句if a! =b


位運算講解系列文章(目錄) (已經404了)爽死你。。

位運算簡介及實用技巧(一):基礎篇

位運算簡介及實用技巧(二):進階篇(1)

位運算簡介及實用技巧(三):進階篇(2)

位運算簡介及實用技巧(四):實戰篇


不被人知道是對的,因為這麼做除了寫的時候感覺比較炫以外沒有任何好處。為什麼呢?既然你也問了還有哪些「技巧」,那麼看完下面這個技巧我想兩個問題你都能明白。

首先我也給你一個神奇的語句。

~arg1~arg2

如你所見,就是對第一個參數取反,再對第二個參數取反,最後把他們位與起來。

這東西有啥用呢,用處可大啦,幾乎所有的邏輯運算和所有的數學運算都能用它或者多個它組合起來搞定!

比如說這句

~(~(~29886600~29886600)~(~1333568~1333568))~(~29886600~1333568)

這是啥呢,這是

29886600^1333568

也就是這倆數的位異或,是不是很簡單呢,讓我們把上面這段神奇代碼中的每一個邏輯運算再次用它替換,就變成了……

(~(~(~(~(~(~(~(~(~29886600 ~29886600) ~(~29886600 ~29886600)) ~(~(~29886600 ~29886600) ~(~29886600 ~29886600))) ~(~(~(~29886600 ~29886600)
~(~29886600 ~29886600)) ~(~(~29886600 ~29886600) ~(~29886600 ~29886600)))) ~(~(~(~(~29886600 ~29886600) ~(~29886600 ~29886600)) ~(~(~29886600
~29886600) ~(~29886600 ~29886600))) ~(~(~(~29886600 ~29886600) ~(~29886600 ~29886600)) ~(~(~29886600 ~29886600) ~(~29886600 ~29886600)))))
~(~(~(~(~(~1333568 ~1333568) ~(~1333568 ~1333568)) ~(~(~1333568 ~1333568) ~(~1333568 ~1333568))) ~(~(~(~1333568 ~1333568) ~(~1333568 ~1333568))
~(~(~1333568 ~1333568) ~(~1333568 ~1333568)))) ~(~(~(~(~1333568 ~1333568) ~(~1333568 ~1333568)) ~(~(~1333568 ~1333568) ~(~1333568 ~1333568)))
~(~(~(~1333568 ~1333568) ~(~1333568 ~1333568)) ~(~(~1333568 ~1333568) ~(~1333568 ~1333568)))))) ~(~(~(~(~(~(~29886600 ~29886600) ~(~29886600
~29886600)) ~(~(~29886600 ~29886600) ~(~29886600 ~29886600))) ~(~(~(~29886600 ~29886600) ~(~29886600 ~29886600)) ~(~(~29886600 ~29886600)
~(~29886600 ~29886600)))) ~(~(~(~(~29886600 ~29886600) ~(~29886600 ~29886600)) ~(~(~29886600 ~29886600) ~(~29886600 ~29886600)))
~(~(~(~29886600 ~29886600) ~(~29886600 ~29886600)) ~(~(~29886600 ~29886600) ~(~29886600 ~29886600))))) ~(~(~(~(~(~1333568 ~1333568)
~(~1333568 ~1333568)) ~(~(~1333568 ~1333568) ~(~1333568 ~1333568))) ~(~(~(~1333568 ~1333568) ~(~1333568 ~1333568)) ~(~(~1333568 ~1333568)
~(~1333568 ~1333568)))) ~(~(~(~(~1333568 ~1333568) ~(~1333568 ~1333568)) ~(~(~1333568 ~1333568) ~(~1333568 ~1333568))) ~(~(~(~1333568 ~1333568)
~(~1333568 ~1333568)) ~(~(~1333568 ~1333568) ~(~1333568 ~1333568))))))) ~(~(~(~(~(~(~(~29886600 ~29886600) ~(~29886600 ~29886600))
~(~(~29886600 ~29886600) ~(~29886600 ~29886600))) ~(~(~(~29886600 ~29886600) ~(~29886600 ~29886600)) ~(~(~29886600 ~29886600) ~(~29886600 ~29886600))))
~(~(~(~(~29886600 ~29886600) ~(~29886600 ~29886600)) ~(~(~29886600 ~29886600) ~(~29886600 ~29886600))) ~(~(~(~29886600 ~29886600) ~(~29886600 ~29886600))
~(~(~29886600 ~29886600) ~(~29886600 ~29886600))))) ~(~(~(~(~(~1333568 ~1333568) ~(~1333568 ~1333568)) ~(~(~1333568 ~1333568) ~(~1333568 ~1333568)))
~(~(~(~1333568 ~1333568) ~(~1333568 ~1333568)) ~(~(~1333568 ~1333568) ~(~1333568 ~1333568)))) ~(~(~(~(~1333568 ~1333568) ~(~1333568 ~1333568))
~(~(~1333568 ~1333568) ~(~1333568 ~1333568))) ~(~(~(~1333568 ~1333568) ~(~1333568 ~1333568)) ~(~(~1333568 ~1333568) ~(~1333568 ~1333568))))))
~(~(~(~(~(~(~29886600 ~29886600) ~(~29886600 ~29886600)) ~(~(~29886600 ~29886600) ~(~29886600 ~29886600))) ~(~(~(~29886600 ~29886600)
~(~29886600 ~29886600)) ~(~(~29886600 ~29886600) ~(~29886600 ~29886600)))) ~(~(~(~(~29886600 ~29886600) ~(~29886600 ~29886600))
~(~(~29886600 ~29886600) ~(~29886600 ~29886600))) ~(~(~(~29886600 ~29886600) ~(~29886600 ~29886600)) ~(~(~29886600 ~29886600)
~(~29886600 ~29886600))))) ~(~(~(~(~(~1333568 ~1333568) ~(~1333568 ~1333568)) ~(~(~1333568 ~1333568) ~(~1333568 ~1333568)))
~(~(~(~1333568 ~1333568) ~(~1333568 ~1333568)) ~(~(~1333568 ~1333568) ~(~1333568 ~1333568)))) ~(~(~(~(~1333568 ~1333568) ~(~1333568 ~1333568))
~(~(~1333568 ~1333568) ~(~1333568 ~1333568))) ~(~(~(~1333568 ~1333568) ~(~1333568 ~1333568)) ~(~(~1333568 ~1333568) ~(~1333568 ~1333568))))))))
~(~(~(~(~(~29886600 ~29886600) ~(~29886600 ~29886600)) ~(~(~1333568 ~1333568) ~(~1333568 ~1333568))) ~(~(~(~29886600 ~29886600)
~(~29886600 ~29886600)) ~(~(~1333568 ~1333568) ~(~1333568 ~1333568)))) ~(~(~(~(~29886600 ~29886600) ~(~29886600 ~29886600))
~(~(~1333568 ~1333568) ~(~1333568 ~1333568))) ~(~(~(~29886600 ~29886600) ~(~29886600 ~29886600)) ~(~(~1333568 ~1333568) ~(~1333568 ~1333568))))));

是不是很炫酷!快去把自己代碼里的運算都替換成它吧,然後你再也不會想維護自己的代碼了。

-------------------------------------

講正經的,這種技巧在生產環境中沒什麼好處,還會增加維護難度和運行時間。維護難度看上面的例子就懂了,運行時間的話,在絕大多數現代編譯器上,普通的交換變數都會被發現並且優化,比起幾個數學運算不知道快到哪裡去了。

這也不算是演算法,演算法是什麼可以參看百科或者《演算法》《演算法導論》。

這也不算軟體工程,同可參看百科和書。

在工作中,軟體工程、設計模式、軟體架構設計這些知識才是有用有效的「技巧」,比如在寫不能隨手完成的項目之前先畫出UML類圖、在複雜的一對多架構中使用觀察者模式、隨時對自己的代碼做單元測試

-------------------------------------

關於上面那個小技巧,其實就是用邏輯指令模擬了一個NOR(或非門),即當且僅當兩個輸入皆為假時輸出真)。而nor又可以用來模擬任何邏輯指令,算數運算又可以由邏輯運算模擬。

wiki鏈接:Logical NOR

nor模擬邏輯指令(圖片截自上面的wiki鏈接)另外,NAND(與非門)也可以實現類似的效果,在這裡就不提了,上面wiki都能找到。

-------------------------------------

最後,如果你鐵了心要玩特技,這個答案可以幫到你,也可以看看這個問題下的其他答案。

你見過哪些令你瞠目結舌的C/C++代碼技巧? - Long Han 的回答


int類型32位我們可以知道最大值是2147483647 。

如果a和b都很大,有可能這樣加會爆掉。

抑或的那個a ^= b ^= a ^= b如果在兩個數相等的時候也有問題。

我覺得,如果你有興趣,最好再深入了解一丟丟,把陷阱也一帶了解一下。

所以我推薦《c陷阱》這本書。


既然這種「演算法」好,不看彙編肯定是沒有說服力的。以下是多種C語言交換變數的彙編:

一、傳統的臨時變數法

tmp = a;

a = b;

b = tmp;

把上述代碼編譯:

mov eax,dword ptr [a]

mov dword ptr [tmp],eax

mov eax,dword ptr [b]

mov dword ptr [a],eax

mov eax,dword ptr [tmp]

mov dword ptr [b],eax

6個mov指令,很直觀,用eax寄存器做倒手,完成三次變數賦值操作,每次賦值是2個mov指令。效果中規中矩,何樂而不為?

二、加減法:

a = a+b;

b = a-b;

a = a-b;

彙編:

mov eax,dword ptr [a]

add eax,dword ptr [b]

mov dword ptr [a],eax

mov eax,dword ptr [a]

sub eax,dword ptr [b]

mov dword ptr [b],eax

mov eax,dword ptr [a]

sub eax,dword ptr [b]

mov dword ptr [a],eax

6個mov+1個add+2個sub,一共9個指令!你可能會說,為什麼會這樣?C語言里同樣是3個語句,為啥臨時變數法和加減法會產生不同的彙編指令數量?這是因為加減法需要先將操作數複製到寄存器(eax)里,再做加減法運算。事實證明這樣做除了節省一個整數的空間外,在效率和可讀性上遠比臨時變數法差。平常千萬不要用。

三、異或法:

a = a^b;

b = a^b;

a = a^b;

彙編:

mov eax,dword ptr [a]

xor eax,dword ptr [b]

mov dword ptr [a],eax

mov eax,dword ptr [a]

xor eax,dword ptr [b]

mov dword ptr [b],eax

mov eax,dword ptr [a]

xor eax,dword ptr [b]

mov dword ptr [a],eax

同樣是9個彙編指令,和加減法一樣,需要把變數移入eax再做計算。考慮到異或運算快於加減運算,所以只比加減法好一丁點。這方法也非常糟糕,平常千萬不要用。

四、內聯彙編版

有沒有既不需要內存變數又高效的方法呢,有!那就是內聯彙編

__asm{

mov eax,a

xchg eax,b

mov a,eax

}

將內聯彙編代碼插入__asm{}塊內,簡簡單單三句指令,第一句將a複製入寄存器eax內,第二句將eax和b交換,第三句將eax複製入a內。這樣做的優點就是充分發揮了彙編的高效,不需要額外內存變數做臨時變數。缺點是代碼可讀性差,不懂彙編的讀著會懵逼。所以,該不該這樣用,還得看實際情況

——————————

根據評論區給出的建議,已將原來的「反彙編」改為「彙編」和「編譯」

雖然我的彙編代碼是用VS的反彙編查看的,但這裡用「編譯」才是這種優化方法的思想本質。


謝邀。

a=a^b;b=a^b;a=a^b;


推薦閱讀:

程序中的變數名總是起的很長怎麼辦?英文單詞的縮寫有規律么?
如果重新設計C#你最希望增加什麼特性,去掉什麼特性,改變什麼語法?
Unity3d&Cocos2dx進階書籍推薦?
new和malloc內部的實現方式有什麼區別?
如何高效的學習Nginx源碼,汲取養分?

TAG:編程 | 軟體工程 | C編程語言 | C | CC |