《啊哈!算法》第二章 - 第一节- 解密QQ号(Java实现)
解密QQ号——队列
新学期开始了,小哈是小哼的新同桌(小哈是个小美女哦~),小哼向小哈询问 QQ号, 小哈当然不会直接告诉小哼啦,原因嘛你懂的。所以小哈给了小哼一串加密过的数字,同时 小哈也告诉了小哼解密规则。 规则是这样的:首先将第 1个数删除,紧接着将第 2个数放到 这串数的末尾,再将第 3个数删除并将第 4个数放到这串数的末尾,再将第 5个数删除…… 直到剩下后一个数,将后一个数也删除。按照刚才删除的顺序,把这些删除的数连在一 起就是小哈的 QQ啦。 现在你来帮帮小哼吧。小哈给小哼加密过的一串数是“6 3 1 7 5 8 9 2 4”
可以发现解密QQ号的过程就像是给数字 “ 排队 ”,我们每次从数字的最前面拿走两个数字,第 1 个扔掉,第 2 个放到数字的尾部。具体过程是这样的:
刚开始这串数是 “ 6 3 1 7 5 8 9 2 4 ”
第 1 次:删除 6 并将 3 放到数字末尾,这串数更新为 “ 1 7 5 8 9 2 4 3 ”;
第 2 次:删除 1 并将 7 放到数字末尾,即更新为 “ 5 8 9 2 4 3 7 ”;
第 3 次:删除 5 并将 8 放到数字末尾,即更新为 “ 9 2 4 3 7 8 ”;
第 4 次:删除 9 并将 2 放到数字末尾,即更新为 “ 4 3 7 8 2 ”;
第 5 次:删除 4 并将 3 放到数字末尾,即更新为 “ 7 8 2 3 ”;
第 6 次:删除 7 并将 8 放到数字末尾,即更新为 “ 2 3 8 ”;
第 7 次:删除 2 并将 3 放到数字末尾,即更新为 “ 8 3 ”;
第 8 次:删除 8 并将 3 放到数字末尾,即更新为 “ 3 ”;
第 9 次:删除 3。
因此被删除的数字的顺序是 “ 6 1 5 9 4 7 2 8 3 ”,这就是小哈的 QQ号码了
解密的规则和过程搞清楚了,现在我们就用代码来实现一下吧!
题目给了我们一串数字,因此我们需要用数组来存储这些数字,定义一个长度为100的数组,并初始化数组,即:
System.out.println("请输入需要解密的QQ号的数字个数:");
int n = sc1.nextInt();
Scanner sc1 = new Scanner(System.in);
System.out.println("请输入需要解密的QQ号:");
Scanner sc1 = new Scanner(System.in);
int a[] = new int[100];
for(int i = 0;i < n; i++){ // 初始化数组
a[i] = sc2.nextInt();
}
之后可以开始模拟解密了,解密的第一步是需要删除数字,数组里删除数字最简单的方法是将所有后面的数都往前面挪动一位,将前面的数覆盖。就好比我们在排队买票,前面的人买好离开了,后面所有的人就需要全部向前面走一步,补上之前的空位,但是这样的做法很耗费时间。
不过这给了我们一个很好的思路——排队,我们可以把这串数字看做一个队列,然后定义两个整型变量 head 和 tail ,分别指向队列的队首(即第一位)和队尾(即最后一位)的下一个位置。
你可能会问:为什么 tail 不直接记录队尾,却要记录队尾的下一个位置呢?
这是因为当队列中只剩下一个元素时,队首和队尾重合会带来一些麻烦。我们这里规定队首和队尾重合时,队列为空。
我在这里简单的补充一下队列的概念:
队列是一种特殊的线性结构,它只允许在队列的首部(head)进行删除操作,这称为 “ 出队”;而在队列的尾部(tail)进行插入操作,这称为 “入队”。
当队列中没有元素时(即 head==tail),称为空队列。
在我们的日常生活中有很多情况都符合队列的特性。比如我们之前提到过的买票,每个排队买票的窗口就是一个队列。在这个队列当中,新来的人总是站在队列的后面,来得越早的人越靠前,也就越早能买到票,就是先来的人先服务。
我们称之为 “ 先进先出 ”(First In First Out,简称:FIFO)原则。
补充完毕!继续前行!
小哈给小哼的加密的QQ号有 9 个数,我们将这9 个数全部放入队列之后,
head = 0;
tail = 9; (9 个数放入数组中,数组的下标是0~8,因此 tail = 8+1 = 9)
此时 head 和 tail 之间的数就是目前队列中 “ 有效 ” 的数。如果要删除一个数的话,就将 head++ 就 OK 了,这样仍然可以保持 head 和 tail 之间的数为目前队列中 “ 有效 ” 的数。这样做虽然浪费了一个空间,却节省了大量的时间,这是非常划算的。新增加一个数也很简单,把需要增加的数放到队尾即 q[tail] 之后再 tail++ 就 OK 啦!
(注意:用空间换时间也是算法的一个思想)
小总结:
队首删除一个数的操作:head++
队尾增加一个数(假设这个数是 x)的操作: q[tail] = x;
tail++;
整个解密过程如下图:
完整代码如下:
import java.util.Scanner;
public class T1 {
public static void main(String[] args) {
// QQ号解密
// 输入要解密的QQ号的数字个数及要解密的QQ号
System.out.print("请输入要解密的QQ号的数字个数:");
Scanner sc1 = new Scanner(System.in);
int n = sc1.nextInt();
System.out.println("请输入要解密的QQ号:");
// 因为我们选择的删除数据的方式是直接head++往后走,
// 同时tail++继续向后创建新的空间,因此我们需要的数组的空间要比QQ号的个数多
// 故我们创建数组q时多给一点空间
// 这样做虽然浪费了空间,但是节省了大量的时间,也是算法的一种思想“用空间换时间”
int q[] = new int[101];
Scanner sc2 = new Scanner(System.in);
for(int i = 0; i < n; i++) { // 循环读入QQ号
q[i] = sc2.nextInt();
}
// 定义两个整型变量作为头指针和尾指针标记数组下标
int head = 0; // 头指针,数组下标=0
// 尾指针,指向队尾的下一个位置,在这里等于QQ号的数字个数,
// 因为数组的下标是从0开始的,所以第9个数字的下标为8,
// 因此队尾的下一个位置的下标为9,即QQ号的数字个数
int tail = n;
// 不知道需要循环多少次或者循环的次数不能立刻算出来,就使用while循环
// 当删除最后一个数字的时候,头指针和尾指针指向的是同一个数字,即可结束循环
while(head < tail) {
System.out.print(q[head]+" "); // 打印队首元素
head++; // 将队首元素出队,即头指针指向下一个元素,
// 根据题目可知,队首元素的下一个元素需要放到队尾,
// 即将队首元素出队后,head指向的元素需要放到队尾,
// 故将元素值赋给尾指针指向的空间
q[tail] = q[head];
// 赋值完毕后,尾指针向后走
tail++;
// 头指针也向后走
head++;
// 此时完成了第一个解密过程
// 即将第一个数删除,第二个数放到数字的末尾
// 这时head指向的是第三个数,在下一次循环的开始被删除
// 之后开启下一轮解密过程,直至循环结束,完成所有的解密过程
}
}
}
运行结果如下: