算法合集
https://github.com/SYRollingStone/SiYangUnityAlgorithmsSamples
一、分治法
分:大问题分解物小问题。
治:递归解决小问题。
并:将小问题的结果合并成为结果。
二、合并排序
4, 8, 1, 6, 0, 5, 3, 9
分
4, 8, 1, 6, 0, 5, 3, 9
↓ 切一刀
4, 8, 1, 6\] \[0, 5, 3, 9
↓ ↓
4, 8\] \[1, 6\] \[0, 5\] \[3, 9
↓ ↓ ↓ ↓
4\] \[8\] \[1\] \[6\] \[0\] \[5\] \[3\] \[9
治+合:
合并第 1 层:把两个"长度1的有序段"合成"长度2的有序段"
merge([4], [8]) => [4, 8]
merge([1], [6]) => [1, 6]
merge([0], [5]) => [0, 5]
merge([3], [9]) => [3, 9]
合并第 2 层:把两个"长度2的有序段"合成"长度4的有序段"
merge([4,8], [1,6]) => [1,4,6,8]
merge([0,5], [3,9]) => [0,3,5,9]
合并第 3 层:合成最终结果
merge([1,4,6,8], [0,3,5,9]) => [0,1,3,4,5,6,8,9]
merge中的双指针排序
以这一步为例:
merge([4,8], [1,6])
用两个指针 i、j 分别指向左右数组的"当前最小元素":
Left : [4, 8]
i^
Right: [1, 6]
j^
结果 result: []
先4和1比较,1更小,1放入result,j指针移动到6
Left : [4, 8]
i^
Right: [1, 6]
j^
结果 result: [1]
4和6比较,4放入result,i指针移动到8
Left : [4, 8]
i^
Right: [1, 6]
j^
结果 result: [1,4]
6和8比较,6放入result
Left : [4, 8]
i^
Right: [1, 6]
j^
结果 result: [1,4,6]
将最后一个8放入result
结果 result: [1,4,6,8]
三、代码
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// 分治法
// 合并排序
public class MergeSort : MonoBehaviour
{
private int[] _data = {2,1,5,3,4,7,8,9,0,15,67,45 ,100,134,121,111,11};
// Start is called before the first frame update
void Start()
{
Debug.Log("Before: " + string.Join(", ", _data));
int[] temp = new int[_data.Length];
Sort(_data, temp, 0, temp.Length - 1);
Debug.Log("After: " + string.Join(", ", _data));
}
private void Sort(int[] array,int[] temp,int left,int right)
{
// 分,每次分一半,直到不可以再分 即 数组长度为1
if (left >= right) return;
int mid = left + (right - left) / 2;
Sort(array,temp,left,mid);
Sort(array,temp,mid+1,right);
Merge(array, temp, left, mid, right);
}
private void Merge(int [] array,int [] temp,int left,int mid, int right)
{
for (int i = left; i <= right; i++)
{
temp[i] = array[i];
}
int iLeft = left; // 左边数组的下指针
int iRight = mid + 1;// 右边数组的指针,从mid+1开始
int cur = left;
while (iLeft <= mid && iRight <= right)
{
// <= 保持稳定性(相等时先取左边)
if (temp[iLeft] <= temp[iRight]) // 改成 >= 就是降序
array[cur++] = temp[iLeft++];
else
array[cur++] = temp[iRight++];
}
// 左边剩余拷贝回去(右边剩余不需要:已经在 a 的正确位置)
while (iLeft <= mid)
array[cur++] = temp[iLeft++];
}
// Update is called once per frame
void Update()
{
}
}
四、分治法和动态规划的区别
我们发现分治法和动态规划都是划分子问题。
如果子问题重复度极高,用动态规划。子问题重复度不高用分治法。
严谨的说法:
-
分治法 :把问题拆成相互独立(或基本不重叠)的子问题,分别求解后合并;通常不需要缓存子结果。
-
动态规划 :子问题存在重叠(Overlapping Subproblems) ,并且满足最优子结构(Optimal Substructure),所以要用记忆化/填表复用子问题结果,避免重复计算。