问题如下:
小明同学把1到n这n个数字按照一定的顺序放入了一个队列Q中。现在他对队列Q执行了如下程序:
while(!Q.empty()) //队列不空,执行循环
{
int x=Q.front(); //取出当前队头的值x
Q.pop(); //弹出当前队头
Q.push(x); //把x放入队尾
x = Q.front(); //取出这时候队头的值
printf("%d\n",x); //输出x
Q.pop(); //弹出这时候的队头
}
做取出队头的值操作的时候,并不弹出当前队头。 小明同学发现,这段程序恰好按顺序输出了1,2,3,…,n。现在小明想让你构造出原始的队列,你能做到吗?
输入描述:
第一行一个整数T(T ≤ 100)表示数据组数,每组数据输入一个数n(1 ≤ n ≤ 100000),输入的所有n之和不超过200000。
输出描述:
对于每组数据,输出一行,表示原始的队列。数字之间用一个空格隔开,不要在行末输出多余的空格.
输入例子:
4
1
2
3
10
输出例子:
1
2 1
2 1 3
8 1 6 2 10 3 7 4 9 5
根据问题可以得出原来算法的java程序:
static int[] outResult(Queue<Integer> queue){
int[] array = new int[queue.size()];
int i = 0;
while (!queue.isEmpty()) {
int head = queue.poll();
queue.add(head);
array[i++] = queue.poll();
}
return array;
}
定义数组下标0,对应队列的头,依次类推。可以用一些数据来测试以上java程序。
测试用例:
//源数组{4,1,6,2,5,3,7} 进入队列queue
int[] testArray = new int[]{4,1,6,2,5,3,7};
Queue<Integer> queue = new LinkedList<Integer>();
for (int i = 0; i < testArray.length; i++) {
queue.add(testArray[i]);
}
//source -> result
System.out.print("source:"+queue);
//经过问题所述算法演算
int[] a = Qtest.outResult(queue);
System.out.println("->result:"+Arrays.toString(a));
//输出为source:[4, 1, 6, 2, 5, 3, 7]->result:[1, 2, 3, 4, 5, 6, 7]
以上是由源数据到结果数据的顺向过程,我们的目标是要得出其逆向过程。我们可以先举些例子来逐步推导。我们不难得出,当队列queue大小为1的时候,结果数据为{queue[0]},源数据也为{queue[0]};当queue大小为2的时候,结果数据为{queue[0],queue[1]},源数据为{queue[1],queue[0]}。从源数据到 结果数据,即执行outResult方法,我们可以发现奇数位的数据是首先输出的,然后偶数位的数据一个一个地放到后面再组成一个新的队列,按照此规律输出。到这里,我们可以意识到是一个循环过过程,可以用递归实现,而递归的边界就是size为1和2的时候,队列的操作就只有两种:1.把数据放到队列尾部;2.把数据输出;而且这两个操作是间隔实现的,也就是说按照先1后2的顺序。这时候,我们可以发现,只要把结果数据从中间分成均匀的两部分,后一部分就往前一部分间隔的插入。以下为队列大小size>2的时候,结果数据到数据的推导实例。
//结果数据->源数据
//size = 1
1->1
//size = 2
1 2->2 1
//size = 3
1 2 3->1
2 3->2
3
-> 3 2->2 1 3
//size = 4
1 2 3 4-> 1 2
3 4->3
4
-> 4 3->4 1 3 2
//size = 5
1 2 3 4 5->1 2
3 4 5->3
4 5->4
5
-> 5 4->4 3 5-> 3 1 5 2 4
//size = 6
1 2 3 4 5 6->1 2 3
4 5 6->4
5 6->5
6
->6 5->5 4 6->5 1 4 2 6 3
//size = 7
1 2 3 4 5 6 7->1 2 3
4 5 6 7->4 5
6 7->6
7
->7 6->7 4 6 5-> 4 1 6 2 5 3 7
解释一下上面的推导过程:
//size = 4
1 2 3 4(结果数据)-> 1 2(将数组分成(1 2)size/2和(3 4)size-size/2前后两部分)
3 4->3(继续对半分割)
4(直到数组为大小为1)
-> 4 3(结果数据3 4的源数据)->4 1 3 2(源数据)
最后总结得出逆向算法:
static int[] outSource(final int[] array){
final int size = array.length;
int[] resultArray = new int[size];
final int rest = size % 2;
if(size == 1){
return array;
}else if(size == 2){
return new int[]{array[1],array[0]};
}else if(size > 2){
for (int i = 0; i < size / 2; i++) {
resultArray[2*i+1] = array[i];
}
int[] part = Arrays.copyOfRange(array, size / 2, size);
int[] postArray = outSource(part);
if(rest == 0){
for (int i = 0; i < postArray.length; i++) {
resultArray[2*i] = postArray[i];
}
}else{
for (int i = 0; i < postArray.length-1; i++) {
resultArray[2*i] = postArray[i+1];
}
resultArray[size-1] = postArray[0];
}
return resultArray;
}else{
return null;
}
}
根据上面的算法,还得出一个规律,就是当数组大小为偶数的时候,将后半部分的数组一个一个地插入前半部分数组的每个元素的前页面。
1 2 3 4 5 6
1 2 3
| | | -> 5 1 4 2 6 3
5 4 6
如果数组大小为奇数的时候,就要先把后半部分的数组的第一个元素放到尾部,然后再执行上面的操作。
1 2 3 4 5->1 2
3 4 5->3
4 5->4
5
-> 5 4->4 3 5-> 3 1 5 2 4
3 5 4(4移到尾部)
| | -> 3 1 5 2 4
1 2
完整的测试用例:
int[] testArray = new int[]{4,1,3,2};
Queue<Integer> queue = new LinkedList<Integer>();
for (int i = 0; i < testArray.length; i++) {
queue.add(testArray[i]);
}
//source -> result
System.out.print("source:"+queue);
int[] a = Qtest.outResult(queue);
System.out.println("->result:"+Arrays.toString(a));
//result -> source
System.out.print("result:"+Arrays.toString(a));
int[] b = outSource(a);
System.out.println("->source:"+Arrays.toString(b));