(1)问题描述
(以下都是题目老师可能怕我们看不懂详细的就差给我们写出来了)
一只猴子一直在打印机上胡乱打字,只要有无限的时间,总有一天可以恰好打印出一部莎士比亚的著作。这个理论同样可以用在排序上面。如果我们给数组随机排列顺序,每一次排列之后验证数组是否有序,只要次数足够多,总有一次数组刚好被随机成有序数组。
这样的排序算法和猴子用打印机打出莎士比亚著作的理论很相似,所以被称为“猴子排序”。
(2)设计思路
如何对数组元素进行随机排列?一个可行的方法是随机挑选数组中的两个元素,交换它们的位置,重复多次就得到一个随机后的数组。使用C语言和C++语言的标准库函数都可以实现。
C方法:
如果使用C语言系统库,可以使用rand()函数。库函数rand()可以产生在[0, RAND_MAX]范围内的随机整数,可在程序中直接使用,其中RAND_MAX是系统库内定义的宏,代表rand()函数可以产生的最大的整数。通过取余操作可以把生成的整数变换到你想要的范围。例如:rand() % n 会生成[0, n-1]之间的整数。以上函数均在C的标准库stdlib.h中,如果在标准C++项目中,应该使用#include 这个预处理指令。
需要注意的一点是,计算机产生的随机数都是伪随机数,即看起来是随机的,但是如果不做好随机数产生器的初始化,那么每次得到的随机数序列都是相同的。为了程序每次运行得到不同的随机数序列,需要在每次程序运行时给随机数产生器一个不同的种子(基本上就是一个无符号整数)。当然你可以每次让用户输入一个整数作为种子,但是这样肯定会让用户抓狂。比较通用的办法是使用当前系统的时间,比如把当前日期的年月日时分秒的数字加在一起作为种子,这样遇到相同种子的概率是很低的。C标准库里有time函数,该函数返回当前时间,作为随机数种子传给srand函数:srand((unsigned int)time(0)),这样每次程序运行时rand函数就会产生不同的伪随机数序列。
1. srand((unsigned int)time(0));
3. unsigned int a[5];
4. for (int i = 0; i < 5; i++)
5. a[i] = rand();
每次运行上面代码时,数组a里的元素都是不同的随机整数。如果没有srand那行,那么每次运行程序数组a里面的随机数都是相同的,可以自己注释掉试试看。
有了随机数就可以利用它产生随机的数组下标,多次随机交换2个数据元素,得到一个随机排列的数组。
C++的方法:
C++标准库提供了将容器元素随机排序的shuffle方法。关于什么是“容器”等我们学习C++的泛型编程时会讲,现在可以简单地理解,数组就是一种存放数据的容器。
如果你使用的是比较老版的C++标准,比如C++0x,那么可以使用random_shuffle函数。需要添加如下头文件:
- #include // std::random_shuffle
- #include // std::time
- #include // std::rand, std::srand
在有些标准库实现版本里(Windows下的MinGW64,v6.0),random_shuffle函数的随机种子可以由srand函数设定,而有些实现版本不行,那么可以自己定义一个基于rand函数的随机数产生函数:
1. // random generator function:
2. int myrandom (int i) { return std::rand()%i;}
在主函数里,可以调用random_shuffle函数对数组进行乱序:
1. std::srand ( unsigned ( std::time(0) ) );
2.
3. int a[5] = {1, 2, 3, 4, 5};
4.
5. // using built-in random generator:
6. std::random_shuffle ( a, a+5 );
7.
8. // using myrandom:
9. std::random_shuffle ( a, a+5, myrandom);
上面2种不同的random_shuffle函数调用方法,第二个是使用了我们自己定义的随机数产生器,可以根据自己编译器的实现看看两种方法的不同。
如果你使用的是比较新的C++标准,比如C++17删除了C++0x的random_shuffle函数,程序应该使用C++11添加进去的std::shuffle函数。首先需要添加下面的头文件:
1. #include <algorithm> // std::shuffle
2. #include <random> // std::default_random_engine
3. #include <chrono> // std::chrono::system_clock
这样就可以把一个数组里的元素进行随机排列。
1. int a[5] = {1, 2, 3, 4, 5};
2. // 利用系统时间获得一个随机数种子
3. unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
4. shuffle(a, a+5, std::default_random_engine(seed)); // 数组a里面的元素已被乱序
以上都是老师给的题目里的……
以下是我的
//包含的头文件和声明啥的
#include<iostream>
#include<algorithm>
#include<random>
#include<chrono>
#define N 10
int large;
using std::cout;
using std::cin;
using std::endl;
main函数
int main()
{
int num[N];
input_data(num);
while (1)
{
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
shuffle(num, num + large, std::default_random_engine(seed));
//上面介绍的随机排序的方法
if (judge(num))//若成功就输出耶
{
for (int j = 0; j < large; j++)
cout << num[j]<<" ";
return(0);
}
}return(0);
}
不再多说这个数据输入函数
void input_data(int num[])
{
num[100];
int i = 0;
char ch;
do
{
cin >> num[i++];
ch = cin.get();
} while (ch != '\n');
large = i;
}
判断一下是否已经排序完成啦?
int judge(int num[])//传入数组num
{
for (int i = 0; ; )
{
while (num[i] <= num[i + 1])//依此判断是不是顺序的
{
i++;
if (i == large - 1)//i到了倒数第二个(也就是和倒数第一个已经比较过了)
return 1;
}
if (i < large - 1)//如果没比到倒数第二个那么就返回0,失败了
return 0;
}
return 0;
}
这个函数最大的缺点就是没办法输入多个数比较,判断的次数太多了 ,要好久好久好久……
2.4 珠排序
这个排序算法和算盘相似。见过算盘的人都知道,算盘上有许多圆圆的珠子被串在细杆上,就像下面这样:
如果把算盘竖起来,会发生什么呢?算盘上的小珠子会在重力的作用下滑到算盘底部,就像下面这样:
这里有一个很神奇的细节:如果统计每一横排珠子的个数,你会发现下落后每一排珠子数量恰好是下落前珠子数量的升序排列!
比如上面的例子,下落前后每一横排的珠子数量:
那么,我们可以模拟珠子下落的原理,对一组正整数进行排序。用二维数组来模拟算盘,有珠子的位置设为1,没有珠子的位置设为0。那么,一个无序的整型数组就可以转化成下面的二维数组:
接下来,我们模拟算盘珠子掉落的过程,让所有的元素1都落到二维数组的最底部:
最后,把掉落后的 “算盘” 转化成一维有序数组:
这样,排序就完成了。这个排序算法有一个非常形象的名字:珠排序。请你用代码实现这个算法吧。
这老师也太用心良苦了……这图解……
#include<iostream>
#define N 10
using std::cout;
using std::cin;
using std::endl;
int aba[N][N] = {0};
int large ;
void swap(int *a, int *b)//交换函数
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
void input_data(int num[])//输入函数
{
int i = 0;
char ch;
do
{
cin >> num[i];
ch = cin.get();
i++;
} while (ch != '\n');
large = i;
/*cout << large;*/
}
int main()主函数
{
int count = 0;
int num[N];
input_data(num);
//二维数组赋值,变成算盘
for (int i = 0; i < large; i++)
{
for (int j = 0; j < num[i]; j++)
{
aba[i][j] = 1;
}
}
//输出二维数组
//for (int i = 0; i < large; i++)
//{
// for (int j = 0; j < N; j++)
// cout << aba[i][j];
// cout << endl;
//}
//cout << endl;
int p = 0;
//主要的算法
//方法是:一个指针从上到下遍历,另一个从下到上遍历,上面的遇到1,下面的遇到0,就交换,写着还挺麻烦的
//另一种方法:从上到下统计一共有几个1,(也就是几个珠子),在从下到上赋值这n个珠子,写起来简单好多,但是我没想到TAT是别人告诉我滴
for (int j = 0; j < N; j++)
{
for (int i = large-1,p=0; p<i; i--,p++)
{
while (aba[p][j] == 0)
{
p++;
if (i == p)
break;
}
while (aba[i][j] == 1)
{
i--;
if (i == p)
break;
}
if (aba[i][j] == 0 && aba[p][j] == 1)
{
swap(&aba[i][j],&aba[p][j]);
p++;
i--;
}
}
}
//for (int i = 0; i < large; i++)
//{
// for (int j = 0; j < N; j++)
// cout << aba[i][j];
// cout << endl;
//}
for (int i = 0; i < large; i++)//统计珠子数
{
for (int j = 0; j < 20; j++)
{
if (aba[i][j] == 0)
break;
if(aba[i][j] == 1)
count++;
}
num[i] = count;
count = 0;
}
cout << endl;
for (int i = 0; i < large; i++)
cout << num[i]<<" ";
}
贴一下结果叭
**
代码勘误:!!!
**
//修改了原来的写法,增加了几个判断;又增加了一种新的写法;感谢勘误
#include<iostream>
#define N 10
using std::cout;
using std::cin;
using std::endl;
int aba[N][N] = {0};
int large ;
void swap(int *a, int *b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
void input_data(int num[])
{
int i = 0;
char ch;
do
{
cin >> num[i];
ch = cin.get();
i++;
} while (ch != '\n');
large = i;
}
int main()
{
int count = 0;
int num[N];
input_data(num);
//二维数组赋值
for (int i = 0; i < large; i++)
{
for (int j = 0; j < num[i]; j++)
{
aba[i][j] = 1;
}
}
//输出二维数组
//for (int i = 0; i < large; i++)
//{
// for (int j = 0; j < N; j++)
// cout << aba[i][j];
// cout << endl;
//}
//cout << endl;
int p = 0; //修改了原来的写法
for (int j = 0; j < N; j++)
{
for (int i = large-1,p=0; p<=i; i--,p++)
{
while (aba[p][j] == 0)
{
if (i == p) //先判断
break;
p++;
}
while (aba[i][j] == 1)
{
if (i == p) //先判断
break;
i--;
}
for (;aba[i][j] == 0 && aba[p][j] == 1;)
{
swap(&aba[i][j],&aba[p][j]);
if (i == p || p+1==i )//增加判断
break;
p++;
i--;
}
}
}
//更新一种写法
/*int i = 0;
for (int j = 0; j < N; j++)
{
int n = 0; //n是一排算珠的个数
for (; i < large; i++)
{
if (aba[i][j] == 1)
n++;
}
n -= 1;
for (i = large - 1; i >= 0 && n >= 0; i--, n--)//从下到上给算珠付n个值
{
aba[i][j] = 1;
}
for (; i >= 0; i--)//上面的付为0
{
aba[i][j] = 0;
}
}*/
//输出二维代码
//for (int i = 0; i < large; i++)
//{
// for (int j = 0; j < N; j++)
// cout << aba[i][j];
// cout << endl;
//}
for (int i = 0; i < large; i++)
{
for (int j = 0; j < 20; j++)
{
if (aba[i][j] == 0)
break;
if(aba[i][j] == 1)
count++;
}
num[i] = count;
count = 0;
}
cout << endl;
for (int i = 0; i < large; i++)
cout << num[i]<<" ";
}
2.5 面条排序
如果桌子上有一把长短不一的面条,此时你将面条立起来,下端平放在桌面上,此时你用手掌在空中从上往下缓慢移动,慢慢的,你的手掌触碰到了一根面条(这根面条是最高的),你将这根面条抽走(抽走的面条当然想怎么吃就怎么吃),手继续慢慢向下移动,这时,你又碰到了倒数第二高的面条,你又将其抽走,。。。。
算法中,我们用一个数模拟手,每次-1,为了不至于手掌无处安放,我们将手直接放在最高的面条的顶端。
请你用代码实现这个算法吧。
最后一个没给啥东西,我还理解错题目了(丢人(lll¬ω¬)),我以为是挑出最大值,然后放到最后一个,这不就是选择排序么???
但其实这道题就是手每次向下挪一位,然后看看有木有面条~
十分短小的代码
#include<iostream>
#include<algorithm>//头文件
using namespace std;
int main()
{
int num[6] = { 6,5,4,3,2,1 };
int tmp[6] = { 0 };
int large = *max_element(num,num+6);//找最大值
for (int j = 5; j >= 0; j--)
{
for (int i = 0; i < 6; i++)
if (num[i] == large)//摸到面条
{
tmp[j] = num[i];
num[i] = 0;
}
large--;//手向下挪
}
for (int i = 0; i < 6; i++)
cout << tmp[i];
}