修订记录

日期

内容

2022/11/10

初步展示了前四个排序的代码实现

2022/11/14

增加流程图;对于选择、插入、希尔的代码实现做出优化

2022/11/15

增加快速排序、堆排序、二叉排序树

概述

  • 冒泡、选择:基于交换;
  • 插入、希尔:基于插入,希尔是对插入的改良;
  • 归并、快速:都用了分治的思想,都运用了双指针,都可以用递归来实现;
  • 基数排序、计数排序、桶排序都用到了桶的概念;
  • 堆排序利用了二叉树,堆就是一棵完全二叉树(扩展:二叉排序树BST)


各算法的实现

Talk is cheap! Let's go to the code!

冒泡排序

综述:相邻的两个数字依次比较

代码实现:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Buddle : MonoBehaviour
{
int[] array = new int[] { 7, 9, 13, 5, 2, 8 };
private void Start()
{
BubbleSort(array);
}

public void BubbleSort(int[] array)
{
int temp;
for (int i=0; i<array.Length-1; i++)
{
for(int j=0; j<array.Length-1; j++)
{
if (array[j]>array[j+1])
{
temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
PrintArray(array, i + 1);
}
}

public void PrintArray(int[] array, int index)
{
string ls = "";
for (int i=0; i<array.Length; i++)
{
ls = ls + " " + array[i].ToString();
}
Debug.Log("轮次:" + index + "数组:" + ls);
}
}

输出结果:

十大排序算法_数据结构

我们可以看到第四轮的时候这个数组其实就已经排序好了,下面做出改造:

public void BubbleSort(int[] array)
{
int temp;

for (int i=0; i<array.Length-1; i++)
{
bool isChanged = false;
for (int j=0; j<array.Length-1; j++)
{
if (array[j]>array[j+1])
{
temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
isChanged = true;
}
}

if (isChanged == false)
break;
PrintArray(array, i + 1);
}
}

以下为输出结果:

十大排序算法_算法_02


选择排序

综合思想:每一轮找出最小的值和第一个交换

十大排序算法_数据结构_03

代码实现:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Select : MonoBehaviour
{
int[] array = new int[] { 7, 9, 13, 5, 2, 8 };
private void Start()
{
SelectSort(array);
}

public void SelectSort(int[] array)
{
int temp;
for (int i=0; i<array.Length-1; i++)
{
int minIndex = i;
for (int j=i+1; j<array.Length; j++)
{
if (array[j]< array[minIndex])
{
minIndex = j;
}
}
temp = array[minIndex];
array[minIndex] = array[i];
array[i] = temp;

PrintArray(array, i + 1);
}
}

public void PrintArray(int[] array, int index)
{
string ls = "";
for (int i = 0; i < array.Length; i++)
{
ls = ls + " " + array[i].ToString();
}
Debug.Log("轮次:" + index + " 数组:" + ls);
}
}

输出:

十大排序算法_算法_04

插入排序

综述:分别提取第n个数作为待排序的对象,将n之前的数组看作已经排好序的部分,进行插入。

流程图:由于前半部分是已经排好序的,所以如果第i位是小于第j位的,这一轮内循环就不用进行了。

十大排序算法_算法_05

ps:整个流程图是按照我的写法迭代2来的。

代码示例:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Insert : MonoBehaviour
{
int[] array = new int[] { 7, 9, 13, 5, 2, 8 };
private void Start()
{
InsertSort(array);
}
public void PrintArray(int[] array, int index)
{
string ls = "";
for (int i = 0; i < array.Length; i++)
{
ls = ls + " " + array[i].ToString();
}
Debug.Log("轮次:" + index + "数组:" + ls);
}

//写法迭代1:这是我第一遍写的插入排序方法,运行结果是正确的,但是最好情况下的时间复杂度不够优化,每次内循环都还是需要执行的
public void InsertSort(int[] array)
{
int temp;
for (int j=1; j<array.Length; j++)
{
for (int i= 0; i<j; i++)
{
if (array[j] < array[i])
{
temp = array[j];
array[j] = array[i];
array[i] = temp;
}
}
PrintArray(array, j);
}
}

//写法迭代2:第二次写的插入排序方法(20221114)
//优化了一些细节,将内循环的顺序变成了从后往前,这样一开始比较就是排序好的话就不用进入内循环了
public void InsertSortMethod2(int[] arr)
{

for (int j=1; j<arr.Length; j++)
{
int temp = arr[j];
for (int i=j-1; i>=0; i--)
{
if (temp< arr[i])
{
arr[i + 1] = arr[i];
arr[i] = temp;
}
else
break;
}
PrintArray(array, j);
}
}
}

输出效果:

十大排序算法_数据结构_06


希尔排序

综述:对于插入排序的改进;引入了步长的概念,将一个数组划分成多个数组进行插入排序。

插入排序对于几乎已经排好序的数组是高效的,所以希尔排序的思想就是将数组分割成多个数组分别进行排序,等分别排好序了再进行整体的插入排序。

代码实现:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ShellSort : MonoBehaviour
{
int[] array = new int[] { 7, 9, 13, 5, 2, 8 };
private void Start()
{
ShellSortMethod(array);
}
public void PrintArray(int[] array, int index)
{
string ls = "";
for (int i = 0; i < array.Length; i++)
{
ls = ls + " " + array[i].ToString();
}
Debug.Log("步长:" + index + "数组:" + ls);
}

//写法迭代1:严格意义上不算是希尔
public void ShellSortMethod(int[] arr)
{
int temp;
for (int step = arr.Length/2; step>=1; step = step/2)
{
for (int i=0; i<arr.Length-step; i++)
{
for (int j=i+step; j<arr.Length; j=j+step)
{
if (arr[i] > arr[j])
{
temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
}
PrintArray(arr, step);
}
}
}
}

输出效果:

十大排序算法_算法_07

写法迭代2:这才算是正宗的希尔

public void ShellSortMethod2(int[] arr)
{
int step = 1;
while (step <=arr.Length/3)
{
step = step * 3 + 1;
}

while (step >=1)
{
for (int j = step; j<arr.Length; j++)
{
int temp = arr[j];
for (int i = j - step; i >= 0; i = i - step)
{
if (arr[i] > temp)
{
arr[i + step] = arr[i];
arr[i] = temp;
}
}
PrintArray(arr, step);
}

step = step / 3;
}
}

输出效果:

十大排序算法_算法_08

归并排序

综述:采用分治法的典型应用。有两种实现方式:自上而下的递归,自下而上的迭代。

代码实现:

这里我们使用自上而下的递归方式。递归往往涉及到两个概念:递归式和递归边界。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Merge : MonoBehaviour
{
int[] array = new int[] { 7, 9, 13, 5, 2, 8 };
private void Start()
{
MergeSort(array);
}
public void PrintArray(int[] array, int index)
{
string ls = "";
for (int i = 0; i < array.Length; i++)
{
ls = ls + " " + array[i].ToString();
}
Debug.Log("轮次:" + index + "数组:" + ls);
}

//通过循环分割数组
public int[] MergeSort(int[] arr)
{
int mid = arr.Length / 2;
if (mid < 1)
return arr;

int[] arr1 = new int[mid];
int[] arr2 = new int[arr.Length - mid];

//给左右两个数组赋值
for (int i=0; i<mid; i++)
{
arr1[i] = arr[i];
}
for (int j=mid; j<arr.Length;j++)
{
arr2[j - mid] = arr[j];
}

return MergeMethod(MergeSort(arr1),MergeSort(arr2));
}

public int[] MergeMethod(int[] arr1, int[] arr2)
{
int[] temp= new int[arr1.Length+arr2.Length];

int i = 0;
int j = 0;
int k = 0;

while (i<arr1.Length && j<arr2.Length && k<temp.Length)
{
temp[k++] = arr1[i] > arr2[j] ? arr2[j++] : arr1[i++];
}

while (i<arr1.Length)
{
temp[k++] = arr1[i++];
}

while (j < arr2.Length)
{
temp[k++] = arr2[j++];
}

PrintArray(temp, arr1.Length);
return temp;
}
}

输出效果:

十大排序算法_算法_09


快速排序

概述:快速排序应该算是在冒泡排序基础上的递归分治法。

代码实现:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Quick : MonoBehaviour
{
int[] array = new int[] { 7, 9, 13, 5, 2, 8 };

public void PrintArray(int[] array, int index)
{
string ls = "";
for (int i = 0; i < array.Length; i++)
{
ls = ls + " " + array[i].ToString();
}
Debug.Log("轮次:" + index + "数组:" + ls);
}
private void Start()
{
QuickSort(array, 0, array.Length - 1);
}

public void QuickSort(int[] arr, int left, int right)
{
if (left<right)
{
int index = Partition(arr,left, right);
QuickSort(arr, left, index - 1);
QuickSort(arr, index+1, right);
PrintArray(arr, index);
}

}
//划分之后的分别单个排序
public int Partition(int[] arr, int left, int right)
{
int temp = arr[left];//提取基准
while(left < right)
{
while (left<right && arr[right] > temp) right--;
arr[left] = arr[right];
while (left< right && arr[left] < temp) left++;
arr[right] = arr[left];
}
arr[left] = temp;
return left;
}
}

输出效果:

十大排序算法_算法_10

解释:

  • 第一轮:整体
  • out: 2 5 7 13 9 8
  • index = 2
  • temp = 7
  • 第二轮:左侧
  • out: 2 5
  • index = 0
  • temp = 2
  • 第三轮:右侧
  • out: 8 9 13
  • index = 5
  • temp =13
  • 第四轮:右侧的左侧
  • out:8 9
  • index = 3
  • temp = 8​

堆排序

概述:接下来的就是需要用到特殊的数据结构的方式了。堆是基于完全二叉树的,分为大顶堆和小顶堆。

  • 分类
  • 大顶堆:根节点大于叶子节点:Key[i] >= Key[2i+1] && key >= key[2i+2]
  • 小顶堆:叶子节点大于根节点: Key[i] <= key[2i+1] && Key[i] <= key[2i+2]
  • 一般升序排序用大顶堆,降序排序用小顶堆​

流程图:

十大排序算法_数据结构_11

代码实现:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Heap : MonoBehaviour
{
int[] array = new int[] { 7, 9, 13, 5, 2, 8 };

public void PrintArray(int[] array, int index)
{
string ls = "";
for (int i = 0; i < array.Length; i++)
{
ls = ls + " " + array[i].ToString();
}
Debug.Log("轮次:" + index + "数组:" + ls);
}
private void Start()
{
HeapSort(array);
}

public void HeapSort(int[] arr)
{

//构建初始的大顶堆
//建堆的过程就是从上往下调整
for (int i = (arr.Length/2)-1 ; i >= 0; i--)
{
AdjustHeap(arr, i, arr.Length);
PrintArray(arr, i);
}

for (int i = arr.Length - 1; i > 0; i--)
{
int temporary = arr[i];
arr[i] = arr[0];
arr[0] = temporary;

AdjustHeap(arr, 0, i);

PrintArray(arr, i);
}
PrintArray(arr, 100);
}

public void AdjustHeap(int[] arr, int root, int nodeCount)
{
int parent = arr[root];
int childIndex= root * 2 + 1;
//先从第0个元素开始

while( childIndex < nodeCount)
{
//找到左右节点中最大的值
if (childIndex+1<nodeCount && arr[childIndex]<arr[childIndex+1])
{
childIndex++;
}
if (arr[childIndex] <= parent)
{
break;
}

//将最大的子节点的值赋给根节点
int temp = parent;
arr[root] = arr[childIndex];
arr[childIndex] = temp;
//比较完成之后将这个子节点作为新的父节点,再比较这个节点的子节点
root = childIndex;
childIndex = 2 * childIndex + 1;
}
}
}

输出效果:

十大排序算法_数据结构_12

  • 前三个轮次是在构建初始的大顶堆
  • 后面的轮次是在依次拿根节点的值到数组的末尾


二叉排序树

本质上是一棵数据域有序的二叉树。左子树上所有结点都小于根节点,右子树上所有结点都大于根节点。

性质:对二叉排序树进行中序遍历的时候,遍历的结果是有序的。

一些概念区分:(后续再补)

  • 平衡二叉树
  • 满二叉树
  • 完全二叉树

代码实现:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BinaryTreeNode : MonoBehaviour
{
string ls = "";
int index = 0;

private void Start()
{
int[] array = { 15, 5, 11, 22, 6, 54, 8 };
BinarySort(array);
}

public void PrintArray(int[] array, int index)
{
//index = 0;
ls = "";
foreach(var r in array)
{
ls += r + ",";
}
Debug.Log("轮次:" + index++ + "数组:" + ls);
}

public void BinarySort(int[] arr)
{
var binarySortTreeNode = new BinaryTreeNodeClass(arr[0]);//原始的根节点是arr[0]

for (int i=1; i<arr.Length; i++)
{
binarySortTreeNode.Insert(arr[i]);

}
binarySortTreeNode.InorderTraversal();
}

public class BinaryTreeNodeClass
{
public int Key { get; set; }
public BinaryTreeNodeClass Left { get; set; }
public BinaryTreeNodeClass Right { get; set; }
//List<int> temp;
public int[] temp = new int[100];
int index = 0;

public BinaryTreeNodeClass(int key)//为根节点建立一个二叉树
{
Key = key;
}

public void Insert(int key)
{
var tree = new BinaryTreeNodeClass(key);

if (tree.Key <= Key)//如果创建之处的根结点小于要插入的值
{
if (Left == null)//如果左侧为空
{
Left = tree;//将根节点作为左侧子节点
}
else
{
Left.Insert(key);//如果左侧不为空,递归的插入到左侧就好
}
}
else//如果根节点的值大于要插入的值
{
if (Right == null)
{
Right = tree;//将根节点作为要插入的值的右侧树
}
else
{
Right.Insert(key);
}
}
}

//一个中序遍历的方法
public void InorderTraversal()
{
//左中右
Left?.InorderTraversal();
Debug.Log(Key);
Right?.InorderTraversal();
}
}
}

输出效果:

十大排序算法_数据结构_13