需求:将一个长度为n的有序数组变为一个随机乱序数组
(答案在文末)
方法一:
//伪代码for (int i = 0;i < n; ++i){srand((unsigned)time(NULL));swap(arr[i],arr[rand()%n]);}
问题:
1、产生了n^2方种情况,显然不是排列组合中n!的整数倍,所以这种乱序方法是有问题的
如果不理解,我们下面进行实践
srand((unsigned)time(NULL));vector<int> arr = {4,10,3,5,7,1,6,9,2,8};// vector<int> arr = {1,2,3};long result[10][10] = {0};for (int q=0;q<THOUSAND*THOUSAND;++q){for (int i=0;i<arr.size();++i){swap(arr[i],arr[rand()%arr.size()]);}for (int i = 0;i<arr.size();++i){for (int j = 0; j<arr.size(); ++j){if (arr[i] == j+1){++result[i][j];break;}}}}cout<<" p | ";for (int i = 0;i<arr.size();++i){cout<<setw(6)<<i+1<<" | ";}cout<<endl;for (int i = 0;i<arr.size();++i){cout <<setw(2)<< i+1<<" | ";for (int j = 0; j<arr.size(); ++j){cout<<setw(8)<<result[i][j]*1.0/(THOUSAND*THOUSAND) << " ";}cout<<endl;}return 0;

显然概率是有规律的
如果觉得不明显,可以打印长度为3的数组
方法二:
因为排列组合有n!种情况,所以产生一个随机数rand()%(n的阶乘),根据产生的随机数确定排列组合
问题:
1、如果n很大,n!必然更大,产生的随机数可能没那么大
2、根据随机数确定排列组合是一个时间复杂度为O(n^2),且涉及到n!的不优雅的算法
方法三:
遍历n次,每次选择随机两个或一个数交换。
int main(){long long num=0;srand((unsigned)time(NULL));vector<int> arr = {4,10,3,5,7,7,6,4,2,8};for (int q=0;q<THOUSAND*THOUSAND;++q){sortByBubbing1_0(arr);//排序for (int i=0;i<arr.size();++i){swap(arr[rand()%arr.size()],arr[rand()%arr.size()]);}if (arr[0]==2) ++num;}cout<<num*1.0/(THOUSAND*THOUSAND)<<endl;return 0;}
产生n^3种情况,错误和方法一类似,输出结果大概为0.2,显然是不对的
方法四:
与第二种方法思路类似,但用的是链表实现,且有n个随机数
int main(){long long num=0;vector<int> arr = {4,10,3,5,7,1,6,9,2,8};long result[10][10] = {0};srand((unsigned)time(NULL));for (int q=0;q<THOUSAND*THOUSAND;++q){list<int>::iterator iter;list<int> dataListCopy;list<int> dataList = {0,1,2,3,4,5,6,7,8,9};// 进行乱序for (int i=dataList.size();i>0;--i){int n = rand()%i;int j = 0;for (iter = dataList.begin(); iter != dataList.end(); ++iter){if (j==n){dataListCopy.push_back(*iter);dataList.erase(iter);break;}++j;}}// 统计结果int k = 0;for (iter = dataListCopy.begin(); iter != dataListCopy.end(); ++iter){for (int j = 0; j<arr.size(); ++j){if (*iter == j){++result[k][j];break;}}++k;}}// 输出结果cout<<" p | ";for (int i = 0;i<arr.size();++i){cout<<setw(6)<<i+1<<" | ";}cout<<endl;for (int i = 0;i<arr.size();++i){cout <<setw(2)<< i+1<<" | ";for (int j = 0; j<arr.size(); ++j){cout<<setw(8)<<result[i][j]*1.0/(THOUSAND*THOUSAND) << " ";}cout<<endl;}return 0;}

方法五:
通过思考方法一与方法四,我们发现只要小小的修改一下方法一就能达到方法四的目的(后来我才了解到这是knuth洗牌算法)
// 将数组的头部看成是长度为i+1的原链表,将数组的尾部看成是长度为n-i-1的一个链表,// 每次从头部中随机选一个元素插入到尾部链表的头部,for (int i=arr.size()-1; i > 0 ;--i){swap(arr[i],arr[rand()%(i+1)]);}

答案虽然重要,但更重要的是思考的过程,它能让我们成长。
点击关注,持续推送质量好文;如有不当,欢迎指出;转载请注明出处。
