算法与数据结构(基于Go语言)学习笔记01

算法初识:汉诺塔问题(Towers of Hanoi)

规则简介

​ 给定三根柱子,记为 A A A B B B C C C,其中 A A A 柱子上有 n − 1 n-1 n1 个盘子,从上至下盘子越来越大。问:将 A A A柱上的盘子经由 B B B柱,移动到 C C C柱最少需要多少次?每次应该怎么搬运?

​移动时应注意:①一次只能移动一个盘子;②大的盘子不能压在小盘子上。
汉诺塔图示

方法概述:递归法

宏观来看,我们首先需要将最大的底盘和其他盘分离开来,这样才有机会将底盘先放到 C C C柱上。如果最大底盘和其他盘已经分开,其他盘肯定不能堆在 C C C柱,否则我们不可能将最大底盘放到 C C C柱底部。因此其他盘一定是堆砌在 B B B柱的。
我们顺利将最大的底盘移动到了目标位置,而现在剩下的盘子都在 B B B处。让我们忘掉最大的底盘,现在的任务是将 n − 1 n-1 n1个盘子从 B B B移动到 C C C
如何思考这个问题呢?注意多个盘子的绝对位置没有意义,如果要移动多个盘子,直接移动是不行的,一定要有中介
因此,将 n − 1 n-1 n1个盘子从 B B B移动到 C C C的情形和将 n − 1 n-1 n1个盘子从 A A A移动到 C C C是完全类似的!
下面让我们假设一个函数:

func hanoi(n int, a, b, c string) {...
}

函数头为hanoi(n, a, b, c),意思为将 n n n个盘子从a经由b移动到c(注意多个盘子不能直接从a移动到c,所以必须有中介)。
综上,我们可以将 n n n个盘子的汉诺塔任务分解为以下三步:

  1. n − 1 n-1 n1个盘子从 A A A经由 C C C移动到 B B B
  2. 将最大底盘从 A A A直接移动到 C C C
  3. n − 1 n-1 n1个盘子从 B B B经由 A A A移动到 C C C.
    从中我们明显看出了递归的影子。注意递归方法的重点在于将关于 n n n的问题拆解成若干关于 n − 1 n-1 n1的问题,从而用后者来清晰有效地表达出前者的内含结构。除此之外,递归必须包含终止条件。在这个问题中,终止条件就是 n ≤ 0 n\leq0 n0

代码实现

func hanoi(n int, a, b, c string) {if n > 0 {hanoi(n - 1, a, c, b)fmt.Printf("Moving from %s to %s", a, c)hanoi(n - 1, b, a, c)}
}

搜索问题

搜索问题在生活和编程中都很常见,即给定一组数据和一个目标数据,需要找到目标数据对应的索引并返回。

暴力实现:线性查找(linear search)

func linear_search(arr []int, elem int) int {for k, v := range arr {if elem == v {return k}}return -1
}

当找不到目标数据时,我们返回-1。这种方法的复杂度显然是 O ( n ) O(n) O(n)级别的。

二分查找(binary search)

对于有序数组,我们还可以使用二分法进行查找。

func binary_search(arr []int, elem int) int {//只能用于有序列表left, right := 0, len(arr)-1for left <= right {mid := left + (right-left)/2if arr[mid] == elem {return mid} else if elem < arr[mid] {left = mid + 1} else {right = mid - 1}}return -1
}

例如在军训时需要按照身高从大到小(从左到右)进行排队,而我想在其中找到一个高为175cm的学生,我可以从队伍的中间先进行比对。如果队伍中间学生的身高高于175cm,说明我想找的学生更靠近队伍末尾,因此我可以将包括中间学生在内的、站在中间学生左边的学生都排除掉,从剩下的学生中进行查找。
注意每次我们都能筛选掉一半的人,因此算法复杂度是 O ( l o g 2 n ) O(log_2 n) O(log2n)的。

排序问题

冒泡排序(bubble sort)

问题分析

排序问题更是经典中的经典,最为直接而暴力的方法就是大名鼎鼎的冒泡排序。
考虑长度为 n n n的数组arr,我们想把数组从大到小进行排列。假设索引为 i − 1 i-1 i1之前的元素已经排列好了(从大到小),我们接下来对索引为 i i i的元素进行排列。

  1. 从arr[i+1]遍历到arr[n-1],即从当前位置遍历到数组末尾;
  2. 只要如果遍历到的元素大于当前arr[i],则交换二者。
    注意此时arr[i]的位置可以说是人来人往,只要arr[i]后面有比自己大的数据,就会被换到arr[i]的位置上。除此之外arr[i]的位置还可以说是风水轮流转,可能某个数据比arr[i]的原始数据大,某个时刻被换到了arr[i]上,但如果后面还有更大的数据,那arr[i]的位子就得腾出来给别人了。
    从上述叙述中我们看出,冒泡排序的内核其实是把从当前位置(即索引为i处)到数组末尾的最大值挑出来放到索引为i处,于是我们的数组就变成了{最大值,次大值,第三最大值,…}

代码实现

func bubbleSort(a []int) []int {for i := 0; i < len(a); i++ {for j := i + 1; j < len(a); j++ {if a[i] < a[j] {a[i], a[j] = a[j], a[i]}}}return a
}

选择排序(selection sort)

问题分析

从冒泡排序的本质我们可以提出另一种算法——选择排序法。
从数组arr中挑选出最大值,将其放入另一个数组res的第一位,随后从arr中删除这一元素。反复重复前述步骤,直到数组arr为空。

代码实现

我们首先使用循环语句实现插入排序:

func selectionSort(a []int, opt SortOption) []int {res := make([]int, len(a))for i := 0; i < len(res); i++ {k0, v0 := 0, 0for k, v := range a {if v > v0 {k0, v0 = k, v}}slices.Delete(a, k0, k0+1)res[i] = v0}return res
}

此外我们可以挑战使用递归法实现这一逻辑:

func selectionSortRecursion(a []int) []int {if len(a) == 1 {return a} else {return append([]int{slices.Max(a)},selectionSortRecursion(slices.Delete(a,slices.Index(a, slices.Max(a)),slices.Index(a, slices.Max(a)) + 1,), opt,)...,)}
}

插入排序

接下来我们介绍插入排序,这种排序在发牌时很常见。

问题分析

把数组arr当成我们的手牌,假设索引为i之前的牌已经按从大到小的顺序排列好,我们现在从索引为i的牌开始比较。和谁比较呢?和索引为i之前的牌开始比较,我们希望将arr[i]恰好插入到前一张牌比arr[i]大、后一张牌比arr[i]小的位置。
为此,我们从索引为j=i-1处开始比较。比较的方式如下:

  1. 如果arr[j]比arr[i]原始值小,我们将arr[j]复制到arr[j+1]处,同时j自动减一
  2. 如果arr[j]比arr[i]原始值大,由于前面的牌已经是从大到小的顺序,因此这个位置一定是我们想要的插入arr[i]原始值的位置。

代码实现

func insertionSort(a []int, opt SortOption) []int {for i := 1; i < len(a); i++ {tmp := a[i]j := i - 1for j >= 0 && a[j] < tmp {a[j+1] = a[j]j--}a[j+1] = tmp}return a
}

快速排序(quick sort)

问题分析

上述算法复杂度均为 O ( n 2 ) O(n^2) O(n2),接下来我们介绍一种更快的排序方法——快速排序:
对于函数quick_sort(arr, left, right)

  1. 取arr的第一个元素,使其归位(partition),即让第一个元素提前到达最终它应该在的位置。
  2. 将arr分成两个部分:比第一个元素大的部分和比第一个元素小的部分;
  3. 递归地解决这两个部分。
    代码逻辑大体如下:
func quickSort(arr []int, left, right int) []int {if left < right {mid = partition(arr, left, right)quickSort(arr, mid + 1 , right)quickSort(arr, left, mid - 1)}return arr
}

其中left是我们考察区间的最左侧位置,right同理。那么归位函数partition(arr, left, right)应该如何实现呢?
首先把arr的第一个元素单独拿出来作为head,此时left指向了一个空位!因此我们移动right,直到找到一个比head小的元素arr[right],将其复制到arr[left]处。接下来开始移动left,直到找到一个比head大的元素arr[left],将其复制到arr[right]处。
注意在上述过程中,我们再次用到了“空位”和“复制”的概念,而空位就是通过逐对复制元素实现的,这和插入排序不谋而合。

代码实现

func partition(a *[]int, left, right int, opt SortOption) int {head := (*a)[left]for left < right {for left < right && *a)[right] <= head {// 找出比head小的数right-- //右指针向左移动一位}(*a)[left] = (*a)[right] // 把右边的值放到左边空位for left < right && *a)[left] >= head {// 找出比head大的数left++ //左指针向右移动一位}(*a)[right] = (*a)[left] // 把右边的值放到左边空位}(*a)[left] = head // 把head归位return left
}func quickSort(a *[]int, left, right int) []int {if left < right {mid := partition(a, left, right)quickSort(a, left, mid-1)quickSort(a, mid+1, right)}return *a
}

今日Leetcode

问题描述

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

代码实现

暴力实现

思路太简单:

func twoSum(nums []int, target int) []int {for k1, v1 := range nums {for k2, v2 := range nums {if k1 != k2 && v1 + v2 == target {return []int{k1, k2}}}}return []int{}
}

哈希表实现

上述方式耗时太久,主要原因在于寻找target-num花了太长时间(或者说出现了太多重复查找),因此我们可以引入哈希表来缩短查找时间。

func twoSum(nums []int, target int) []int {helpMap := make(map[int]int) // value -> indexfor i, num := range nums {if j, ok := helpMap[target - num]; ok {return []int{i, j}}helpMap[num] = i}return []int{}
}

参考资料

清华大学博士讲解Python数据结构与算法
用二进制来解汉诺塔问题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/625780.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Leetcode 15. 三数之和(暴力->双指针)

给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元组。 示例 1…

解决EasyPoi导入Excel获取不到第一列的问题

文章目录 1. 复现错误2. 分析错误2.1 导入的代码2.2 DictExcel实体类2.2 表头和标题3. 解决问题1. 复现错误 使用EasyPoi导入数据时,Excel表格如下图: 但在导入时,出现如下错误: name为英文名称,在第一列,Excel表格有值,但导入的代码中为null,就很奇怪? 2. 分析错误 …

机器学习中的激活函数

激活函数存在的意义&#xff1a; 激活函数决定了某个神经元是否被激活&#xff0c;当这个神经元接收到的信息是有用或无用的时候&#xff0c;激活函数决定了对这个神经元接收到的信息是留下还是抛弃。如果不加激活函数&#xff0c;神经元仅仅做线性变换&#xff0c;那么该神经网…

基于Ultrascale+系列GTY收发器8b/10b编码方式的数据传输(三)——利用In System IBERT优化信号质量

基于Ultrascale系列GTY收发器8b/10b编码方式的数据传输&#xff08;二&#xff09;——数据收发及上板测试 一文介绍了利用GTY高速收发器进行8B/10B编码数据收发的使用方式&#xff0c;本文继续介绍使用In System IBERT IP核构建ibert眼图测试&#xff0c;以及通过设置参数以优…

ArcGIS三维景观分层显示

今天将向大家介绍的事在ArcGIS中如何创建多层三维显示。 地表为影像的 地表为地形晕渲的 在土壤分层、油气分层等都有着十分重要的应用。下面我们具体来看看实现过程 一、 准备数据及提取栅格范围 我们这次准备的数据是之前GIS100例-30讲的案例数据。《ArcGIS三维影像图剖面图…

C语言 函数——代码风格

目录 基本的代码规范 程序版式 对齐&#xff08;Alignment&#xff09;与缩进&#xff08;indent&#xff09; 变量的对齐规则 空行——分隔程序段落的作用 代码行内的空格——增强单行清晰度 代码行 长行拆分 标识符命名规则 标识符命名的共性规则 windows应用程序…

发人深省:如果前端开发是青春饭,为何你的青春这么匆匆。

有很多人说前端开发是青春饭&#xff0c;干不了多久&#xff0c;很容易被取代&#xff0c;这就是危言耸听了。只要前端岗位在一天&#xff0c;这就不是青春饭&#xff1b;即便被取代&#xff0c;资深前端很快就会在别的地方找到自己立身之本&#xff0c; 反观哪些天天抱怨&…

【Excel2LaTeX】复杂表格制作的解决方案

刚开始用LaTeX写论文&#xff0c;遇到的第一道坎就是绘制表格&#xff0c;较小的普通表格可以通过简单的语法实现&#xff0c;但是较大的复杂的表格却让我无从下手。 Excel2LaTeX插件 这里介绍一种我用到非常顺手的工具&#xff1a;Excel2LaTeX插件&#xff0c;下载地址&#x…

C语言:文件操作(四)

目录 前言 6、文本文件和二进制文件 7、文件读取结束的判定 7.1 被错误使用的feof 8、文件缓冲区 总结 前言 接上篇&#xff0c;本篇要讲解的是文本文件和二进制文件的内容、文件读取结束的判定以及文件缓冲区。 6、文本文件和二进制文件 根据数据的组织形式&#xff0c;数…

嵌入式工程师如何摸鱼?

有老铁问我&#xff0c;做嵌入式开发要加班吗&#xff1f; 也不知道搞什么鬼&#xff0c;现在的年轻人对加班这么抵触。 我刚做开发那会&#xff0c;啥也不懂&#xff0c;每天基本都要加班到晚上7-9点不等&#xff0c;我并不抵触加班&#xff0c;因为早早回家&#xff0c;也没什…

HarmonyOS开发实战:【亲子拼图游戏】

概述 本篇Codelab是基于TS扩展的声明式开发范式编程语言编写的一个分布式益智拼图游戏&#xff0c;可以两台设备同时开启一局拼图游戏&#xff0c;每次点击九宫格内的图片&#xff0c;都会同步更新两台设备的图片位置。效果图如下&#xff1a; 说明&#xff1a; 本示例涉及使…

搜狗多线程长尾词挖掘软件-【批量挖掘搜狗大家还在搜和相关搜索长尾词】

搜狗多线程长尾词挖掘软件-【批量挖掘搜狗大家还在搜和相关搜索长尾词】介绍&#xff1a; 1、软件根据放入多个关键词批量多线程去搜狗搜索里拓搜狗大家还在搜和相关搜索。 2、搜狗大家还在搜和相关搜索长尾词质量好。 3、一个关键词可以拓19个高质量相关长尾词。 4、软件自…