第一次练习的题目:
三人行设计了一个灌水论坛。信息学院的学生都喜欢在上面交流灌水,传说在论坛上有一个“水王”,他不但喜欢发帖,还会回复其他ID发的每个帖子。坊间风闻该“水王”发帖数目超过了帖子数目的一半。如果你有一张当前论坛的帖子(包括回帖)列表,其中帖子的作者的ID也在其中,你能快速的找到这个传说中的水王吗?
第二次练习的题目是基于第一次的练习题目:
随着论坛的发展,管理员发现水王没有了,但是统计结果表明,有三个发帖很多的ID。据统计他们的发帖数量超过了1/4,你能从发帖列表中快速找到他们吗?
程序设计思想:
第二个程序练习的思想同样是基于第一次的基础,简单而言,第一个程序要解决的问题无非就是将一个列表中的ID号通过一次循环分成两类,第一类就是“水王”的ID号,在列表中占据一半以上,第二类就是其他用户的ID,通过两两比较删掉不同的ID号以减少总数量的方式来找到“水王”,因为通过删减,无论“水王”的ID是否在这两个删掉的ID号中,最终剩下的列表中的“水王”的ID仍然占据一半以上。这里所讲的删减并没有真正的去改变ID列表中的内容,而是利用一个变量来记录“水王”出现的次数,通过增加和减少这个变量的值来虚拟的实现ID号的删减。
第二次的程序设计同样基于这样的思想,不过与第一次相比最大的不同在于,要在列表中查找到三个不同的ID号,并假设这三个ID号就是“小水王”,当某一个ID号与这三个ID号都不想同时才能对这四个ID号进行同时的删减。
程序的概要设计如下:
我的程序中首先假设列表中有十个用户ID,用户的ID号分别用0-9十个数字字符来代替,利用随机函数,生成N条记录,即将用户的ID号保存在一个长度为N的一维数组中。程序的起始同样用随机数生成了三个自定义的“小水王”,利用while循环保证了这三个ID号是不同的,在后续的程序中通过不重复随机替换的方式,确保了一维数组中的自定义的三个“小水王”ID号都在四分之一以上。查找小水王的核心程序从列表的第一个ID号开始查找,查找到三个不同的ID号后,假设初始的这三个ID号分别为三个“小水王”,对于其后的每一个ID进行循环比较,根据记录“小水王”出现次数的变量的值是否为零可以得到八种情况(即:000、001、010、011、100、101、110、111),这八种情况对应了八个不同的分支以及八种不同的操作。当变量的值都不为零时,即第八种情况,利用找水王01的思想,当遇到一个ID与这三个ID号都不同时,则将其都删掉。程序的详细设计见程序源代码中的注释。
程序源代码:
//现有一张灌水论坛的帖子列表,有三个“小水王”发帖的数目都超过了帖子数目的四分之一,找出这三个“小水王”的ID
//05-27,2016,Hailin Song
#include<iostream>
#include<stdlib.h>
#include<time.h>
using namespace std;
#define N 100
int main()
{
srand((int)time(NULL)); //定义时间种子
int XiaoShuiWang1, XiaoShuiWang2, XiaoShuiWang3;
XiaoShuiWang1 = 0 + rand() % 10; //假设帖子列表中有10个ID号,分别用0-9这十个数字字符代替,自定义第一个“小水王”的ID
XiaoShuiWang2 = 0 + rand() % 10; //假设帖子列表中有10个ID号,分别用0-9这十个数字字符代替,自定义第二个“小水王”的ID
while (XiaoShuiWang1 == XiaoShuiWang2) //确保第一个“小水王”和第二个“小水王”的ID号不同
{
XiaoShuiWang2 = 0 + rand() % 10;
}
XiaoShuiWang3 = 0 + rand() % 10; //假设帖子列表中有10个ID号,分别用0-9这十个数字字符代替,自定义第三个“小水王”的ID
while ((XiaoShuiWang1 == XiaoShuiWang3) || (XiaoShuiWang2 == XiaoShuiWang3)) //确保第三个“小水王”的ID和第一个、第二个“小水王”的ID都不同
{
XiaoShuiWang3 = 0 + rand() % 10;
}
cout << "自定义的三个“小水王”的ID分别为:";
cout << XiaoShuiWang1 << " " << XiaoShuiWang2 << " " << XiaoShuiWang3 << endl; //输出自定义的三个“小水王”的ID,同时测试“找小水王”程序的结果是否正确
cout << endl;
int Tiezi[N]; //假设帖子列表中有N条记录
int numberlocation = (N / 4 + 1) * 3; //记录需要替换的位置
int location[(N / 4 + 1) * 3];
for (int i = 0; i < N; i++) //随机生成帖子列表
{
Tiezi[i] = 0 + rand() % 10;
}
for (int i = 0; i < numberlocation; i++) //循环执行((N / 4 + 1) * 3)次,确保帖子列表中三个“小水王”的ID都超过总数N的四分之一
{
location[i] = 0 + rand() % N; //确定帖子列表中随机替换的位置
for (int j = 0; j < i; j++)
{
if (location[i] == location[j]) //确保随机替换的位置不会发生重复
{
location[i] = 0 + rand() % N;
j = -1;
}
}
}
for (int i = 0; i < numberlocation / 3; i++)
{
Tiezi[location[i]] = XiaoShuiWang1; //用第一个“小水王”的ID替换随机产生的用户的ID
}
for (int i = numberlocation / 3; i < numberlocation / 3 * 2; i++)
{
Tiezi[location[i]] = XiaoShuiWang2; //用第二个“小水王”的ID替换随机产生的用户的ID
}
for (int i = numberlocation / 3 * 2; i < numberlocation; i++)
{
Tiezi[location[i]] = XiaoShuiWang3; //用第三个“小水王”的ID替换随机产生的用户的ID
}
cout << "帖子列表的ID如下所示:" << endl;
for (int i = 0; i < N; i++)
{
cout << Tiezi[i] << " ";
if (i % 10 == 9)
{
cout << endl;
}
}
cout << endl;
//找“小水王”的核心程序,其基本思想是将帖子列表分为四类,即小水王一、小水王二、小水王三和其他ID
int temp; //记录i的位置
int SelectShuiWang1, SelectShuiWang2, SelectShuiWang3; //这三个变量用于保存程序中找出的“小水王”的ID
int shuiwangcount1 = 0, shuiwangcount2 = 0, shuiwangcount3 = 0; //这三个变量用于记录“小水王”出现的次数,也用做判断条件
SelectShuiWang1 = Tiezi[0]; //假定帖子列表中的第一个元素即使第一个“小水王”
shuiwangcount1 = 1;
for (int i = 1; i < N; i++) //从第二个元素开始查找,直到找到与第一个ID不同的ID号为止,把这个ID号假定为第二个“小水王”的ID
{
while (SelectShuiWang1 == Tiezi[i])
{
shuiwangcount1++;
i = i + 1;
}
temp = i;
SelectShuiWang2 = Tiezi[temp];
shuiwangcount2 = 1;
i = N;
}
for (int i = temp + 1; i < N; i++) //从假定的第二个“小水王”的后一个ID号开始,直到找到与第一个、第二个ID号都不同的ID号为止,把这个ID号定义为第三个“小水王”的ID,从而确保程序假定了三个不同的ID
{
if (SelectShuiWang1 == Tiezi[i])
{
shuiwangcount1++;
}
else
{
if (SelectShuiWang2 == Tiezi[i])
{
shuiwangcount2++;
}
else
{
temp = i;
SelectShuiWang3 = Tiezi[i];
shuiwangcount3 = 1;
i = N;
}
}
}
for (int i = temp + 1; i < N; i++) //从假定的第三个“小水王”的后一个ID号开始,一直循环到结束,每一个分支分别对应一种情况,共八个大的分支
{
if ((0 == shuiwangcount1) && (0 == shuiwangcount2) && (0 == shuiwangcount3)) //三个记录次数的变量都为零的情况,即表明前面假定的三个“小水王”并不是真正的“小水王”,因此,程序需要再次假定新的“小水王”
{
SelectShuiWang1 = Tiezi[i];
shuiwangcount1++;
}
else if ((0 == shuiwangcount1) && (0 == shuiwangcount2) && (0 != shuiwangcount3)) //若第三个小水王的次数不为零,则再次假定另外的“小水王”时,需要先判断后一个ID号是否和第三个“小水王”的ID号相同,以下分支都是这同一样的原理
{
if (SelectShuiWang3 == Tiezi[i])
{
shuiwangcount3++;
}
else
{
SelectShuiWang1 = Tiezi[i];
shuiwangcount1++;
}
}
else if ((0 == shuiwangcount1) && (0 != shuiwangcount2) && (0 == shuiwangcount3))
{
if (SelectShuiWang2 == Tiezi[i])
{
shuiwangcount2++;
}
else
{
SelectShuiWang1 = Tiezi[i];
shuiwangcount1++;
}
}
else if ((0 == shuiwangcount1) && (0 != shuiwangcount2) && (0 != shuiwangcount3))
{
if (SelectShuiWang2 == Tiezi[i])
{
shuiwangcount2++;
}
else if (SelectShuiWang3 == Tiezi[i])
{
shuiwangcount3++;
}
else
{
SelectShuiWang1 = Tiezi[i];
shuiwangcount1++;
}
}
else if ((0 != shuiwangcount1) && (0 == shuiwangcount2) && (0 == shuiwangcount3))
{
if (SelectShuiWang1 == Tiezi[i])
{
shuiwangcount1++;
}
else
{
SelectShuiWang2 = Tiezi[i];
shuiwangcount2++;
}
}
else if ((0 != shuiwangcount1) && (0 == shuiwangcount2) && (0 != shuiwangcount3))
{
if (SelectShuiWang1 == Tiezi[i])
{
shuiwangcount1++;
}
else if (SelectShuiWang3 == Tiezi[i])
{
shuiwangcount3++;
}
else
{
SelectShuiWang2 = Tiezi[i];
shuiwangcount2++;
}
}
else if ((0 != shuiwangcount1) && (0 != shuiwangcount2) && (0 == shuiwangcount3))
{
if (SelectShuiWang1 == Tiezi[i])
{
shuiwangcount1++;
}
else if (SelectShuiWang2 == Tiezi[i])
{
shuiwangcount2++;
}
else
{
SelectShuiWang3 = Tiezi[i];
shuiwangcount3++;
}
}
else //当记录“小水王”次数的三个变量都不为零时,证明其出现的次数多,当遇到某一个ID号都不同于这三个ID号时,则将其数量全部减一,即虚拟性的删去这四个不同的ID,但剩余的ID号列表中三个“小水王”的ID仍然占四分之一以上,从而利用了找水王1的程序原理
{
if (SelectShuiWang1 == Tiezi[i])
{
shuiwangcount1++;
}
else if (SelectShuiWang2 == Tiezi[i])
{
shuiwangcount2++;
}
else if (SelectShuiWang3 == Tiezi[i])
{
shuiwangcount3++;
}
else
{
shuiwangcount1--;
shuiwangcount2--;
shuiwangcount3--;
}
}
}
cout << "友情提示:只要程序找出的ID号与自定义的ID号相同即结果正确,无先后顺序!" << endl;
cout << "程序找出的“小水王”的ID为:";
cout << SelectShuiWang1 << " " << SelectShuiWang2 << " " << SelectShuiWang3 << endl; //输出程序找出的“小水王”ID,与自定义的“小水王”ID作比较,判断程序的逻辑是否正确,“小水王”的ID没有先后顺序
cout << endl;
return 0;
}
程序运行结果如下所示:

