【隨機演算法】洗牌
$0 384. 打亂陣列
直接使用 std::shuffle
的寫法
class Solution {
public:
Solution(vector<int>& nums) {
vec = nums;
}
vector<int> reset() {
return vec;
}
vector<int> shuffle() {
vector<int> result = vec;
int seed = rand();
std::default_random_engine dre(seed);
std::shuffle(result.begin(), result.end(), dre);
return result;
}
private:
vector<int> vec;
};
$1 洗牌演算法
(1) 基於抽取 Fisher-Yates Shuffle
基本思想就是從原始陣列中隨機取一個之前沒取過的數字到新的陣列中
初始化 vec, new_vec,長度為 n
每一輪從 vec 中取出一個,放到 new_vec 中
當前輪次,vec 的長度為 k
取 0 <= random_idx < k
從 vec 中將 k 取出,放入 new_vec
知道 vec 耗盡
正確性證明
一個元素 m 被放進第 i 個位置的概率為 p,為前 i - 1 個位置選擇元素時,沒有選中 m 的概率乘以第 i 個位置選中 m 的概率
因此
p = n − 1 n × n − 2 n − 1 × . . . × n − i + 1 n − i + 2 × 1 n − i + 1 = 1 n p = \frac{n - 1}{n} \times \frac{n-2}{n-1} \times ... \times \frac{n-i+1}{n-i+2} \times \frac{1}{n-i+1} = \frac{1}{n} p=nn−1×n−1n−2×...×n−i+2n−i+1×n−i+11=n1
程式碼
時間 O ( N 2 ) O(N^{2}) O(N2),空間 O ( N ) O(N) O(N)
class Solution {
public:
Solution(vector<int>& nums) {
vec = nums;
int seed = rand();
dre = std::default_random_engine(seed);
dr = std::uniform_real_distribution<double>(0.0, 1.0);
}
vector<int> reset() {
return vec;
}
vector<int> shuffle() {
vector<int> tmp(vec.begin(), vec.end());
vector<int> result;
int n = tmp.size();
for(int i = 0; i < n; ++i)
{
int k = tmp.size();
int random_idx = floor(dr(dre) * k);
result.push_back(tmp[random_idx]);
tmp.erase(tmp.begin() + random_idx);
}
return result;
}
private:
vector<int> vec;
std::default_random_engine dre;
std::uniform_real_distribution<double> dr;
};
(2) 基於交換 Knuth-Durstenfeld Shuffle
對基於抽取的 Fisher-Yates Shuffle 的優化。每次抽取出的牌直接交換到原陣列尾部,而不是從原陣列刪掉再插入到新陣列。
程式碼
時間 O ( N ) O(N) O(N),空間 O ( N ) O(N) O(N)
class Solution {
public:
Solution(vector<int>& nums) {
vec = nums;
int seed = rand();
dre = std::default_random_engine(seed);
dr = std::uniform_real_distribution<double>(0.0, 1.0);
}
vector<int> reset() {
return vec;
}
vector<int> shuffle() {
vector<int> result(vec.begin(), vec.end());
int n = result.size();
for(int k = n; k >= 1; --k)
{
int random_idx = floor(dr(dre) * k);
swap(result[random_idx], result[k - 1]);
}
return result;
}
private:
vector<int> vec;
std::default_random_engine dre;
std::uniform_real_distribution<double> dr;
};
(3) 基於插入 Inside-Out Algorithm
Inside-Out Algorithm 演算法的基本思思是從前向後掃描資料,把位置 i 的資料隨機插入到前 i 個位置中:
插入位置確定:當前是原陣列第 i 個位置(vec[i]
),取 [0, i]
範圍的隨機下標 random_idx = floor(dr(dre) * (i + 1))
,
將 vec[i]
放進新陣列的 random_idx
,但該位置可能已經插入了前面的值。此時將原有的值交換到新陣列的 i 位置(此位置當前肯定沒有插入過元素)。
其實效果相當於新陣列中位置 k 和位置 i 的數字進行互動。
正確性證明
對於 nums[i]
,在新陣列中的位置為 j。
0 <= j <= i
第 i 次剛好隨機放到了 j 位置,在後面的 n - i 次選擇中該數字不被選中
p ( j ) = 1 i × i i + 1 × i + 1 i + 2 × . . . × n − 1 n = 1 n p(j) = \frac{1}{i} \times \frac{i}{i+1} \times \frac{i+1}{i+2} \times ... \times \frac{n-1}{n} = \frac{1}{n} p(j)=i1×i+1i×i+2i+1×...×nn−1=n1
i + 1 <= j < n
假設是第 k 次 (i+1 <= k < n
) 隨機到了 j 位置。在後面的 n-k 次選擇中該數字不被選中。
p ( j ) = 1 k × k k + 1 × k + 1 k + 2 × . . . × n − 1 n = 1 n p(j) = \frac{1}{k} \times \frac{k}{k+1} \times \frac{k+1}{k+2} \times ... \times \frac{n-1}{n} = \frac{1}{n} p(j)=k1×k+1k×k+2k+1×...×nn−1=n1
程式碼
時間 O ( N ) O(N) O(N),空間 O ( N ) O(N) O(N)
class Solution {
public:
Solution(vector<int>& nums) {
vec = nums;
int seed = rand();
dre = std::default_random_engine(seed);
dr = std::uniform_real_distribution<double>(0.0, 1.0);
}
vector<int> reset() {
return vec;
}
vector<int> shuffle() {
int n = vec.size();
vector<int> result(n);
for(int i = 0; i < n; ++i)
{
int random_idx = floor(dr(dre) * (i + 1));
swap(result[random_idx], result[i]);
result[random_idx] = vec[i];
}
return result;
}
private:
vector<int> vec;
std::default_random_engine dre;
std::uniform_real_distribution<double> dr;
};
$2 std::shuffle
的實現
演算法
Ref: 《計算機程式設計藝術》 $3-4-2
void shuffle(RandomAccessIterator first, RandomAccessIterator last, RandomNumberGenerator& rand)
{
if(first == last) return;
for(auto i = first + 1; i != last; ++i)
iter_swap(i, first + rand((i - first) + 1));
}
程式碼測試
class Solution {
public:
Solution(vector<int>& nums) {
vec = nums;
int seed = rand();
dre = std::default_random_engine(seed);
dr = std::uniform_real_distribution<double>(0.0, 1.0);
}
vector<int> reset() {
return vec;
}
vector<int> shuffle() {
vector<int> result = vec;
shuffle(result.begin(), result.end(), dre);
return result;
}
private:
void shuffle(vector<int>::iterator first, vector<int>::iterator last, std::default_random_engine& rand)
{
if(first == last) return;
std::uniform_real_distribution<double> dr(0.0, 1.0);
for(auto i = first + 1; i != last; ++i)
iter_swap(i, first + floor(dr(rand) * (i - first + 1)));
}
vector<int> vec;
std::default_random_engine dre;
std::uniform_real_distribution<double> dr;
};
相關文章
- 實現陣列的隨機排序(含洗牌演算法)陣列隨機排序演算法
- 洗牌演算法擴充(從n個數中隨機m個數)演算法隨機
- 演算法題:洗牌演算法演算法
- 隨機演算法隨機演算法
- golang洗牌演算法實現Golang演算法
- 隨機森林演算法隨機森林演算法
- 隨機森林演算法梳理隨機森林演算法
- javaScript隨機排序演算法JavaScript隨機排序演算法
- CUDA 的隨機數演算法 API隨機演算法API
- 「演算法」貪心與隨機化演算法隨機
- 隨機森林演算法深入淺出隨機森林演算法
- 關於洗牌演算法的錯誤認識演算法
- 打造屬於自己的underscore系列(六)- 洗牌演算法演算法
- 隨機化演算法(5) — 蒙特卡羅(Monte Carlo)演算法隨機演算法
- 隨機森林演算法原理與Python實現隨機森林演算法Python
- 機器學習演算法系列(十八)-隨機森林演算法(Random Forest Algorithm)機器學習演算法隨機森林randomRESTGo
- Bagging與隨機森林(RF)演算法原理總結隨機森林演算法
- 洗牌演算法及 random 中 shuffle 方法和 sample 方法淺析演算法random
- python生成隨機數、隨機字串Python隨機字串
- CTF中的最佳化隨機演算法(爬山&退火)隨機演算法
- 基於量子隨機遊走的影像加密演算法隨機加密演算法
- 通俗易懂--決策樹演算法、隨機森林演算法講解(演算法+案例)演算法隨機森林
- 用Python寫演算法 | 蓄水池演算法實現隨機抽樣Python演算法隨機
- WTP的大洗牌
- 演算法隨筆——manacher演算法
- Book of Shaders 03 - 學習隨機與噪聲生成演算法隨機演算法
- 基於隨機森林演算法進行硬碟故障預測隨機森林演算法硬碟
- Spotify如何使用抖動演算法隨機播放歌曲?演算法隨機
- RandomForest 隨機森林演算法與模型引數的調優randomREST隨機森林演算法模型
- 資料結構和演算法面試題系列—隨機演算法總結資料結構演算法面試題隨機
- 快取演算法:LRU、LFU、隨機替換等常見演算法簡介快取演算法隨機
- 演算法金 | 決策樹、隨機森林、bagging、boosting、Adaboost、GBDT、XGBoost 演算法大全演算法隨機森林
- Linux Shell 生成隨機數和隨機字串Linux隨機字串
- 隨機數隨機
- 隨機抽樣一致性(RANSAC)演算法詳解隨機演算法
- 《演算法筆記》3. 歸併排序、隨機快排整理演算法筆記排序隨機
- 【ECMAScript】睡眠、陣列洗牌陣列
- Python如何隨機生成1到100的隨機數?Python隨機