简述
路径计算又两种方法,一种通过迭代,另一种通过队列实现。
迭代方案
从起始点开始遍历,一个点周围又八个next_point,去除超出棋盘的点后,选择遍历的点满足,该点未被遍历或该点之前遍历路径所需的步数大于当前路径对于的步数。
队列方案
将起始点push到队列中。当队列不为空时,每次循环从队列中pop一个点。如果那八个next_point满足条件:1. 点不超出棋盘;2. 该点未被遍历或该点之前遍历路径所需的步数大于当前路径对于的步数。
实现
lib.rs
pub mod checkerboard;
pub mod checkerboardq;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn for_horse() {
let mut board = super::checkerboard::CheckerBoard::new();
board.calculate((2,3));
println!("{}",board.get_count());
assert_eq!(board.get(&(2,3)),0);
}
#[test]
fn for_horse_query() {
let mut board = super::checkerboardq::CheckerBoardQ::new();
board.calculate((2,3));
board.print_board();
println!("{}",board.get_count());
assert_eq!(board.get(&(2,3)),0);
}
}
checkerboard.rs
//迭代方案
#[derive(Debug)]
pub struct CheckerBoard {
board: Vec<Vec<isize>>,//存放路径对应步数
count:usize,//记录遍历次数
}
impl CheckerBoard {
// add code here
pub fn new() -> CheckerBoard {
let mut board:Vec<Vec<isize>> = Vec::with_capacity(10);
let count:usize=0;
for _ in 0..10 {
board.push(vec![-1;9]);
}
CheckerBoard{
board,
count,
}
}
pub fn get(&self,point:&(usize,usize)) -> isize{
self.board[point.0][point.1]
}
pub fn set(&mut self,point:&(usize,usize),length:isize){
self.board[point.0][point.1] = length;
}
pub fn print_board(&self){
for i in 0..10{
println!("{:?}",self.board[i]);
}
}
pub fn calculate(&mut self,point:(usize,usize)){
self.travel(point,-1);
}
fn travel(&mut self,point:(usize,usize),length:isize){
if point.0>9 || point.1>8{
return;
}
self.count = self.count + 1;
let _length = self.get(&point);
if _length>=0 && _length<=length+1{
return;
}
self.count = self.count+1;
let _length = length+1;
self.set(&point,length+1);
//以下代码这么丑是为了减少bool计算次数
if point.0>1{
if point.1>1{
self.travel((point.0-1,point.1-2),_length);
self.travel((point.0-1,point.1+2),_length);
self.travel((point.0-2,point.1-1),_length);
self.travel((point.0-2,point.1+1),_length);
self.travel((point.0+1,point.1-2),_length);
self.travel((point.0+2,point.1-1),_length);
}else if point.1>0 {
self.travel((point.0-1,point.1+2),_length);
self.travel((point.0-2,point.1-1),_length);
self.travel((point.0-2,point.1+1),_length);
self.travel((point.0+2,point.1-1),_length);
}
}else if point.0>0{
if point.1>1{
self.travel((point.0-1,point.1-2),_length);
self.travel((point.0-1,point.1+2),_length);
self.travel((point.0+1,point.1-2),_length);
self.travel((point.0+2,point.1-1),_length);
}else if point.1>0{
self.travel((point.0-1,point.1+2),_length);
self.travel((point.0+2,point.1-1),_length);
}
}
self.travel((point.0+1,point.1+2),_length);
self.travel((point.0+2,point.1+1),_length);
}
pub fn get_count(&self) -> usize{
self.count
}
}
checkerboardq.rs
#[derive(Debug)]
struct Queue<T> {
qdata: Vec<T>,
}
impl <T> Queue<T> {
pub fn new() -> Self {
Queue{qdata: Vec::new()}
}
pub fn len(&self)->usize{
self.qdata.len()
}
pub fn push(&mut self, item:T) {
self.qdata.push(item);
}
pub fn pop(&mut self) -> T{
self.qdata.remove(0)
}
}
#[derive(Debug)]
pub struct CheckerBoardQ {
board: Vec<Vec<isize>>,
count:usize,
point_query:Queue<(isize,isize)>,
}
impl CheckerBoardQ {
// add code here
pub fn new() -> CheckerBoardQ {
let mut board:Vec<Vec<isize>> = Vec::with_capacity(10);
let count:usize=0;
let point_query:Queue<(isize,isize)> = Queue::new();
for _ in 0..10 {
board.push(vec![-1;9]);
}
CheckerBoardQ{
board,
count,
point_query,
}
}
fn set(&mut self,point:&(isize,isize),length:isize){
self.board[point.0 as usize ][point.1 as usize] = length;
}
pub fn get(&self,point:&(usize,usize)) -> isize{
self.board[point.0][point.1]
}
fn _get(&self,point:&(isize,isize)) -> isize{
self.board[point.0 as usize][point.1 as usize]
}
pub fn print_board(&self){
for i in 0..10{
println!("{:?}",self.board[i]);
}
}
pub fn calculate(&mut self,point:(usize,usize)){
assert!(point.0<10 && point.1<9);
let i_point = (point.0 as isize,point.1 as isize);
self.set(&i_point,0);
self.point_query.push(i_point);
while self.point_query.len()>0 {
self.count = self.count + 1;
let _point = self.point_query.pop();
self.travel(_point);
}
}
fn update_query(&mut self,length:isize,next_point:(isize,isize)){
let length = length+1;
let _length = self._get(&next_point);
if _length<0 || _length>length {
self.set(&next_point,length);
self.point_query.push(next_point);
}
}
fn travel(&mut self,point:(isize,isize)) {
//以下代码看上去简洁许多,但实际bool计算次数较上面方案多
let mut _next_point_query = vec![
(point.0-1,point.1-2),(point.0-1,point.1+2),
(point.0+1,point.1-2),(point.0+1,point.1+2),
(point.0-2,point.1-1),(point.0-2,point.1+1),
(point.0+2,point.1-1),(point.0+2,point.1+1)].into_iter();
let length = self._get(&point);
while let Some(_point) = _next_point_query.next() {
if _point.0<0||_point.0>9||_point.1<0||_point.1>8{
continue;
}
self.update_query(length,_point);
}
}
pub fn get_count(&self) -> usize {
self.count
}
}
测试结果
cargo test -- --nocapture --test-threads=1
测试结果很明显,迭代方案性能远远低于队列方案。这一点其实非常好理解,虽然,两个方案的核心判断条件相同,但迭代方案是从一个点出发,将其满足核心判断条件的路径全部遍历一遍。而队列方案每次只前进一步,就可以提前结束一大部分肯定会被淘汰掉的遍历路径。
小结
上述代码中设计到的rust语法不多。比较有意思的是八个next_point是否在棋盘上的判断算法,要兼顾优雅和高效。我所用的两个算法基本算是这种情况的极端。第一种代码一大坨,但是遍历一个点的平均bool计算次数为3.7,第二种明显是每个next_point点都判断一次,为8 。