算法编程题-排序

算法编程题-排序

    • 比较型排序算法
      • 冒泡排序
      • 选择排序
      • 插入排序
      • 希尔排序
      • 堆排序
      • 快速排序
      • 归并排序
    • 非比较型排序算法
      • 计数排序
      • 基数排序

本文将对七中经典比较型排序算法进行介绍,并且给出golang语言的实现,还包括基数排序、计数排序等非比较型的算法的介绍和实现。

比较型排序算法

所谓的比较型排序算法就是算法中会使用数据之间的比较,只能数组保存的是能相关比较大小的数据即可使用该类算法,相比于非比较型排序算法适用面更广。
在实现上进行了一定的封装,支持泛型和自定义排序规则,默认传入一个比较函数less,按照less指定的小于关系进行“从小到大”排序。抽象接口代码如下:

type Sorter[T any] struct {less func(item1, item2 T) bool
}func NewSorter[T any](less func(item1, item2 T) bool) *Sorter[T] {return &Sorter[T]{less: less}
}

冒泡排序

冒泡排序如其名,就是排序的过程和冒泡有点像。一个气泡从水底浮向水面,其体积是越来越大的。那么同理,在排序的过程中,也可以让一个个大的元素浮上去,从而完成整个的排序。代码实现如下:

// BubbleSort 冒泡排序,时间复杂度:O(n^2) 空间复杂度:O(1), 稳定
func (s *Sorter[T]) BubbleSort(arr []T) {n := len(arr)for i := 0; i < n; i++ {var bubbleFlag bool // 是否有冒泡for j := i; j+1 < n; j++ {if s.less(arr[j+1], arr[j]) {arr[j], arr[j+1] = arr[j+1], arr[j]bubbleFlag = true}}if !bubbleFlag { // 无冒泡,说明已经排序完成,提前退出break}}
}

冒泡排序时间复杂度是 O ( n 2 ) O(n^2) O(n2),空间复杂度为 O ( 1 ) O(1) O(1)。其是一种稳定的排序算法。在实现上可以记录一个标志,表明此一轮有没有发生冒泡,如果没有,说明数组已经完全有序,可以以前退出。

选择排序

选择排序同样也是人如其名,在每一轮中,选择剩余数组中的最小值,放入一个位置,经过n轮,完成排序。实现代码如下:

// SelectSort 选择排序,时间复杂度:(n ^ 2), 空间复杂度:O(1),不稳定
func (s *Sorter[T]) SelectSort(arr []T) {n := len(arr)for i := 0; i < n; i++ {minPtr := ifor j := i + 1; j < n; j++ {if s.less(arr[j], arr[minPtr]) {minPtr = j}}arr[minPtr], arr[i] = arr[i], arr[minPtr]}
}

选择排序时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度为 O ( 1 ) O(1) O(1),是一种不稳定的排序算法。

插入排序

插入排序的排序过程为:维护一个有序序列,然后将待排序的数字找到一个合适的位置,然后插入进去,这样还是一个有序序列,依次将所有元素完成这一个操作,即可完成数组的排序。实现代码如下:

// InsertSort 插入排序,时间复杂度:O(n^2) 空间复杂度:O(1), 稳定
func (s *Sorter[T]) InsertSort(arr []T) {n := len(arr)for i := 1; i < n; i++ {for j := i; j > 0; j-- {if s.less(arr[j], arr[j-1]) {arr[j], arr[j-1] = arr[j-1], arr[j]} else {break}}}
}

插入排序的时间复杂度平均为 O ( n 2 ) O(n^2) O(n2),但是对于一个基本有序的数组进行排序,时间复杂度可以达到 O ( n ) O(n) O(n),空间复杂度为 O ( 1 ) O(1) O(1),是一种稳定的排序算法。
优化插入排序,可以从两个方面入手,减少找位置的时间或者移动元素的时间,每一轮这两个的时间都是 O ( n ) O(n) O(n)级别的,前者可以通过二分查找来优化,后者可以通过链表来优化,但是如何同时优化呢?笔者认为可以使用跳表,这样可以将插入排序的平均时间复杂度优化到 O ( n l o g n ) O(nlogn) O(nlogn),但是缺点在于实现复杂,且空间开销较大,相比于快速排序堆排序等,有点得不偿失。

希尔排序

希尔排序其实就是对于插入排序的优化,前文提到,插入排序在基本有序的数组排序效率比较高,那么可以考虑对于数组的子序列进行排序,具体的思路就是将子数组分为多个子序列,在每一轮中只对子序列内部进行插入排序。子序列分组的规则是每隔gap个的元素作为一个子序列,gap值在轮数下不断减小,直到为一后相当于是对全部数据进行插入排序,由于此时数组已经基本有序,所以最后一轮插入排序的效率很高。

// 希尔排序,时间复杂度: O(n^(3 / 2)) 空间复杂度:O(1), 不稳定
func (s *Sorter[T]) ShellSort(arr []T) {n := len(arr)gap := n >> 1for gap > 0 {for i := 0; i < gap; i++ {for j := i; j < n; j += gap {for k := i + gap; k < n; k += gap {if s.less(arr[k], arr[k-gap]) {arr[k], arr[k-gap] = arr[k-gap], arr[k]}}}}gap >>= 1}
}

希尔排序的时间复杂度为 O ( n 3 / 2 ) O(n ^ {3 / 2}) O(n3/2),但是最坏情况下也可能恶化成 O ( n 2 ) O(n^2) O(n2),空间复杂度为 O ( 1 ) O(1) O(1),是一种不稳定的排序算法,适用于中等规模的数据集排序。

堆排序

堆排序就是基于堆这种数据结构的性质,往往使用一个最小堆,将堆顶的元素取出,然后将最后一个元素放到堆顶后,从上到下进行调整,重复这一个过程,就能完成数组的排序。

type Heap[T any] struct {less   func(item1, item2 T) boolarray  []Tlength int // 标识实际长度
}// NewHeap 构建一个空的堆
func NewHeap[T any](less func(item1, item2 T) bool) *Heap[T] {return &Heap[T]{less: less, array: make([]T, 0), length: 0}
}// NewHeapWithArr 根据数组构建一个堆
func NewHeapWithArr[T any](less func(item1, item2 T) bool, arr []T) *Heap[T] {h := &Heap[T]{less: less, array: arr, length: len(arr)}for i := 1; i < len(arr); i++ {h.siftUp(i)}return h
}// Push 往堆h中插入一个元素
func (h *Heap[T]) Push(v T) {h.pushBack(v)h.siftUp(h.length - 1)
}// Top 返回堆顶的元素
func (h *Heap[T]) Top() T {return h.array[0]
}// Pop 移除堆顶的元素并返回
func (h *Heap[T]) Pop() T {ret := h.array[0]h.array[0] = h.array[h.length-1]h.length--h.siftDown(0)return ret
}func (h *Heap[T]) ToArr() []T {return h.array
}func (h *Heap[T]) IsEmpty() bool {return h.length == 0
}// pushBack 在尾部插入一个元素
func (h *Heap[T]) pushBack(v T) {if len(h.array) == h.length {h.array = append(h.array, v)} else {h.array[h.length] = v}h.length++
}// siftUp 从pos位置处开始往上调整
func (h *Heap[T]) siftUp(pos int) {if pos < 0 || pos >= h.length {return}i := posj := (pos - 1) / 2for j >= 0 && h.less(h.array[i], h.array[j]) {h.array[i], h.array[j] = h.array[j], h.array[i]i = jj = (i - 1) / 2}
}// siftDown 从pos位置处开始往下调整
func (h *Heap[T]) siftDown(pos int) {if pos < 0 || pos >= len(h.array) {return}i := 0j1 := 2*i + 1j2 := 2*i + 2for j1 < h.length {if h.less(h.array[j1], h.array[i]) && ((j2 >= h.length) || h.less(h.array[j1], h.array[j2])) {h.array[i], h.array[j1] = h.array[j1], h.array[i]i = j1} else if h.less(h.array[j2], h.array[i]) {h.array[i], h.array[j2] = h.array[j2], h.array[i]i = j2} else {break}j1 = 2*i + 1j2 = 2*i + 2}
}// HeapSort 堆排序,时间复杂度:O(nlogn), 空间复杂度:O(1), 不稳定
func (s *Sorter[T]) HeapSort(arr []T) {h := NewHeapWithArr(s.less, arr)k := len(arr) - 1for !h.IsEmpty() {arr[k] = h.Pop()k--}for i := 0; i < len(arr) / 2; i++ {arr[i], arr[len(arr) - 1 - i] = arr[len(arr) - 1 - i], arr[i]}
}

堆排序的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),空间复杂度为 O ( n ) O(n) O(n),是一种不稳定的排序算法,适用于大数据量下且内存资源相对宝贵的条件下。

快速排序

快速排序是一种基于分制的思路,对于一个数组,从中选择一个基准点,以这个基准点,小于的数交换到左边取,大于的数交换到右边去,然后递归地对左边和右边的子数组进行同样的处理,从而完成整个排序过程。

// 快速排序实现,时间复杂度:O(nlogn), 空间复杂度:O(1), 不稳定
func (s *Sorter[T]) QuickSort(arr []T) {s.quickSortV1(arr, 0, len(arr)-1)
}// 递归形式的排序
func (s *Sorter[T]) quickSortV1(arr []T, left, right int) {if left >= right { // 递归终点return}base := arr[left]basePtr := lefti := left + 1for i <= right {if s.less(arr[i], base) {basePtr++if basePtr != i {arr[basePtr], arr[i] = arr[i], arr[basePtr]}}i++}arr[left] = arr[basePtr]arr[basePtr] = bases.quickSortV1(arr, left, basePtr-1)s.quickSortV1(arr, basePtr+1, right)
}

以上实现版本的快速排序时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn),空间复杂度为 O ( 1 ) O(1) O(1),是一种不稳定的排序算法,但是最坏情况下时间复杂度也会恶化到 O ( n 2 ) O(n^2) O(n2)。比如上面的实现在数组基本有序的情况下,会化身为时间刺客,如下图。

在这里插入图片描述
一种常见的优化手段是从数组中取三个数字,然后以这三个数字的中位数作为基准点,如下为相关代码实现:

// quickSortV3优化实现,基准取头尾中三数中的平均数
func (s *Sorter[T]) quickSortV3(arr []T, left, right int) {if left >= right { // 递归终点return}s.adjustBase(arr, left, right)base := arr[left]basePtr := lefti := left + 1for i <= right {if s.less(arr[i], base) {basePtr++if basePtr != i {arr[basePtr], arr[i] = arr[i], arr[basePtr]}}i++}arr[left] = arr[basePtr]arr[basePtr] = bases.quickSortV3(arr, left, basePtr-1)s.quickSortV3(arr, basePtr+1, right)
}// adjustBase 调整基准值,将头尾中三数中的平均数放在头部作为基准
func (s *Sorter[T]) adjustBase(arr []T, left, right int) {basePtrs := []int{left, right, (left + right) / 2}s1 := NewSorter(func(i, j int) bool {return s.less(arr[i], arr[j])})s1.InsertSort(basePtrs)arr[left], arr[basePtrs[1]] = arr[basePtrs[1]], arr[left]
}

但是以上方法对于一个全是相同数字的数组还是会恶化成 O ( n 2 ) O(n^2) O(n2)
在某些面试中,可能有些面试官要求实现一些递归算法的非递归版本,比如实现非递归版本的快速排序。递归在编程语言中的实质就是借助栈来实现的,所以这种题目本质上就是在模拟递归,代码实现如下:

// 非递归实现,借助栈来模拟递归
func (s *Sorter[T]) quickSortV2(arr []T, left, right int) {stack := NewStack[[2]int]()stack.Push([2]int{left, right})for !stack.IsEmpty() {state := stack.Pop()left, right := state[0], state[1]if left >= right {continue}base := arr[left]basePtr := lefti := left + 1for i <= right {if s.less(arr[i], base) {basePtr++if basePtr != i {arr[basePtr], arr[i] = arr[i], arr[basePtr]}}i++}arr[left] = arr[basePtr]arr[basePtr] = basestack.Push([2]int{left, basePtr - 1})stack.Push([2]int{basePtr + 1, right})}
}

实际上,也可以不使用栈,使用队列或者其他的数据结构都是可以的。

归并排序

归并排序的重点在于归并上,对于两个已经有序的数组而言,可以合并到一个有序的数组中,时间复杂度为 O ( n ) O(n) O(n),这样,最小的段即一个数的数组,必然是有序的,小段合成大段,最后合并完成整个数组的排序。

// MergeSort 归并排序,时间复杂度:O(nlogn), 空间复杂度:O(n), 稳定
func (s *Sorter[T]) MergeSort(arr []T) []T {return s.mergeSortV1(arr)
}// 递归形式的归并排序
func (s *Sorter[T]) mergeSortV1(arr []T) []T {if len(arr) <= 1 { // 递归终点return arr}// 递归过程leftArr := s.mergeSortV1(append([]T(nil), arr[:len(arr)/2]...))rightArr := s.mergeSortV1(append([]T(nil), arr[len(arr)/2:]...))// 合并i := 0j := 0k := 0for i < len(leftArr) || j < len(rightArr) {if i >= len(leftArr) || (j < len(rightArr) && s.less(rightArr[j], leftArr[i])) {arr[k] = rightArr[j]j++k++} else {arr[k] = leftArr[i]i++k++}}return arr
}

归并排序也可以实现非递归的形式,如下:

// 非递归形式的归并排序
func (s *Sorter[T]) mergeSortV2(arr []T) []T {segLen := 1 // 比较的段长for segLen < len(arr) {for p := 0; p < len(arr); p += 2 * segLen {leftArr := append([]T(nil), arr[p:min(p+segLen, len(arr))]...)rightArr := append([]T(nil), arr[min(p+segLen, len(arr)):min(p+2*segLen, len(arr))]...)// 合并i := 0j := 0k := 0for i < len(leftArr) || j < len(rightArr) {if i >= len(leftArr) || (j < len(rightArr) && s.less(rightArr[j], leftArr[i])) {arr[p+k] = rightArr[j]j++k++} else {arr[p+k] = leftArr[i]i++k++}}}segLen *= 2}return arr
}

归并排序时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),空间复杂度为 O ( n ) O(n) O(n),是一种稳定的排序算法。归并排序适用于大数据量且要求稳定的场景,毕竟同时间复杂度的排序算法快速排序和堆排序都是不稳定的。

非比较型排序算法

可以注意到以上的排序算法都需要通过比较元素的大小来交换位置或者其他的操作,这类算法统称为比较型排序算法。另外一类非比较型排序算法,不需要比较大小,但是其适用面比较小。

计数排序

所谓的计数排序就是维护一个数组,用来表示每一个元素出现的次数,然后遍历数组,将各个数字取出即可。

type IntSorter struct {
}func NewIntSorter() *IntSorter {return &IntSorter{}
}// 计数排序,时间复杂度:O(n+k) k为数据范围 空间复杂度:O(k) 稳定
// 适用于数据范围不大的情况下
func (s *IntSorter) CountSort(arr []int64) {minv := arr[0]maxv := arr[0]for _, item := range arr {minv = min(minv, item)maxv = max(maxv, item)}counts := make([]int64, maxv-minv+1)for _, item := range arr {counts[item-minv]++}k := 0for i := int64(0); i < maxv-minv+1; i++ {for j := int64(0); j < counts[i]; j++ {arr[k] = i + minvk++}}
}

计数排序的时间复杂度为 O ( n ) O(n) O(n)级别,空间复杂度为 O ( m ) O(m) O(m),其中m为数组中最大数减去最小数,可以看到,计数排序只适用于整数,且最好数据比较集中。

基数排序

以整数的基数排序来说明这一个过程。由于整数的每一位只有十种可能,从0到9,那么可以建立十个桶,遍历数组,按照最低位对应的数字放到对应的桶中,然后再将所有数组按照0号桶收集起来,然后从数字的低第二位做重复的操作,直到最大数字的最高位。代码实现如下:

func (s *IntSorter) RadixSort(arr []int) {var newBuckets func() []*List[int] = func() []*List[int] {buckets := make([]*List[int], 10)for i := 0; i < 10; i++ {buckets[i] = NewList[int]()}return buckets}buckets := newBuckets()oldBuckets := newBuckets()for _, num := range arr {oldBuckets[num%10].AppendNodeTail(NewListNode[int](num/10, num))}flag := true // 停止标志for flag {flag = falsefor i := 0; i < 10; i++ {for node := oldBuckets[i].Begin(); node != oldBuckets[i].End(); node = node.Next {if node.Key != 0 {flag = true}buckets[node.Key%10].AppendNodeTail(NewListNode[int](node.Key/10, node.Val))}}oldBuckets = bucketsbuckets = newBuckets()}// 此时所有数据都在零号桶里k := 0for node := oldBuckets[0].Begin(); node != oldBuckets[0].End(); node = node.Next {arr[k] = node.Valk++}
}

基数排序的时间复杂度也大致在 O ( n m ) O(nm) O(nm)左右,对于整数可以认为是 O ( n ) O(n) O(n),空间复杂度为 O ( n ) O(n) O(n),适用于整数或者字符串等。

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

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

相关文章

【软考】系统架构设计师-信息系统基础

#信息系统基础核心知识点 信息系统5个基本功能&#xff1a;输入、存储、处理、输出和控制 诺兰模型&#xff1a;信息系统计划的阶段模型&#xff0c;6阶段 初始阶段&#xff0c;传播阶段&#xff0c;控制阶段&#xff0c;集成阶段&#xff0c;数据管理阶段&#xff0c;成熟阶…

【架构】主流企业架构Zachman、ToGAF、FEA、DoDAF介绍

文章目录 前言一、Zachman架构二、ToGAF架构三、FEA架构四、DoDAF 前言 企业架构&#xff08;Enterprise Architecture&#xff0c;EA&#xff09;是指企业在信息技术和业务流程方面的整体设计和规划。 最近接触到“企业架构”这个概念&#xff0c;转念一想必定和我们软件架构…

使用低成本的蓝牙HID硬件模拟鼠标和键盘来实现自动化脚本

做过自动化脚本的都知道&#xff0c;现在很多传统的自动化脚本方案几乎都可以被检测&#xff0c;比如基于root&#xff0c;adb等方案。用外置的带有鼠标和键盘功能集的蓝牙HID硬件来直接点击和滑动是非常靠谱的方案&#xff0c;也是未来的趋势所在。 一、使用蓝牙HID硬件的优势…

数据结构-二叉树_堆

目录 1.二叉树的概念 ​编辑1.1树的概念与结构 1.2树的相关语 1.3 树的表示 2. ⼆叉树 2.1 概念与结构 2.2 特殊的⼆叉树 2.2.2 完全⼆叉树 2.3 ⼆叉树存储结构 2.3.1 顺序结构 2.3.2 链式结构 3. 实现顺序结构⼆叉树 3.2 堆的实现 3.2.2 向下调整算法 1.二叉树的概…

【FPGA开发】AXI-Full总线接口介绍、FPGA搭建仿真平台

文章目录 协议解读接口介绍AW—写地址通道W—写数据通道B—写响应通道AR—读地址通道R—读数据通道 FPGA搭建仿真平台 本文主要介绍AXI-FULL的相关基础内容&#xff0c;AXI-Lite请移步&#xff1a; 【FPGA开发】AXI-Lite总线协议解读、Verilog逻辑开发与仿真、Alex Forencich代…

【已解决】“EndNote could not connect to the online sync service”问题的解决

本人不止一次在使用EndNote软件时遇到过“EndNote could not connect to the online sync service”这个问题。 过去遇到这个问题都是用这个方法来解决&#xff1a; 这个方法虽然能解决&#xff0c;但工程量太大&#xff0c;每次做完得歇半天身体才能缓过来。 后来再遇到该问…

Python深度学习环境配置(Pytorch、CUDA、cuDNN),包括Anaconda搭配Pycharm的环境搭建以及基础使用教程(保姆级教程,适合小白、深度学习零基础入门)

全流程导览 一、前言二、基本介绍2.1全过程软件基本介绍2.1.1 Pytorch2.1.2 Anaconda2.1.3 Pycharm2.1.4 显卡GPU及其相关概念2.1.5 CUDA和cuDNN 2.2 各部分相互间的联系和安装逻辑关系 三、Anaconda安装3.1安装Anaconda3.2配置环境变量3.3检验是否安装成功 四、Pycharm安装五、…

Java-05 深入浅出 MyBatis - 配置深入 动态 SQL 参数、循环、片段

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 大数据篇正在更新&#xff01;https://blog.csdn.net/w776341482/category_12713819.html 目前已经更新到了&#xff1a; MyBatis&#xff…

python成长技能之正则表达式

文章目录 一、认识正则表达式二、使用正则表达式匹配单一字符三、正则表达式之重复出现数量匹配四、使用正则表达式匹配字符集五、正则表达式之边界匹配六、正则表达式之组七、正则表达式之贪婪与非贪婪 一、认识正则表达式 什么是正则表达式 正则表达式&#xff08;英语&…

OpenCV与AI深度学习|16个含源码和数据集的计算机视觉实战项目(建议收藏!)

本文来源公众号“OpenCV与AI深度学习”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;分享&#xff5c;16个含源码和数据集的计算机视觉实战项目 本文将分享16个含源码和数据集的计算机视觉实战项目。具体包括&#xff1a; 1. 人…

Kafka 工作流程解析:从 Broker 工作原理、节点的服役、退役、副本的生成到数据存储与读写优化

Kafka&#xff1a;分布式消息系统的核心原理与安装部署-CSDN博客 自定义 Kafka 脚本 kf-use.sh 的解析与功能与应用示例-CSDN博客 Kafka 生产者全面解析&#xff1a;从基础原理到高级实践-CSDN博客 Kafka 生产者优化与数据处理经验-CSDN博客 Kafka 工作流程解析&#xff1a…

HarmonyOs鸿蒙开发实战(17)=>沉浸式效果第二种方案一组件安全区方案

1.沉浸式效果的目的 开发应用沉浸式效果主要指通过调整状态栏、应用界面和导航条的显示效果来减少状态栏导航条等系统界面的突兀感&#xff0c;从而使用户获得最佳的UI体验。 2.组件安全区方案介绍 应用在默认情况下窗口背景绘制范围是全屏&#xff0c;但UI元素被限制在安全区内…

五天SpringCloud计划——DAY1之mybatis-plus的使用

一、引言 咱也不知道为啥SpringCloud课程会先教mybatis-plus的使用&#xff0c;但是教都教了&#xff0c;就学了吧&#xff0c;学完之后觉得mybatis-plus中的一些方法还是很好用了&#xff0c;本文作为我学习mybatis-plus的总结提升&#xff0c;希望大家看完之后也可以熟悉myba…

Matlab 答题卡方案

在现代教育事业的飞速发展中&#xff0c;考试已经成为现代教育事业中最公平的方式方法&#xff0c;而且也是衡量教与学的唯一方法。通过考试成绩的好与坏&#xff0c;老师和家长可以分析出学生掌握的知识多少和学习情况。从而老师可以了解到自己教学中的不足来改进教学的方式方…

丹摩|丹摩助力selenium实现大麦网抢票

丹摩&#xff5c;丹摩助力selenium实现大麦网抢票 声明&#xff1a;非广告&#xff0c;为用户体验 1.引言 在人工智能飞速发展的今天&#xff0c;丹摩智算平台&#xff08;DAMODEL&#xff09;以其卓越的AI算力服务脱颖而出&#xff0c;为开发者提供了一个简化AI开发流程的强…

【生成数据集EXCEL文件】使用生成对抗网络GAN生成数据集:输出生成数据集EXCEL

本文采用MATLAB编程&#xff0c;使用生成对抗网络GAN生成数据集&#xff1a;输出生成数据集EXCEL格式文件&#xff0c;方便大家使用。 实际工程应用中&#xff0c;由于经济成本和人力成本的限制&#xff0c;获取大量典型的有标签的数据变得极具挑战&#xff0c;造成了训练样本…

cocos creator 3.8 一些简单的操作技巧,材质的创建 1

这是一个飞机的3D模型与贴图 导入到cocos中&#xff0c;法线模型文件中已经包含了mesh、material、prefab&#xff0c;也就是模型、材质与预制。界面上创建一个空节点Plane&#xff0c;将模型直接拖入到Plane下。新建材质如图下 Effect属性选择builtin-unlit&#xff0c;不需…

手机领夹麦克风哪个牌子好,哪种领夹麦性价比高,热门麦克风推荐

​在如今这个科技飞速发展的时代&#xff0c;麦克风的选择成了很多人关心的问题&#xff0c;特别是无线麦克风该怎么选呢&#xff1f;向我咨询麦克风选购事宜的人可不在少数。要是你只是想简单自娱自乐一下&#xff0c;其实真没必要大费周章&#xff0c;直接用手机自带的麦克风…

【功能实现】bilibili顶部鼠标跟随效果怎么实现?

我们在电脑端打开b站首页时&#xff0c;总会被顶部【鼠标跟随】的效果所吸引&#xff0c;那他是如何实现的&#xff0c;来研究一下。 b站效果&#xff1a; 分析&#xff1a; 1.监听鼠标的位置&#xff0c;当悬浮到该模块时&#xff0c;图片会随鼠标移动 2.引入图片的样式是动…

WebStorm 安装配置(详细教程)

文章目录 一、简介二、优势三、下载四、安装4.1 开始安装4.2 选择安装路径4.3 安装选项4.4 选择开始菜单文件夹4.5 安装完成 五、常用插件5.1 括号插件&#xff08;Rainbow Brackets&#xff09;5.2 翻译插件&#xff08;Translation&#xff09;5.3 代码缩略图&#xff08;Cod…