VB程序設計
VB6.0似乎已經成為開發者的往事,但是Microsoft Office系列軟件的控制和操作仍然是基于VB6.0的。本項目是為所有學習VB6.0的同學而建立的。
問題1: 排序算法
排序算法是計算機程序設計中最經典的算法,在計算機的專業課《數據結構》中有大量的討論和分析。我們在這里只討論最簡單的選擇排序和冒泡排序。討論從一個填空題開始。
' 功能:將數組a()中的元素按從小到大排序'
' 參數:數組a()的下標索引為從1到30'
Sub bubble_sort(a() As Integer)
For i = 1 To 29
For j = 【2】
If a(i) > a(j) Then
m = a(i): a(i) = a(j): a(j) = m
End If
Next j
Next i
End Sub
問題:【2】這個地方應該填什么?是 i+1 To 30?還是 1 To 30-i?
- 首先,最正確的姿勢是記住!甭管是冒泡還是選擇排序,這里j的取值應該與i正好錯開1,【2】處應該填:i+1 To 30
- 其次,理解排序的原理。為什么不是:1 To 30-i呢?
首先,我們要明確目標。因為要對數組a()從小到大排序,我們期望:依次掃描a()中的每個元素,每掃描到一個元素a(i),都使得它是剩下的元素(從a(i)到a(30))中最小的。怎么做到呢?可以從a(i)一直掃描到a(30),找到最小的元素a(p),令a(i)和a(p)的值交換(注意:這里不能直接讓a(i)=a(p),否則,a(i)的原有值就不見了)。用下面的偽代碼表示:
'代碼塊1
Sub selection_sort(a() As Integer)
For i = 1 To 30
'從a(i) 到 a(30)中找到最小的元素a(p)'
'將a(i)和a(p)的值交換'
Next i
End Sub
那么,怎么從a(i) 到 a(30)中找到最小的元素a(p)呢?可以這樣做:
'代碼塊2
p = a(i) '首先,假設a(i)就是最小的'
For j = i To 30 '從a(i) 掃描到 a(30)'
If p > a(j) Then '一旦發現p比a(j)還大,就讓p指向a(j)'
p = a(j)
End If
Next j
上述做法,就是選擇排序的思想。我們可以將代碼優化下:
首先,j 不必從 i 開始掃描,因為當 j = i 時,a(j)沒有必要跟a(p)進行比較(這時,p 也等于 i)。
其次,p 指向a(i)的索引就可以了,沒有必要指向a(i)。
'代碼塊3
p = i ' 假設a(i)就是最小的
For j = i+1 To 30
If a(p) > a(j) Then '一旦發現a(p)比a(j)還大,就讓 p 指向 j
p = j
End If
Next j
這時,將代碼塊3嵌入代碼塊1,就可以得到完整的選擇排序的代碼了:
'代碼塊4
Sub selection_sort(a() As Integer)
For i = 1 To 30
'從a(i) 到 a(30)中找到最小的元素a(p)
p = i '假設a(i)就是最小的
For j = i+1 To 30
If a(p) > a(j) Then '一旦發現a(p)比a(j)還大,就讓 p 指向 j
p = j
End If
Next j
'將a(i)和a(p)的值交換(代碼略)'
Next i
End Sub
- 經過上面的推演,我們就得到了一個雙重循環。反過來,如果直接拿到的就是這樣一個雙重循環的話,會不會感覺閱讀起來很困難?實際上,只要把外層循環 i 固定為某個特定的值來理解內層循環 j 就可以了。
- 再來看冒泡排序法。它與選擇排序法最大的不同在于:在其內層循環 j 中,一旦發現a(p)比a(j)還大,就讓 a(p) 和 a(j) 的值交換,而 p 是始終指向 i 的(因為a(p)中一直存放最小的值,所以,p就沒有必要再移動了)。這樣,每經過一次內層循環,就相當于剩下元素中具有最小值的那個元素(具有最小的質量)從水底下浮出來了,因此稱為“冒泡排序法”。具體代碼如下:
' 代碼塊5
Sub selection_sort(a() As Integer)
For i = 1 To 30
' 從a(i) 到 a(30)中找到最小的元素a(p)
' 假設a(i)就是最小的,并且始終存放剩余元素中的最小值
For j = i+1 To 30
If a(i) > a(j) Then '一旦發現a(i)比a(j)還大,就讓它們交換
m = a(i): a(i) = a(j) : a(j) = m
End If
Next j
Next i
End Sub
- 那么,上面的“選擇排序”和“冒泡排序”哪個更優呢?也就是,哪個排序算法的速度更快呢?顯然是選擇排序。因為冒泡排序在最壞的情況下需要 30-(i+1)+1=30-i 次 a(j) 和 a(i) 的交換,而選擇排序只要交換交換 1 次即可。
- 再來看同學的問題,同學嘗試將內層循環 j 設置為 1 To 30-i 有什么問題呢?事實上,這樣做就意味著:該做的沒有做,不該做的卻費力去做。第一,該做的沒有做。你看,這樣做只能找到從a(1)到a(30-i)中的最小值,那a(30-i)之后的元素呢?第二,不該做的卻費力去做。為了讓 a(i) 為剩下元素中的最小值,還需要做許多冗余的工作:它還要跟它之前的已經比它還小的元素依次比較,這完全是沒有必要的,因為比它大的元素,只可能在它后邊。
- 練習。如果我們非要將內層循環 j 設置為 1 To 30-i 呢?如果非要這樣做,外層循環 i 的設置必須改變,它必須從 a(i) 的最后一個元素倒著走向 a(i) 的第一個元素,以保證:每次a(i)中都存放最大值。這樣的程序該如何寫?請同學們自行練習。