并发实现n皇后问题

前言

最近在用Go并发实现n皇后问题中遇到很多的坑,在此进行记录。

n后问题以及单线程实现

n后问题:简单来说就是在n*n的棋盘上放置n个皇后,使每两者之间都不在同一行,同一列,同一斜线上。求满足条件的解的个数?

// array[i] 代表第 i 行n皇后放置位置为第 arry[i] 列,index代表当前要放置皇后的行号
type Process struct {
    array []int
    index int
}

// 检查该行放置后会不会冲突
func check(process Process) bool {
    array := process.array
    k := process.index
    for i := 0; i < k; i++ {
        if array[i] == array[k] {
            return true
        }

        if int(math.Abs(float64(array[k]-array[i]))) == k-i {
            return true
        }
    }

    return false
}

func NQueen(process Process, n int) int {
    if process.index >= n {
        return 1
    }

    res := 0

    for j := 0; j < n; j++ {
        process.array = append(process.array, j)
        
        if check(process) == false {
            process.index++
            res += NQueen(process, n)
            process.index--
        }

        process.array = process.array[:len(process.array)-1]
    }
    return res
}

func Provide(n int) int {
    process := Process{
        array: make([]int, 0),
        index: 0,
    }
    return NQueen(process, n)
}

func main() {
    fmt.Println(Provide(12))
}

错误的并发代码

思路:创建协程去执行 NQueen 函数,并将结果放入 channel,最后累加得到最终结果。于是,代码可能是下面这个样子的:

// 其他函数不变
func NQueen(ch chan int, process Process, n int) {
    if process.index >= n {
        ch <- 1
        return
    }

    count := 0
    newch := make(chan int, n)
    for j := 0; j < n; j++ {
        process.array = append(process.array, j)

        if check(process) == false {
            process.index++
            count++
            go NQueen(newch, process, n)
            process.index--
        }

        process.array = process.array[:len(process.array)-1]
    }

    res := 0
    for i := 0; i < count; i++ {
        res += <-newch
    }
    ch <- res
}

func Provide(n int) int {
    process := Process{
        array: make([]int, 0),
        index: 0,
    }
    ch := make(chan int, 1)
    NQueen(ch, process, n)
    return <-ch
}

这样,你可能发现,答案并不是我们预期的那样。
分析:
(1)这里只传入同一个 process 实例,那么多个协程同时对他们操作,不加以控制的话,肯定会出现问题;所以我们给每个协程一份单独的 process 实例。
(2)第二个,我们着重来讲 process.array = append(process.array, j) 问题。

append函数机制

首先明确我们的需求:重新开辟内存空间,值为原来数组切片基础上,增加一个元素
append机制:
当切片还有可用容量,调用append,会将值直接加到切片中,这时原切片和现切片共用底层数组,所以地址相同。
而当容量不足,系统会重新开辟一块儿内存,所以原切片和现切片底层数组不同。

func main() {
    ch := make([]int, 5)
    ch = append(ch, 1, 2, 3, 4)
    fmt.Printf("%p\n", ch)
    aa := append(ch, 5)
    fmt.Printf("%p\n", aa)
    bb := append(ch, 5, 6)
    fmt.Printf("%p\n", bb)
}

所以,我们每次初始化新的切片,使系统分配不同的内存空间。

代码实现

type Process struct {
    array []int
    index int
}

func check(process Process) bool {
    array := process.array
    k := process.index - 1
    for i := 0; i < k; i++ {
        if array[i] == array[k] {
            return true
        }

        if int(math.Abs(float64(array[k]-array[i]))) == k-i {
            return true
        }
    }

    return false
}

func NQueen(ch chan int, process Process, n int) {
    if process.index >= n {
        ch <- 1
        return
    }

    count := 0
    newch := make(chan int, n)
    for j := 0; j < n; j++ {
        newProcess := Process{
            array: make([]int, 0),
            index: process.index + 1,
        }
        newProcess.array = append(newProcess.array, process.array...)
        newProcess.array = append(newProcess.array, j)
        if check(newProcess) == false {
            count++
            go NQueen(newch, newProcess, n)
        }

    }

    res := 0
    for i := 0; i < count; i++ {
        res += <-newch
    }
    ch <- res
}

func Provide(n int) int {
    process := Process{
        array: make([]int, 0),
        index: 0,
    }
    ch := make(chan int, 1)
    NQueen(ch, process, n)
    return <-ch
}

func main() {
    fmt.Println(Provide(8))
}

如何限流

何为限流,就是同一时刻创建恒定个协程。这里我们用channel来进行限流。

type Process struct {
    array []int
    index int
}

func check(process Process) bool {
    array := process.array
    k := process.index - 1
    for i := 0; i < k; i++ {
        if array[i] == array[k] {
            return true
        }

        if int(math.Abs(float64(array[k]-array[i]))) == k-i {
            return true
        }
    }

    return false
}

var channel = make(chan int, 5)

func NQueen(ch chan int, process Process, n int) {
    <-channel
    if process.index >= n {
        ch <- 1
        return
    }

    count := 0
    newch := make(chan int, n)
    for j := 0; j < n; j++ {
        newProcess := Process{
            array: make([]int, 0),
            index: process.index + 1,
        }
        newProcess.array = append(newProcess.array, process.array...)
        newProcess.array = append(newProcess.array, j)
        if check(newProcess) == false {
            channel <- 1
            count++
            go NQueen(newch, newProcess, n)
        }

    }

    res := 0
    for i := 0; i < count; i++ {
        res += <-newch
    }
    ch <- res
}

func Provide(n int) int {
    process := Process{
        array: make([]int, 0),
        index: 0,
    }
    ch := make(chan int, 1)
    channel <- 1
    NQueen(ch, process, n)
    return <-ch
}

func main() {
    fmt.Println(Provide(8))
}

你可能感兴趣的:(并发实现n皇后问题)