(轉)交換兩個變數的值,不使用第三個變數的四種法方

来源:http://www.cnblogs.com/Dylansuns/archive/2017/06/07/6959513.html
-Advertisement-
Play Games

通常我們的做法是(尤其是在學習階段):定義一個新的變數,藉助它完成交換。代碼如下: int a,b; a=10; b=15; int t; t=a; a=b; b=t; 這種演算法易於理解,特別適合幫助初學者瞭解電腦程式的特點,是賦值語句的經典應用。在實際軟體開發當中,此演算法簡單明瞭,不會產生歧義, ...


通常我們的做法是(尤其是在學習階段):定義一個新的變數,藉助它完成交換。代碼如下:
int a,b;
a=10; b=15;
int t;
t=a; a=b; b=t;

這種演算法易於理解,特別適合幫助初學者瞭解電腦程式的特點,是賦值語句的經典應用。在實際軟體開發當中,此演算法簡單明瞭,不會產生歧義,便於程式員之間的交流,一般情況下碰到交換變數值的問題,都應採用此演算法(以下稱為標準演算法)。


上面的演算法最大的缺點就是需要藉助一個臨時變數。那麼不藉助臨時變數可以實現交換嗎?答案是肯定的!這裡我們可以用三種演算法來實現:1)算術運算;2)指針地址操作;3)位運算;4)棧實現。
1) 算術運算
int a,b;
a=10;b=12;
a=b-a; //a=2;b=12
b=b-a; //a=2;b=10
a=b+a; //a=10;b=10

它的原理是:把a、b看做數軸上的點,圍繞兩點間的距離來進行計算。

具體過程:第一句“a=b-a”求出ab兩點的距離,並且將其保存在a中;第二句“b=b-a”求出a到原點的距離(b到原點的距離與ab兩點距離之差),並且將其保存在b中;第三句“a=b+a”求出b到原點的距離(a到原點距離與ab兩點距離之和),並且將其保存在a中。完成交換。
此演算法與標準演算法相比,多了三個計算的過程,但是沒有藉助臨時變數。(以下稱為算術演算法) 缺點:是只能用於數字類型,字元串之類的就不可以了。a+b有可能溢出(超出int的範圍),溢出是相對的, +了溢出了,-回來不就好了,所以溢出不溢出沒關係,就是不安全。

2) 指針地址操作
因為對地址的操作實際上進行的是整數運算,比如:兩個地址相減得到一個整數,表示兩個變數在記憶體中的儲存位置隔了多少個位元組;地址和一個整數相加即“a+10”表示以a為基地址的在a後10個a類數據單元的地址。所以理論上可以通過和算術演算法類似的運算來完成地址的交換,從而達到交換變數的目的。即:
int *a,*b; //假設
*a=new int(10);
*b=new int(20); //&a=0x00001000h,&b=0x00001200h
a=(int*)(b-a); //&a=0x00000200h,&b=0x00001200h
b=(int*)(b-a); //&a=0x00000200h,&b=0x00001000h
a=(int*)(b+int(a)); //&a=0x00001200h,&b=0x00001000h

通過以上運算a、b的地址真的已經完成了交換,且a指向了原先b指向的值,b指向原先a指向的值了嗎?上面的代碼可以通過編譯,但是執行結果卻令人匪夷所思!原因何在?

首先必須瞭解,操作系統把記憶體分為幾個區域:系統代碼/數據區、應用程式代碼/數據區、堆棧區、全局數據區等等。在編譯源程式時,常量、全局變數等都放入全局數據區,局部變數、動態變數則放入堆棧區。這樣當演算法執行到“a=(int*)(b-a)”時,a的值並不是0x00000200h,而是要加上變數a所在記憶體區的基地址,實際的結果是:0x008f0200h,其中0x008f即為基地址,0200即為a在該記憶體區的位移。它是由編譯器自動添加的。因此導致以後的地址計算均不正確,使得a,b指向所在區的其他記憶體單元。再次,地址運算不能出現負數,即當a的地址大於b的地址時,b-a<0,系統自動採用補碼的形式表示負的位移,由此會產生錯誤,導致與前面同樣的結果。
有辦法解決嗎?當然!以下是改進的演算法:
if(a<b)
{
a=(int*)(b-a);
b=(int*)(b-(int(a)&0x0000ffff));
a=(int*)(b+(int(a)&0x0000ffff));
}
else
{
b=(int*)(a-b);
a=(int*)(a-(int(b)&0x0000ffff));
b=(int*)(a+(int(b)&0x0000ffff));
}