个人总结如下:
虽然找水王02是从找水王01的基础上进行的,思想也是来源于找水王01,但是这次的程序却比上次复杂的,因为三个“小水王”需要考虑的因素就要比第一个程序考虑的内容多,因此,为了写好这个程序花费了我将近六个小时的时间。在程序编写的过程中有两个比较困惑的点,第一个点就是如何通过循环从列表的初始位置找到三个不同的暂定假设的“小水王”的ID,本来想通过比较优化的方法写出,但是湿了好几种办法,觉得没有什么更优化的算法,就用最笨的循环控制写了出来,第二个点就是如何保证三个“小水王”的删减是正确的,当小水王的记录次数删减为零时,需要再把下一个元素假设为“小水王”,但是这时需要加以判断,即该元素是否和当下正在假设的“小水王”的ID相同,经过多次的尝试和多次的考虑,最终总结出了(000、001、010、011、100、101、110、111)这八种情况。经过多次的测试,程序暂时没有出现逻辑上的错误。
写完这个程序以后,首先认识到了if、else if、else语法的重要作用,这种语法适合于多种分支的情况,而且每次循环都执行一个分支,可以将程序的逻辑处理功能清晰的分成多个不同的分支,便于理解。另一个感受就是写这样的程序挺锻炼逻辑思维能力的,因为如果程序的逻辑执行不清楚的,那就不可能正确的划分出分支,更不能很好的写出判断条件以及程序该执行的操作。