洗牌算法

所謂的洗牌算法,大概意思就是將一組數隨機打亂,說到隨機,很容易就想到C裏面的rand函數,en...說到這個函數,看看該函數如何使用

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
	srand(time(NULL));
	for(int i=1;i <= 10;++i)
	{
		printf("%d\r\n",rand());
	}
	return 0;
}

這裏我打印了10個數,可以快速的運行幾組程序,其實可以發現每組程序出來的結果會很接近或者相似,爲啥呢,可以跟進去看看源碼

// Seeds the random number generator with the provided integer.
extern "C" void __cdecl srand(unsigned int const seed)
{
    __acrt_getptd()->_rand_state = seed;
}



// Returns a pseudorandom number in the range [0,32767].
extern "C" int __cdecl rand()
{
    __acrt_ptd* const ptd = __acrt_getptd();

    ptd->_rand_state = ptd->_rand_state * 214013 + 2531011;
    return (ptd->_rand_state >> 16) & RAND_MAX;
}

可以看出,其算法之簡陋,其代碼中的_rand_state也就是種子,所以當srand傳人一個固定值的話,每次隨機出來的值也會一樣。我們使用時間當種子,當快速運行幾組程序後,其結果當然也會近似。所以在一些重要的工作場合中,要慎用rand函數隨機,畢竟可能可以預測哦。

這裏的話扯遠了,我們學習就先用這個rand函數吧,那麼洗牌中的打亂問題我們可以解決了,下面解決下一個問題,當隨機出來一個數後,我們必然要保證這個隨機數不能重複出現,畢竟牌值是唯一的。

很容易想到一種方案,那就是遇到重複就重新在隨機一個,en,那麼當範圍值很大的話,可以考慮下可行不?

假設打亂十萬個數,這個時候,越到後面,或者說還剩餘一兩個數的時候,那麼太困難了,雖然可能最後可能可以實現,不過時間成本太大,無限猴子定理,哈哈。

所以換種思路,上面的思路根本原因是沒有把隨機的範圍給縮小,如何縮小,直接把篩選出來的數扔出去唄,重新在這個範圍內選擇,即可以確保其不會重複,也會縮小範圍,大概的算法思想是這樣,具體看下面分析

假設數組有10個元素,其元素值依次爲1 ~ 10,這裏的話因爲涉及到縮小範圍,所以我們需要的是隨機下標值,如下:

那麼我們隨機的範圍就是[0,9],假設我們隨機出來的是5,那麼我們只需將5下標對應的數值6和10進行交換,再將其end指針往前挪一位即可

後面我們的隨機範圍就變成了[0,8],假設此時隨機出來爲3,那麼變換後結果如下:

後面的步驟就不多說了,重複即可。

參考代碼:

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
	srand(time(NULL));
	
	int nArr[10] = {0};
	int nLen = sizeof(nArr)/sizeof(int);
	int nEnd = nLen;
	
	for(int i=0;i < nLen;++i)
	{
		nArr[i] = i + 1;
	}
	//洗牌算法核心 
	while(nEnd)
	{
		int index = rand() % nEnd; //隨機出來[0,nEnd-1]
		--nEnd;//縮小範圍 
		if(index != nEnd)
		{//交換 
			nArr[index] ^= nArr[nEnd];
			nArr[nEnd] ^= nArr[index];
			nArr[index] ^= nArr[nEnd];
		}
	}
	
	printf("洗牌後結果: \r\n");
	for(int i=0;i < nLen;++i)
	{
		printf("%d ",nArr[i]);
	}
	printf("\r\n");
	return 0;
}

測試結果如下:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章