Unity3D 实用技巧 - 经典排序算法回溯篇【C#】

本文介绍并实现了十种经典排序算法,包括选择排序、插入排序、冒泡排序等,并提供了Unity项目的实战示例。
在大数据时代中,如何从海量数据中挖掘优质内容,算法成为有效规整和利用的一种常见应用手段。同时作为程序员修炼内功最直接的途径,就是深度学习和活用这强大的算法,所以让我们奔回曾经学习的起点,回溯这经典的排序算法。
诚然,我们能发现无论是算法书籍,还是论坛里面的优质文章,发现讲述经典排序算法还真不少。 所以我们还是进行汇总式学习吧!直接上总概览图理论性认识经典排序算法哈。
RUNOOB.COM 十大经典算法(里面包含多种编程语言的算法实现): https://www.runoob.com/w3cnote/ten-sorting-algorithm.html
接着,跨越理论的认识,我们还是踏踏实实在引擎编辑中,进行排序算法的实战练习吧。
1、按照常规操作,新建UnitySortExample Unity3D项目,简单的配置我们直接略过,我们直奔核心算法逻辑类(C#)。
    
using System ; using System . Collections . Generic ; using UnityEngine ; /// <summary> /// 排序逻辑控制器(参照:https://www.runoob.com/w3cnote/ten-sorting-algorithm.html) /// </summary> public class SortController : MonoBehaviour { private static SortController _instance ; public static SortController GetInstance ( ) { return _instance ; } void Awake ( ) { if ( _instance == null ) { _instance = this ; DontDestroyOnLoad ( gameObject ) ; } else { if ( _instance != this ) { Debug . Log ( "warning: multiple Manager creating!" ) ; GameObject . Destroy ( gameObject ) ; } } } # region 选择排序 /// <summary> /// 选择排序 /// </summary> /// <param name="arr">需要排序的数组值</param> /// <returns></returns> public int [ ] SelectSort ( int [ ] arr ) { int n = arr . Length ; for ( int i = 0 ; i < n - 1 ; i ++ ) { int min = i ; for ( int j = i + 1 ; j < n ; j ++ ) { if ( arr [ min ] > arr [ j ] ) min = j ; } //交换 int temp = arr [ i ] ; arr [ i ] = arr [ min ] ; arr [ min ] = temp ; } return arr ; } # endregion # region 插入排序 /// <summary> /// 插入排序 /// </summary> /// <param name="arr">需要排序的数组值</param> /// <returns></returns> public int [ ] InsertSort ( int [ ] arr ) { if ( arr == null || arr . Length < 2 ) return arr ; int n = arr . Length ; for ( int i = 1 ; i < n ; i ++ ) { int temp = arr [ i ] ; int k = i - 1 ; while ( k >= 0 && arr [ k ] > temp ) k -- ; //腾出位置插进去,要插的位置是 k + 1; for ( int j = i ; j > k + 1 ; j -- ) arr [ j ] = arr [ j - 1 ] ; //插进去 arr [ k + 1 ] = temp ; } return arr ; } # endregion # region 冒泡排序 /// <summary> /// 冒泡排序 /// </summary> /// <param name="arr">需要排序的数组值</param> /// <returns></returns> public int [ ] BubbleSort ( int [ ] arr ) { if ( arr == null || arr . Length < 2 ) { return arr ; } int n = arr . Length ; for ( int i = 0 ; i < n ; i ++ ) { for ( int j = 0 ; j < n - i - 1 ; j ++ ) { if ( arr [ j + 1 ] < arr [ j ] ) { int t = arr [ j ] ; arr [ j ] = arr [ j + 1 ] ; arr [ j + 1 ] = t ; } } } return arr ; } # endregion # region 希尔排序 /// <summary> /// 希尔排序 /// </summary> /// <param name="arr">需要排序的数组值</param> /// <returns></returns> public int [ ] ShellSort ( int [ ] arr ) { if ( arr == null || arr . Length < 2 ) return arr ; int n = arr . Length ; // 对每组间隔为 h的分组进行排序,刚开始 h = n / 2; for ( int h = n / 2 ; h > 0 ; h /= 2 ) { //对各个局部分组进行插入排序 for ( int i = h ; i < n ; i ++ ) { // 将arr[i] 插入到所在分组的正确位置上 InsertIValue ( arr , h , i ) ; } } return arr ; } /// <summary> /// 将arr[i]插入到所在分组的正确位置上,arr[i]] 所在的分组为 ... arr[i-2*h],arr[i-h], arr[i+h] ... /// </summary> /// <param name="arr"></param> /// <param name="h"></param> /// <param name="i"></param> private void InsertIValue ( int [ ] arr , int h , int i ) { int temp = arr [ i ] ; int k ; for ( k = i - h ; k > 0 && temp < arr [ k ] ; k -= h ) { arr [ k + h ] = arr [ k ] ; } arr [ k + h ] = temp ; } # endregion # region 归并排序 /// <summary> /// 归并排序(递归式) /// </summary> /// <param name="arr">需要排序的数组值</param> /// <param name="left">左半部分索引</param> /// <param name="right">右半部分索引</param> /// <returns></returns> public int [ ] MergeSort ( int [ ] arr , int left , int right ) { // 如果 left == right,表示数组只有一个元素,则不用递归排序 if ( left < right ) { // 把大的数组分隔成两个数组 int mid = ( left + right ) / 2 ; // 对左半部分进行排序 arr = MergeSort ( arr , left , mid ) ; // 对右半部分进行排序 arr = MergeSort ( arr , mid + 1 , right ) ; //进行合并 Merge ( arr , left , mid , right ) ; } return arr ; } /// <summary> /// 归并排序(非递归式) /// </summary> /// <param name="arr"></param> /// <returns></returns> public int [ ] MergeNonFuncSort ( int [ ] arr ) { int n = arr . Length ; //子数组的大小分别为1,2,4,8...,合并的数组大小是1,接着是2,接着4... for ( int i = 1 ; i < n ; i += i ) { //进行数组进行划分 int left = 0 ; int mid = left + i - 1 ; int right = mid + i ; //进行合并,对数组大小为 i 的数组进行两两合并 while ( right < n ) { //合并函数和递归式的合并函数一样 Merge ( arr , left , mid , right ) ; left = right + 1 ; mid = left + i - 1 ; right = mid + i ; } // 剩余数组合并 if ( left < n && mid < n ) { Merge ( arr , left , mid , n - 1 ) ; } } return arr ; } /// <summary> /// 合并函数,把两个有序的数组合并起来。arr[left..mif]表示一个数组,arr[mid+1 .. right]表示一个数组 /// </summary> /// <param name="arr"></param> /// <param name="left"></param> /// <param name="mid"></param> /// <param name="right"></param> private void Merge ( int [ ] arr , int left , int mid , int right ) { //临时数组进行合并汇总 int [ ] a = new int [ right - left + 1 ] ; int i = left ; int j = mid + 1 ; int k = 0 ; while ( i <= mid && j <= right ) { if ( arr [ i ] < arr [ j ] ) { a [ k ++ ] = arr [ i ++ ] ; } else { a [ k ++ ] = arr [ j ++ ] ; } } while ( i <= mid ) a [ k ++ ] = arr [ i ++ ] ; while ( j <= right ) a [ k ++ ] = arr [ j ++ ] ; //临时数组复制到原数组 for ( i = 0 ; i < k ; i ++ ) { arr [ left ++ ] = a [ i ] ; } } # endregion # region 快速排序 /// <summary> /// 快速排序 /// </summary> /// <param name="arr">需要排序的数组值</param> /// <param name="left"></param> /// <param name="right"></param> /// <returns></returns> public int [ ] QuickSort ( int [ ] arr , int left , int right ) { if ( left < right ) { //获取中轴元素所处的位置 int mid = PartPosition ( arr , left , right ) ; //进行分割 arr = QuickSort ( arr , left , mid - 1 ) ; arr = QuickSort ( arr , mid + 1 , right ) ; } return arr ; } /// <summary> /// 获取中轴元素所处的位置 /// </summary> /// <param name="arr"></param> /// <param name="left"></param> /// <param name="right"></param> /// <returns></returns> private int PartPosition ( int [ ] arr , int left , int right ) { //选取中轴元素 int pivot = arr [ left ] ; int i = left + 1 ; int j = right ; while ( true ) { // 向右找到第一个小于等于 pivot 的元素位置 while ( i <= j && arr [ i ] <= pivot ) i ++ ; // 向左找到第一个大于等于 pivot 的元素位置 while ( i <= j && arr [ j ] >= pivot ) j -- ; if ( i >= j ) break ; //交换两个元素的位置,使得左边的元素不大于pivot,右边的不小于pivot int temp = arr [ i ] ; arr [ i ] = arr [ j ] ; arr [ j ] = temp ; } arr [ left ] = arr [ j ] ; // 使中轴元素处于有序的位置 arr [ j ] = pivot ; return j ; } # endregion # region 堆排序 /// <summary> /// 堆排序 /// </summary> /// <param name="arr"></param> /// <returns></returns> public int [ ] HeadSort ( int [ ] arr ) { int n = arr . Length ; //构建大顶堆 for ( int i = ( n - 2 ) / 2 ; i >= 0 ; i -- ) { DownAdjust ( arr , i , n - 1 ) ; } //进行堆排序 for ( int i = n - 1 ; i >= 1 ; i -- ) { // 把堆顶元素与最后一个元素交换 int temp = arr [ i ] ; arr [ i ] = arr [ 0 ] ; arr [ 0 ] = temp ; // 把打乱的堆进行调整,恢复堆的特性 DownAdjust ( arr , 0 , i - 1 ) ; } return arr ; } /// <summary> /// 下沉操作 /// </summary> /// <param name="arr"></param> /// <param name="parent"></param> /// <param name="n"></param> public void DownAdjust ( int [ ] arr , int parent , int n ) { //临时保存要下沉的元素 int temp = arr [ parent ] ; //定位左孩子节点的位置 int child = 2 * parent + 1 ; //开始下沉 while ( child <= n ) { // 如果右孩子节点比左孩子大,则定位到右孩子 if ( child + 1 <= n && arr [ child ] < arr [ child + 1 ] ) child ++ ; // 如果孩子节点小于或等于父节点,则下沉结束 if ( arr [ child ] <= temp ) break ; // 父节点进行下沉 arr [ parent ] = arr [ child ] ; parent = child ; child = 2 * parent + 1 ; } arr [ parent ] = temp ; } # endregion # region 计算排序 /// <summary> /// 计算排序 /// </summary> /// <param name="arr"></param> /// <returns></returns> public int [ ] CountSort ( int [ ] arr ) { if ( arr == null || arr . Length < 2 ) return arr ; int n = arr . Length ; int min = arr [ 0 ] ; int max = arr [ 0 ] ; // 寻找数组的最大值与最小值 for ( int i = 1 ; i < n ; i ++ ) { if ( max < arr [ i ] ) max = arr [ i ] ; if ( min > arr [ i ] ) min = arr [ i ] ; } int d = max - min + 1 ; //创建大小为max的临时数组 int [ ] temp = new int [ d ] ; //统计元素i出现的次数 for ( int i = 0 ; i < n ; i ++ ) { temp [ arr [ i ] - min ] ++ ; } int k = 0 ; //把临时数组统计好的数据汇总到原数组 for ( int i = 0 ; i < d ; i ++ ) { for ( int j = temp [ i ] ; j > 0 ; j -- ) { arr [ k ++ ] = i + min ; } } return arr ; } # endregion # region 桶排序 /// <summary> /// 桶排序 /// </summary> /// <param name="arr"></param> /// <param name="bucketNum"></param> public double [ ] BucketSort ( double [ ] arr , int bucketNum ) { //创建bucket时,在二维中增加一组标识位,其中bucket[x, 0]表示这一维所包含的数字的个数 //通过这样的技巧可以少写很多代码 double [ , ] bucket = new double [ bucketNum , arr . Length + 1 ] ; foreach ( var num in arr ) { int bit = ( int ) ( bucketNum * num ) ; bucket [ bit , ( int ) ++ bucket [ bit , 0 ] ] = num ; } //为桶里的每一行使用插入排序 for ( int j = 0 ; j < bucketNum ; j ++ ) { //为桶里的行创建新的数组后使用插入排序 double [ ] insertion = new double [ ( int ) bucket [ j , 0 ] ] ; for ( int k = 0 ; k < insertion . Length ; k ++ ) { insertion [ k ] = bucket [ j , k + 1 ] ; } //调用插入排序 StraightInsertionSort ( insertion ) ; //把排好序的结果回写到桶里 for ( int k = 0 ; k < insertion . Length ; k ++ ) { bucket [ j , k + 1 ] = insertion [ k ] ; } } //将所有桶里的数据回写到原数组中 for ( int count = 0 , j = 0 ; j < bucketNum ; j ++ ) { for ( int k = 1 ; k <= bucket [ j , 0 ] ; k ++ ) { arr [ count ++ ] = bucket [ j , k ] ; } } return arr ; } private void StraightInsertionSort ( double [ ] array ) { //插入排序 for ( int i = 1 ; i < array . Length ; i ++ ) { double sentinel = array [ i ] ; int j = i - 1 ; while ( j >= 0 && sentinel < array [ j ] ) { array [ j + 1 ] = array [ j ] ; j -- ; } array [ j + 1 ] = sentinel ; } } # endregion # region 基数排序 /// <summary> /// 基数排序 /// </summary> /// <param name="myArray"></param> /// <param name="keyNum"></param> public int [ ] RandixSort ( int [ ] myArray , int keyNum ) { SingleLinkedList < int > listArray = new SingleLinkedList < int > ( ) ; foreach ( int i in myArray ) { listArray . AddLast ( new SingleLLNode < int > ( ) { Value = i } ) ; } for ( int i = 0 ; i < keyNum ; i ++ ) { // 对每个关键字执行分配和收集操作 DistributeAndCollect ( listArray , i ) ; } int j = 0 ; while ( listArray . First != null ) { myArray [ j ++ ] = listArray . First . Value ; listArray . First = listArray . First . Next ; } return myArray ; } /// <summary> /// 分配和收集 /// </summary> /// <param name="listArray"></param> /// <param name="i"></param> private void DistributeAndCollect ( SingleLinkedList < int > listArray , int i ) { int randix = 10 ; //关键字取值范围 int divider = ( int ) Math . Pow ( 10 , i ) ; List < SingleLinkedList < int > > subLists = new List < SingleLinkedList < int > > ( ) ; //建立子序列 for ( int j = 0 ; j < randix ; j ++ ) { subLists . Add ( new SingleLinkedList < int > ( ) ) ; } // 开始一次分配 while ( listArray . First != null ) { int index = ( listArray . First . Value / divider ) % 10 ; SingleLLNode < int > tempNode = listArray . First . Next ; subLists [ index ] . AddLast ( listArray . First ) ; listArray . First = tempNode ; } // 开始一次收集 int k = 0 ; for ( ; k < randix ; k ++ ) { if ( subLists [ k ] . First != null ) { // 找到第一个非空子序列以设置总序列的First值 listArray . First = subLists [ k ] . First ; listArray . Last = subLists [ k ] . Last ; break ; } } // 找好子序列设置好listArray.First后,开始处理非空子序列的首尾相连 for ( ; k < randix ; k ++ ) { if ( subLists [ k ] . First != null ) { listArray . Last . Next = subLists [ k ] . First ; listArray . Last = subLists [ k ] . Last ; } } } # endregion } // 单链表 class SingleLinkedList < T > { public SingleLLNode < T > First { get ; set ; } public SingleLLNode < T > Last { get ; set ; } public void AddLast ( SingleLLNode < T > node ) { if ( First == null ) { First = node ; Last = node ; node . Next = null ; } else { Last . Next = node ; Last = node ; node . Next = null ; } } } // 单链表结点 class SingleLLNode < T > { public T Value { get ; set ; } public SingleLLNode < T > Next { get ; set ; } }
2、结合简单UI的展示,运行预览测试。
相关实战练习工程已直接上传在Github上,感兴趣的小伙伴可以下载一起学习哈。
最后,小编还想推荐一些三方有关算法的分享(感兴趣的可以直接Mark下哈):
1、使用C#和Windows窗体应用程序实现的排序可视化工具: https://github.com/haltaf19/Sorting-Visualizer
2、 排序算法可视化,使用Unity实现的排序算法演示系统: https://github.com/ChengziCao/sorting-visualization
3、基于Unity简单地实现游戏程序开发中常用的算法: https://github.com/IceLanguage/LinHowe_GameAlgorithm
4、Unity中的地形拓扑算法: https://github.com/Scrawk/Terrain-Topology-Algorithms
5、BFS、贪婪最佳优先搜索、Dijkstra和A*路径查找算法: https://github.com/dbrizov/Unity-PathFindingAlgorithms
6、基于遗传进化算法的Unity计算着色程序绘制算法: https://github.com/IRCSS/Procedural-painting
7、Teddy算法(将二维多边形转换为三维模型)在Unity中的实现: https://github.com/mattatz/unity-teddy
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值