iis服务器助手广告广告
返回顶部
首页 > 资讯 > 数据库 >如何用Go语言生成一个排列
  • 696
分享到

如何用Go语言生成一个排列

2024-04-02 19:04:59 696人浏览 薄情痞子
摘要

如何用Go语言生成一个排列,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。算法目前,生成一个序列的

如何用Go语言生成一个排列,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。

算法

目前,生成一个序列的排列常用的有以下几种算法:

暴力法(Brute Force)插入法(Insert)字典法(Lexicographic)SJT算法(Steinhaus-Johnson-Trotter)堆算法(Heap)

下面依次介绍算法的内容,实现和优缺点。

在介绍这些算法之前,我们先做一些示例和代码上的约定:

我的代码实现是使用 Go 语言,且仅实现了求int切片的所有排列,其它类型自行扩展也不难。除非特殊说明,我假定输入的int中无重复元素,有重复元素可自行去重,其中有个别算法可处理重复元素的问题。

完整代码放在GitHub上。

暴力法

描述

暴力法是很直接的一种分治法:先生成 n-1 个元素的排列,加上第 n 个元素即可得到 n 个元素的排列。算法步骤如下:

将第 n 个元素依次交换到最后一个位置上递归生成前 n-1 个元素的排列加上最后一个元素即为 n 个元素的排列

实现

算法实现也很简单。这里引入两个辅助函数,拷贝和反转切片,后面代码都会用到:

    func copySlice(nums []int) []int {

    n := make([]int, len(nums), len(nums))

    copy(n, nums)

    return n

    }

    // 反转切片nums的[i, j]范围

    func reverseSlice(nums []int, i, j int) {

    for i < j {

    nums[i], nums[j] = nums[j], nums[i]

    i++

    j--

    }

    }

算法代码如下:

    func BruteForce(nums []int, n int, ans *[][]int) {

    if n == 1 {

    *ans = append(*ans, copySlice(nums))

    return

    }

    n := len(nums)

    for i := 0; i < n; i++ {

    nums[i], nums[n-1] = nums[n-1], nums[i]

    BruteForce(nums, n-1, ans)

    nums[i], nums[n-1] = nums[n-1], nums[i]

    }

    }

作为一个接口,需要做到尽可能简洁,第二个参数初始值就是前一个参数切片的长度。优化接口:

    func bruteForceHelper(nums []int, n int, ans *[][]int) {

    // 生成排列逻辑

    ...

    }

    func BruteForce(nums []int) [][]int{

    ans := make([][]int, 0, len(nums))

    bruteForceHelper(nums, len(nums), &ans)

    return ans

    }

优缺点

优点:逻辑简单直接,易于理解。

缺点:返回的排列数肯定是n!,性能的关键在于系数的大小。由于暴力法的每次循环都需要交换两个位置上的元素,递归结束后又需要再交换回来,在n较大的情况下,性能较差。

插入法

描述

插入法顾名思义就是将元素插入到一个序列中所有可能的位置生成新的序列。从 1 个元素开始。例如要生成{1,2,3}的排列:

先从序列 1 开始,插入元素 2,有两个位置可以插入,生成两个序列 12 和 21将 3 插入这两个序列的所有可能位置,生成最终的 6 个序列

    1

    12 21

    123 132 312 213 231 321

实现

实现如下:

    func insertHelper(nums []int, n int) [][]int {

    if n == 1 {

    return [][]int{[]int{nums[0]}}

    }

    var ans [][]int

    for _, subPermutation := range insertHelper(nums, n-1) {

    // 依次在位置0-n上插入

    for i := 0; i <= len(subPermutation); i++ {

    permutation := make([]int, n, n)

    copy(permutation[:i], subPermutation[:i])

    permutation[i] = nums[n-1]

    copy(permutation[i+1:], subPermutation[i:])

    ans = append(ans, permutation)

    }

    }

    return ans

    }

    func Insert(nums []int) [][]int {

    return insertHelper(nums, len(nums))

    }

优缺点

优点:同样是简单直接,易于理解。

缺点:由于算法中有不少的数据移动,性能与暴力法相比降低了16%

字典法

描述

该算法有个前提是序列必须是有升序排列的,当然也可以微调对其它序列使用。它通过修改当前序列得到下一个序列。我们为每个序列定义一个权重,类比序列组成的数字的大小,序列升序排列时“权重”最小,降序排列时“权重”最大。下面是 1234 的排列按**“权重”由小到大:

    1234

    1243

    1324

    1342

    1423

    1432

    2134

    ...

我们观察到一开始最高位都是 1,稍微调整一下后面三个元素的顺序就可以使得整个“权重”增加,类比整数。当后面三个元素已经逆序时,下一个序列最高位就必须是 2 了,因为仅调整后三个元素已经无法使“权重”增加了。算法的核心步骤为:

对于当前的序列,找到索引i满足其后的元素完全逆序。这时索引i处的元素需要变为后面元素中大于该元素的最小值。然后剩余元素升序排列,即为当前序列的下一个序列。

该算法用于 c++ 标准库中next_permutation算法的实现,见GNU C++ std::next_permutation。

实现

    func NextPermutation(nums []int) bool {

    if len(nums) <= 1 {

    return false

    }

    i := len(nums) - 1

    for i > 0 && nums[i-1] > nums[i] {

    i--

    }

    // 全都逆序了,达到最大值

    if i == 0 {

    reverse(nums, 0, len(nums)-1)

    return false

    }

    // 找到比索引i处元素大的元素

    j := len(nums) - 1

    for nums[j] <= nums[i-1] {

    j--

    }

    nums[i-1], nums[j] = nums[j], nums[i-1]

    // 将后面的元素反转

    reverse(nums, i, len(nums)-1)

    return true

    }

    func lexicographicHelper(nums []int) [][]int {

    ans := make([][]int, 0, len(nums))

    ans = append(ans, copySlice(nums))

    for NextPermutation(nums) {

    ans = append(ans, copySlice(nums))

    }

    return ans

    }

    func Lexicographic(nums []int) [][]int {

    return lexicographicHelper(nums)

    }

NextPermutation函数即可用于解决前文 LeetCode 算法题。其返回false表示已经到达最后一个序列了。

优缺点

优点:NextPermutation可以单独使用,性能也不错。

缺点:稍微有点难理解。

SJT算法

描述

SJT 算法在前一个排列的基础上通过仅交换相邻的两个元素来生成下一个排列。例如,按照下面顺序生成 123 的排列:

    123(交换23) ->

    132(交换13) ->

    312(交换12) ->

    321(交换32) ->

    231(交换31) ->

    213

一个简单的方案是通过 n-1 个元素的排列生成 n 个元素的排列。例如我们现在用 2 个元素的排列生成 3 个元素的排列。

2 个元素的排列只有 2 个:1 2 和 2 1。

通过在 2 个元素的排列中所有不同的位置插入 3,我们就能得到 3 个元素的排列。

在 1 2 的不同位置插入 3 得到:1 23,132 和31 2。在 2 1 的不同位置插入 3 得到:2 13,231 和32 1。

上面是插入法的逻辑,但是插入法由于有大量的数据移动导致性能较差。SJT 算法不要求生成所有 n-1 个元素的排列。它记录排列中每个元素的方向。算法步骤如下:

查找序列中可移动的最大元素。一个元素可移动意味着它的值大于它指向的相邻元素。交换该元素与它指向的相邻元素。修改所有值大于该元素的元素的方向。重复以上步骤直到没有可移动的元素。

假设我们需要生成序列 1 2 3 4 的所有排列。首先初始化所有元素的方向为从右到左。第一个排列即为初始序列:

    <1 <2 <3 <4

所有可移动的元素为 2,3 和 4。最大的为 4。我们交换 3 和 4。由于此时 4 是最大元素,不用改变方向。得到下一个排列:

    <1 <2 <4 <3

4 还是最大的可移动元素,交换 2 和 4,不用改变方向。得到下一个排列:

    <1 <4 <2 <3

4 还是最大的可移动元素,交换 1 和 4,不用改变方向。得到下一个排列:

    <4 <1 <2 <3

当前 4 已经无法移动了,3 成为最大的可移动元素,交换 2 和 3。注意,元素 4 比 3 大,所以要改变元素 4 的方向。得到下一个排列:

    >4 <1 <3 <2

这时,元素 4 又成为了最大的可移动元素,交换 4 和 1。注意,此时元素 4 方向已经变了。得到下一个排列:

    <1 >4 <3 <2

交换 4 和 3,得到下一个排列:

    <1 <3 >4 <2

交换 4 和 2:

    <1 <3 <2 >4

这时元素 3 为可移动的最大元素,交换 1 和 3,改变元素 4 的方向:

    <3 <1 <2 <4

继续这个过程,最后得到的排列为(强烈建议自己试试):

    <2 <1 >3 >4

已经没有可移动的元素了,算法结束。