演算法做的最大改進就是採用位運算中的與運算“int(a)&0x0000ffff”,因為地址中高16位為段地址,後16位為位移地址,將它和0x0000ffff進行與運算後,段地址被屏蔽,只保留位移地址。這樣就原始演算法吻合,從而得到正確的結果。

此演算法同樣沒有使用第三變數就完成了值的交換,與算術演算法比較它顯得不好理解,但是它有它的優點即在交換很大的數據類型時,它的執行速度比算術演算法快。因為它交換的時地址,而變數值在記憶體中是沒有移動過的。(以下稱為地址演算法)

3) 位運算
int a=10,b=12; //a=1010^b=1100;
a=a^b; //a=0110^b=1100;
b=a^b; //a=0110^b=1010;
a=a^b; //a=1100=12;b=1010;

此演算法能夠實現是由異或運算的特點決定的,通過異或運算能夠使數據中的某些位翻轉,其他位不變。這就意味著任意一個數與任意一個給定的值連續異或兩次,值不變。

4)棧實現。不多解釋了,棧和相關函數定義省去。
int exchange(int x,int y) 
{ 
     stack S; 
     push(S,x); 
     push(S,y); 
     x=pop(S); 
     y=pop(S); 
}

以上演算法均實現了不藉助其他變數來完成兩個變數值的交換,相比較而言算術演算法和位演算法計算量相當,地址演算法中計算較複雜,卻可以很輕鬆的實現大類型(比如自定義的類或結構)的交換,而前兩種只能進行整形數據的交換(理論上重載“^”運算符,也可以實現任意結構的交換)。

介紹這三種演算法並不是要應用到實踐當中,而是為了探討技術,展示程式設計的魅力。從中可以看出,數學中的小技巧對程式設計而言具有相當的影響力,運用得當會有意想不到的神奇效果。而從實際的軟體開發看,標準演算法無疑是最好的,能夠解決任意類型的交換問題。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 註意:轉載自併發編程網 – ifeve.com本文鏈接地址: Java NIO系列教程(二) Channel Channel Java NIO的通道類似流,但又有些不同: 既可以從通道中讀取數據,又可以寫數據到通道。但流的讀寫通常是單向的。 通道可以非同步地讀寫。 通道中的數據總是要先讀到一個Buff ...
  • 註意: 轉載自併發編程網 – ifeve.com本文鏈接地址: Java NIO系列教程(一) Java NIO 概述 JAVA-1NIO概述 Java NIO 由以下幾個核心部分組成: Channels Buffers Selectors 雖然Java NIO 中除此之外還有很多類和組件,但在我看 ...
  • idea啟動TOMCAT html 亂碼,在運行/調試 配置對話框的Startup/Connection面板中,勾選Pass environment variables.並添加一個environment variable,Name填 JAVA_TOOL_OPTIONS, Value填 -Dfile.... ...
  • 作業:購物商城 商品展示,價格 買,加入購物車 付款,錢不夠 流程圖如下: 代碼共有4個文件,如下: 用戶文件: 商品文件: 購物車文件: 錢包文件: 代碼如下: 上述代碼運行流程如下: (1)展示商品信息; (2)用戶登錄驗證; (3)用戶輸入想購買產品及數量,輸入quit退出購物; (4)添加到 ...
  • 實際項目中pom.xml依賴寫法: Maven 安裝 JAR 包的命令是: 例如我的這個spring-context-support-3.1.0.RELEASE.jar 文件放在了"D:\mvn\"中 則命令為(在cmd中執行): mvn install:install-file -Dfile=D: ...
  • 基於51單片機的萬年曆,用到了單片機獨立鍵盤、數位管、LED燈模塊實現。 想要簡單還是DS1302好用。 -- -- -- 參考: http://www.cnblogs.com/LXSYD-C/p/6364888.html 如有錯誤還請指出,如有侵權還請告知,如需轉載請註明出處! 本人博客:http ...
  • 給人搬了十幾個網站,老站用西部數位建站助手創建的,現在過期了無法繼續創建,只能在Internet 信息服務(IIS)管理器創建網站,創建下來都沒問題,但是就是無法打開網站。 測試打開txt文檔、靜態頁面都能打開,一到打開php文件就直接就掛了,無法打開,什麼報錯都沒有。 之前有用iis6以外的伺服器 ...
  • 嘗試用springmvc,mybatis,mysql做個工具平臺。 在本地mac筆記本上運行正常,但把包放置到伺服器上,啟動tomcat就報錯。類找不到了。 文件目錄: 實現需求:上傳文檔並記錄在資料庫中。自建了DocFile類。創建對應的mapper文件寫sql語句。 mapper.xml中nam ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...