簡化版的桶排序不僅僅有上一節所遺留的問題,更要命的是:它非常浪費空間!例如需要排序數的範圍是0~2100000000之間,那你則需要申請2100000001個變量,也就是說要寫成int a[2100000001]。因為我們需要用2100000001個“桶”來存儲0~2100000000之間每一個數出現的次數。即便隻給你5個數進行排序(例如這5個數是1,1912345678,2100000000,18000000和912345678),你也仍然需要2100000001個“桶”,這真是太浪費了空間了!還有,如果現在需要排序的不再是整數而是一些小數,比如将5.56789,2.12,1.1,3.123,4.1234這五個數進行從小大排序又該怎麼辦呢?現在我們來學習另一種新的排序算法:冒泡排序。它可以很好的解決這兩個問題。
冒泡排序的基本思想是:每次比較兩個相鄰的元素,如果他們的順序錯誤就把他們交換過來。
例如我們需要将12 35 99 18 76這5個數進行從大到小進行排序。既然是從大到小排序也就是說越小的越靠後,你是不是覺得我在說廢話,但是這句話很關鍵(∩_∩)。
首先比較第1位和第2位的大小,現在第1位是12,第2位是35。發現12比35要小,因為我們希望越小越靠後嘛,是以需要交換這兩個數的位置。交換之後這5個數的順序是35 12 99 18 76。
按照剛才的方法,繼續比較第2位和第3位的大小,第2位是12,第3位是99。12比99要小,是以需要交換這兩個數的位置。交換之後這5個數的順序是35 99 12 18 76。
根據剛才的規則,繼續比較第3位和第4位的大小,如果第3位比第4位小,則交換位置。交換之後這5個數的順序是35 99 18 12 76。
最後,比較第4位和第5位。4次比較之後5個數的順序是35 99 18 76 12。
經過4次比較後我們發現最小的一個數已經就位(已經在最後一位,請注意12這個數的移動過程),是不是很神奇。現在再來回憶一下剛才比較的過程。每次都是比較相鄰的兩個數,如果後面的數比前面的數大,則交換這兩個數的位置。一直比較下去直到最後兩個數比較完畢後,最小的數就在最後一個了。就如同是一個氣泡,一步一步往後“翻滾”,直到最後一位。是以這個排序的方法有一個很好聽的名字“冒泡排序”。
說道這裡其實我們的排序隻将5個數中最小的一個歸位了。每将一個數歸位我們将其稱為“一趟”。下面我們将繼續重複剛才的過程,将剩下的4個數一一歸位。
好現在開始“第二趟”,目标是将第2小的數歸位。首先還是先比較第1位和第2位,如果第1位比第2位小,則交換位置。交換之後這5個數的順序是99 35 18 76 12。接下來你應該都會了,依次比較第2位和第3位,第3位和第4位。注意此時已經不需要再比較第4位和第5位。因為在第一趟結束後已經可以确定第5位上放的是最小的了。第二趟結束之後這5個數的順序是99 35 76 18 12。
“第三趟”也是一樣的。第三趟之後這5個數的順序是99 76 35 18 12。
現在到了最後一趟“第四趟”。有的同學又要問了,這不是已經排好了嗎?還要繼續?當然,這裡純屬巧合,你可以用别的數試一試可能就不是了。你能找出這樣的資料樣例來嗎?請試一試。
“冒泡排序”原理是:每一趟隻能确定将一個數歸位。即第一趟隻能确定将末位上的數(既第5位)歸位,第二趟隻能将倒數第2位上的數(既第4位)歸位,第三趟隻能将倒數第3位上的數(既第3位)歸位,而現在前面還有兩個位置上的數沒有歸位,是以我們仍然需要進行“第四趟”。
“第四趟”隻需要比較第1位和第2位的大小。因為後面三個位置上的數歸位了,現在第1位是99,第2位是76,無需交換。這5個數的順序不變仍然是99 76 35 18 12。到此排序完美結束了,5個數已經有4個數歸位,那最後一個數也隻能放在第1位了。
最後我們總結一下:如果有n個數進行排序,隻需将n-1個數歸位,也就是說要進行n-1趟操作。而“每一趟”都需要從第1位開始進行相鄰兩個數的比較,将較小的一個數放在後面,比較完畢後向後挪一位繼續比較下面兩個相鄰數的大小,重複此步驟,直到最後一個尚未歸位的數,已經歸位的數則無需再進行比較(已經歸位的數你還比較個啥,浪費表情)。
這個算法是不是很強悍。記得我每次拍集體照的時候就總是被别人換來換去的,當時特别煩。不知道發明此算法的人當時的靈感是否來源于此。羅裡吧嗦地說了這麼多,下面是代碼。建議先自己嘗試去實作一下看看,再來看我是如何實作的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<code>#include <stdio.h></code>
<code>int</code> <code>main()</code>
<code>{</code>
<code> </code><code>int</code> <code>a[100],i,j,t,n;</code>
<code> </code><code>scanf</code><code>(</code><code>"%d"</code><code>,&n); </code><code>//輸入一個數n,表示接下來有n個數</code>
<code> </code><code>for</code><code>(i=1;i<=n;i++) </code><code>//循環讀入n個數到數組a中</code>
<code> </code><code>scanf</code><code>(</code><code>"%d"</code><code>,&a[i]);</code>
<code> </code><code>//冒泡排序的核心部分</code>
<code> </code><code>for</code><code>(i=1;i<=n-1;i++) </code><code>//n個數排序,隻用進行n-1趟</code>
<code> </code><code>{</code>
<code> </code><code>for</code><code>(j=1;j<=n-i;j++) </code><code>//從第1位開始比較直到最後一個尚未歸位的數,想一想為什麼到n-i就可以了。</code>
<code> </code><code>{</code>
<code> </code><code>if</code><code>(a[j]<a[j+1]) </code><code>//比較大小并交換</code>
<code> </code><code>{ t=a[j]; a[j]=a[j+1]; a[j+1]=t; }</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
<code> </code><code>for</code><code>(i=1;i<=n;i++) </code><code>//輸出結果</code>
<code> </code><code>printf</code><code>(</code><code>"%d "</code><code>,a[i]);</code>
<code> </code><code>getchar</code><code>();</code><code>getchar</code><code>();</code>
<code> </code><code>return</code> <code>0;</code>
<code>}</code>
可以輸入以下資料進行驗證
運作結果是
将上面代碼稍加修改,就可以解決第1節遺留的問題,如下。
22
23
24
25
26
27
<code>struct</code> <code>student</code>
<code> </code><code>char</code> <code>name[21];</code>
<code> </code><code>char</code> <code>score;</code>
<code>};</code><code>//這裡建立了一個結構體用來存儲姓名和分數</code>
<code> </code><code>struct</code> <code>student a[100],t;</code>
<code> </code><code>int</code> <code>i,j,n;</code>
<code> </code><code>scanf</code><code>(</code><code>"%d"</code><code>,&n); </code><code>//輸入一個數n</code>
<code> </code><code>for</code><code>(i=1;i<=n;i++) </code><code>//循環讀入n個人名和分數</code>
<code>scanf</code><code>(</code><code>"%s %d"</code><code>,a[i].name,&a[i].score);</code>
<code> </code><code>//按分數從高到低進行排序</code>
<code> </code><code>for</code><code>(i=1;i<=n-1;i++)</code>
<code> </code><code>for</code><code>(j=1;j<=n-i;j++)</code>
<code> </code><code>if</code><code>(a[j].score<a[j+1].score)</code><code>//對分數進行比較</code>
<code> </code><code>for</code><code>(i=1;i<=n;i++)</code><code>//輸出人名</code>
<code> </code><code>printf</code><code>(</code><code>"%s\n"</code><code>,a[i].name);</code>
huhu 5
haha 3
xixi 5
hengheng 2
gaoshou 8
gaoshou
huhu
xixi
haha
hengheng
冒泡排序的核心部分是雙重嵌套循環。不難看出冒泡排序的時間複雜度是O(N2)。這是一個非常高的時間複雜度。冒泡排序早在1956年就有人開始研究,之後有很多人都嘗試過對冒泡排序進行改進,但結果卻令人失望。如Knuth(Donald E. Knuth中文名為高德納,1974年圖靈獎獲得者)所說:“冒泡排序除了它迷人的名字和導緻了某些有趣的理論問題這一事實之外,似乎沒有什麼值得推薦的。”你可能要問:那還有沒有更好的排序算法呢?請期待下周更新——快速排序。
原文章:http://ahalei.blog.51cto.com/4767671/1364401
感謝您的閱讀,您的支援是我寫部落格動力。