实现

    func getLargestMovableIndex(nums []int, dir []bool) int {

    maxI := -1

    l := len(nums)

    for i, num := range nums {

    if dir[i] {

    if i > 0 && num > nums[i-1] {

    if maxI == -1 || num > nums[maxI] {

    maxI = i

    }

    }

    } else {

    if i < l-1 && num > nums[i+1] {

    if maxI == -1 || num > nums[maxI] {

    maxI = i

    }

    }

    }

    }

    return maxI

    }

    func sjtHelper(nums []int, ans *[][]int) {

    l := len(nums)

    // true 表示方向为从右向左

    // false 表示方向为从左向右

    dir := make([]bool, l, l)

    for i := range dir {

    dir[i] = true

    }

    maxI := getLargestMovableIndex(nums, dir)

    for maxI >= 0 {

    maxNum := nums[maxI]

    // 交换最大可移动元素与它指向的元素

    if dir[maxI] {

    nums[maxI], nums[maxI-1] = nums[maxI-1], nums[maxI]

    dir[maxI], dir[maxI-1] = dir[maxI-1], dir[maxI]

    } else {

    nums[maxI], nums[maxI+1] = nums[maxI+1], nums[maxI]

    dir[maxI], dir[maxI+1] = dir[maxI+1], dir[maxI]

    }

    *ans = append(*ans, copySlice(nums))

    // 改变所有大于当前移动元素的元素的方向

    for i, num := range nums {

    if num > maxNum {

    dir[i] = !dir[i]

    }

    }

    maxI = getLargestMovableIndex(nums, dir)

    }

    }

    func Sjt(nums []int) [][]int {

    ans := make([][]int, 0, len(nums))

    ans = append(ans, copySlice(nums))

    sjtHelper(nums, &ans)

    return ans

    }

优缺点

优点:作为一种算法思维可以学习借鉴。

缺点:性能不理想。

Heap算法

描述

Heap算法优雅、高效。它是从暴力法演化而来的,我们前面提到暴力法性能差主要是由于多次交换,堆算法就是通过减少交换提升效率。

算法步骤如下:

如果元素个数为奇数,交换第一个和最后一个元素。如果元素个数为偶数,依次交换第 i 个和最后一个元素。

Wikipedia上有详细的证明,有兴趣可以看看。

实现

    func heapHelper(nums []int, n int, ans *[][]int) {

    if n == 1 {

    *ans = append(*ans, copySlice(nums))

    return

    }

    for i := 0; i < n-1; i++ {

    heapHelper(nums, n-1, ans)

    if n&1 == 0 {

    // 如果是偶数,交换第i个与最后一个元素

    nums[i], nums[n-1] = nums[n-1], nums[i]

    } else {

    // 如果是奇数,交换第一个与最后一个元素

    nums[0], nums[n-1] = nums[n-1], nums[0]

    }

    }

    heapHelper(nums, n-1, ans)

    }

    // Heap 使用堆算法生成排列

    func Heap(nums []int) [][]int {

    ans := make([][]int, 0, len(nums))

    heapHelper(nums, len(nums), &ans)

    return ans

    }

Heap 算法非常难理解,而且很容易写错,我现在纯粹是背下来了

看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注编程网数据库频道,感谢您对编程网的支持。

您可能感兴趣的文档:

--结束END--

本文标题: 如何用Go语言生成一个排列

