1. 程式人生 > >直接插入排序中的監視哨問題

直接插入排序中的監視哨問題

今天看到直接插入排序,直接排序沒咋懂,倒是直接懵掉了。程式碼看得懂,就是不知道哨兵(又叫監視哨,Sentinel)到底有啥用,於是痛下決心一定要將它搞定。

首先來科普一下直接插入排序(這個沒啥難的,看一遍就懂)。

主要思想:假設之前有一個排列好的陣列,如果再向裡面加入一點新鮮元素(一個新數),那麼首先將這個數放在最後,讓它和前面的同學(數)比較大小,如果遇到比它大的,那麼二者就交換一下位置,直到前面沒有比他大的了,一個新的排列好的陣列也就出來了。根據上面的分析,對於一個給定的陣列,我們假設第一個數就是那個排列好的陣列,後面所有的同學(數)一個一個的加入,那麼第2個同學進來並比較完大小之後,排列好的陣列內就有了兩個元素,第3個同學進來並和前兩個同學比較完大小之後排列好的陣列內就有了三個元素,依此類推……一個新鮮的陣列就排列好了。

實現過程:來不及解釋了,直接上程式碼。

1.        百度百科版


2.        嚴蔚敏老師版


這兩種方法實現起來都對,但是上一種方法不算是設定了監視哨,因此如果處理大量的資料時,方法二要快於方法一。百度百科上的內容和它的程式碼互相矛盾,這也正是為什麼我被搞懵了的原因。以下內容摘抄自百度百科(本文編寫時間2016年6月15日):

哨兵的作用:

演算法中引進的附加記錄R[0]稱監視哨或哨兵(Sentinel)。

哨兵有兩個作用:

① 進人查詢(插入位置)迴圈之前,它儲存了R[i]的副本,使不致於因記錄後移而丟失R[i]的內容;

② 它的主要作用是:在查詢迴圈中"監視"下標變數j是否越界。一旦越界(即j=0),因為R[0].可以和自己比較,迴圈判定條件不成立使得查詢迴圈結束,從而避免了在該迴圈內的每一次均要檢測j是否越界(即省略了迴圈判定條件"j>=1")。

注意:

① 實際上,一切為簡化邊界條件而引入的附加結點(元素)均可稱為哨兵。

【例】單鏈表中的頭結點實際上是一個哨兵

   ② 引入哨兵後使得測試查詢迴圈條件的時間大約減少了一半,所以對於記錄數較大的檔案節約的時間就相當可觀。對於類似於排序這樣使用頻率非常高的演算法,要儘可能地減少其執行時間。所以不能把上述演算法中的哨兵視為雕蟲小技,而應該深刻理解並掌握這種技巧。

我們不妨分析一下兩種方法:

對於百度百科上的方法:假設沒有紅圈標出的j>=0條件,對於初始陣列是[2 4 1 3]而言,如圖,當i= 2時:首先將1存入temp中,然後內層迴圈中讓j從 i =2-1 =1(a[j] =4) 的地方開始遞減,當j = 1時,a[j] = a[1] = 4,滿足a[j] > temp的條件,開始執行for迴圈內部的部分;j變為0,a[j]= a[0] = 2,滿足a[j] > temp的條件,繼續執行for內部的部分;迴圈依舊沒有結束,j卻變為-1,因此出現了下標越界的情況。要解決這種情況的方法就是用j >= 0的條件來約束迴圈,但是這樣每次迴圈都要判斷兩個約束條件顯然不如設定哨兵的方法效率高。

對於設定了哨兵的方法:注意,這種方法需要讓陣列從a[1]開始,將a[0]留出來用作哨兵。如圖,同樣初始陣列為[2 4 1 3],當i = 3時(假設2的角標為1,實際中可以將待排序陣列前面加一個0):將a[3] = 1複製到a[0]中,然後內層迴圈中讓j從 i = 3-1 =2(a[j] = 4) 的地方開始遞減,當j = 2時,a[j] = a[2] =4,滿足a[j] > a[0]的條件,開始執行for迴圈內部的部分;j變為1,a[j] = a[1] = 2,滿足a[j] > a[0],開始執行for迴圈內部的部分;j變為0,a[j] = a[0] = 1,即便變成了哨兵和自己相比較,不滿足條件,退出迴圈,因此用此方法可以省掉迴圈中的一個判斷條件(j>=0)。

經過以上分析,哨兵的設定必須在陣列內部的R[0]上,因為只有這樣才能在查詢迴圈中去掉判斷j是否越界的問題,從而提高效率。

最後,上一個自己寫的沒有設定監視哨的程式碼:

#include <stdio.h>
int main()
{
	int i,j,n,temp;
	int a[10] = {0};
	scanf("%d",&n);
	for(i = 0;i<n;i++)
		scanf("%d",&a[i]);
	for(i = 1;i < n;i++)
	{
		if(a[i] < a[i - 1])
		{
			temp = a[i];
			for(j = i - 1;j >= 0 && a[j] > temp;j--)
			{
				a[j+1] = a[j];
			}
			a[j+1] = temp;
		}
	}
	for(i = 0;i < n;i++)
	{
		printf("%d\n",a[i]);
	}
    //printf("%d",a[-9]);
	return 0 ;
}
監視哨問題解決了,可以愉快的去吃飯了。