本文链接: https://www.lsjlt.com/news/67319.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 如何用Go语言生成一个排列
    如何用Go语言生成一个排列,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。算法目前,生成一个序列的...
    99+
    2024-04-02
  • c语言全排列数怎么生成
    生成C语言全排列数的一种常见方法是使用递归。以下是一个示例代码: #include // 交换两个元素的值 void swap(...
    99+
    2023-10-21
    c语言
  • go语言如何实现全排列
    今天小编给大家分享一下go语言如何实现全排列的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。思路:首先画出全排列的树形结构,以...
    99+
    2023-07-05
  • Go语言如何实现二维码生成?
    随着移动互联网的发展,二维码已经成为了一种非常普遍的扫码方式。在很多场景下,我们都可以看到二维码的身影。那么,在Go语言中如何实现二维码的生成呢?本文将会带大家一起探讨这个问题。 一、使用Go语言实现二维码的基本原理 在Go语言中,我们可...
    99+
    2023-06-04
    二维码 leetcode git
  • 如何用C语言写一个散列表
    本篇文章为大家展示了如何用C语言写一个散列表,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。一、快速理解散列表散列表,就是下标可以为字母的数组。假设现有一个数组int a[100],想查找其中第40个...
    99+
    2023-06-22
  • go语言中xorm如何自动生成model
    这篇文章主要介绍“go语言中xorm如何自动生成model”,在日常操作中,相信很多人在go语言中xorm如何自动生成model问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”go语言中xorm如何自动生成mo...
    99+
    2023-06-25
  • 如何使用Go语言写一个Http Server
    这篇文章主要介绍了如何使用Go语言写一个Http Server的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇如何使用Go语言写一个Http Server文章都会有所收获,下面我们一起来看看吧...
    99+
    2023-06-30
  • 如何在 Go 语言中实现一个二维码生成器,并将生成日志保存到文件?
    在本文中,我们将探讨如何使用 Go 语言实现一个二维码生成器,并将生成日志保存到文件。二维码是一种二维条码,它可以存储大量信息,并且可以在很小的空间内进行存储。在日常生活中,二维码可以用于各种场合,如支付、门票、商业广告等。 Go 语言提供...
    99+
    2023-07-26
    二维码 日志 打包
  • 使用R语言怎么生成一个随机数
    这篇文章给大家介绍使用R语言怎么生成一个随机数,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。1. 均匀分布函数: runif(n, min=0, max=1),n 表示生成的随机数数量,min 表示均匀分布的下限,ma...
    99+
    2023-06-13
  • 如何在Go语言中使用Windows API生成二维码?
    在Go语言中使用Windows API生成二维码可以帮助开发者在Windows平台下快速生成高质量的二维码。本文将介绍如何使用Go语言结合Windows API生成二维码。 一、安装必要的库 在使用Windows API生成二维码之前,我们...
    99+
    2023-09-14
    二维码 windows 函数
  • 如何使用Go语言生成高质量的二维码?
    随着智能手机和移动互联网的普及,二维码越来越成为企业和个人传播信息的一种重要方式。在二维码的制作过程中,Go语言提供了很多方便快捷的工具,本文将介绍如何使用Go语言生成高质量的二维码。 一、Go语言生成二维码的基本原理 二维码的生成是通过将...
    99+
    2023-11-15
    二维码 面试 load
  • 您知道如何使用Go语言生成二维码吗?
    Go语言(又称Golang)是由Google开发的一门静态类型的编程语言,它旨在提高程序员的工作效率。Go语言具有高效的编译速度、良好的并发性能、简单易学的语法等特点,因此被越来越多的开发者所青睐。在本篇文章中,我们将介绍如何使用Go语言生...
    99+
    2023-10-16
    spring linux 二维码
  • 如何使用go语言书写一个区块链
    如何使用go语言书写一个区块链?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。操作环境:windows10系统、GO 1.11.2、thinkpad t480电脑。在区块链公...
    99+
    2023-06-06
  • 如何使用Go语言实现实时生成JavaScript二维码?
    随着移动互联网的发展,二维码已经成为了一种非常方便的信息交互方式。在网页中,我们可以使用JavaScript来生成二维码。那么,如果我们想要在Go语言中实现实时生成JavaScript二维码,应该怎么做呢?本文将为您介绍如何使用Go语言实...
    99+
    2023-11-06
    实时 javascript 二维码
  • 使用Go语言开发一个高效的队列实现
    使用Golang编写高效的队列实现 引言:队列是一种常见的数据结构,可用于实现先进先出(FIFO)的操作。在编程中,队列的实现方式各有优劣,本文将介绍使用Golang编写高效的队列实现,并给出具体的代码示例。...
    99+
    2024-01-24
    高效 (效率)
  • GO语言中如何生成高质量的二维码?
    GO语言是一种快速、高效、可靠的编程语言,被广泛应用于网络和分布式系统开发。近年来,二维码已成为一种流行的信息传递方式,如何在GO语言中生成高质量的二维码成为了许多开发者关注的问题。 在GO语言中,生成二维码可以使用第三方库qrcode。该...
    99+
    2023-07-08
    二维码 并发 大数据
  • 如何使用 Go 语言生成二维码并记录日志?
    在现代的互联网时代,二维码的应用越来越广泛。在很多场景下,我们都需要使用二维码来传递信息,比如在商场、超市、餐厅等地方扫描二维码可以获取更多的信息,或者在一些活动中使用二维码来验证身份等等。因此,二维码的生成和使用也变得越来越重要。在本文...
    99+
    2023-07-26
    二维码 日志 打包
  • 如何在Windows上使用Go编程语言生成二维码?
    Go是一种相对新兴的编程语言,它的简洁和高效性已经吸引了越来越多的程序员。在Windows系统中,我们可以使用Go编程语言来生成二维码,这篇文章将会为大家详细介绍如何使用Go来实现这个过程。 一、安装Go语言环境 在开始之前,我们需要在Wi...
    99+
    2023-09-14
    二维码 windows 函数
  • go语言生成器code generator怎么使用
    这篇文章主要介绍“go语言生成器code generator怎么使用”,在日常操作中,相信很多人在go语言生成器code generator怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家...
    99+
    2023-06-30
  • 二维码:如何在Go语言中生成和解码?
    二维码是现代社会中常用的一种二维条码,它可以将大量的信息编码到一个小小的图像中,便于在移动设备上扫描和识别。在本文中,我们将学习如何使用Go语言生成和解码二维码。 生成二维码 在Go语言中,我们可以使用第三方库github.com/skip...
    99+
    2023-09-08
    numpy apache 二维码
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作