C#常用排序
插入排序
public class BaseInsertSort
{
/*插入排序
* 原理:
* 1. 将一个数组分为两片区域排序区和非排序区,这两片区域使用索引来区分
* 2. 每次都取非排序区的首个元素和排序区的元素依次比较,直到索引值等于数组长度,即排序完毕。
*
*/
public static int[] Sort(params int[] array)
{
//数组的排序索引默认从1开始,因为默认数组的第一个元素是有顺序的
for (int i = 1; i < array.Length; i++)
{
//暂存排序区的首个需要比较的索引数。
int SortNum = i - 1;
//暂存非排序区的第一个需要比较的值。后续排序区的移动会覆盖此项,所以需要提前存起来。
int currentSortNum = array[i];
//当索引值大于等于0,且排序区当前比较数大于非排序区比较数时执行移位操作
//(注意:索引为-1时说明该数是目前最小的数,直接存在SortNum+1即可)
while (SortNum >= 0 && currentSortNum < array[SortNum])
{
array[SortNum + 1] = array[SortNum];
SortNum--;
}
//跳出循环说明当前非排序区比较数找到了位置,赋值即可
//由于跳出循环时索引位置一定会在插入位置之后,所以要+1
array[SortNum + 1] = currentSortNum;
}
return array;
}
}
希尔排序
public class ShellSort
{
/*希尔排序(插入排序的升级版)
*原理:
* 1. 在插入排序的基础上使用步长stepLength对**待排序序列**进行分组,对每个子序列进行插入排序
* 2. 子序列中的元素在原数组中都是以步长stepLength为单位单位选出来的。
*/
public static int[] Sort(params int[] array)
{
/*
最外层确定希尔排序每轮的步长。
默认情况下初始步长为数组长度/2,可以改变除数,之后的每轮补偿都需要/2
步长可以确定排序区,(步长索引到数组最大长度)
*/
for (int stepLength = array.Length / 2; stepLength > 0; stepLength /= 2)
{
//根据步长确定了本轮的排列区长度
for (int i = stepLength; i < array.Length; i++)
{
//确定要比较的索引
int sortIndex = i - stepLength;
//暂存非排序区的第一个需要比较的值。后续排序区的移动会覆盖此项,所以需要提前存起来。
int sortNum = array[i];
//当索引值大于等于0,且排序区当前比较数大于非排序区比较数时执行移位操作
while (sortIndex >= 0 && sortNum <= array[sortIndex])
{
//移位时记得要移动步长的倍数单位(比较时就是按照步长跳跃比较的)
array[sortIndex + stepLength] = array[sortIndex];
//更新索引
sortIndex -= stepLength;
}
//跳出循环或者循环结束后说明找到了本次数值的存放位置
array[sortIndex + stepLength] = sortNum;
}
}
return array;
}
归并排序
/*归并排序
*原理:
* 1. 递归平分数组
* 2. 当数组容量小于2时开始排序
*口诀:
* 数组分为左右边
* 左右元素相比较
* 满足条件放入新数组
* 一侧用完放对面
*
* 递归不停分
* 分完再排序
* 排序结束向上走
* 边走边合并
* 走到顶部出结果
*/
public class MergeSort
{
/*
* 排序方法,主要负责处理数组比较
*/
private static int[] Sort(int[] left,int[] right)
{
//声明初始化左右数组的索引,有数组传入都要考虑创建。
int leftIndex = 0;
int rightIndex = 0;
//创建新数组用于存放比较结果,其大小为左右数组大小的相加和。
int[] array = new int[left.Length + right.Length];
//开始为新数组存放比较结果
for (int i = 0; i < array.Length; i++)
{
//如果leftIndex大于左数组的长度,说明左数组已经放完,按序放入右数组元素即可。
//这里不再写内循环放入,而是由顶层循环来做。
if (leftIndex >= left.Length)
{
array[i] = right[rightIndex];
rightIndex++;
}
//如果rightIndex大于右数组的长度,说明右数组已经放完,按序放入左数组元素即可。
//这里不再写内循环放入,而是由顶层循环来做。
else if (rightIndex >= right.Length)
{
array[i] = left[leftIndex];
leftIndex++;
}
//如果左数组的值大于右数组对应的值,将满足条件的值装入新数组,值对应数组的索引数++。
else if (left[leftIndex] > right[rightIndex])
{
array[i] = right[rightIndex];
rightIndex++;
}
//如果右数组的值大于左数组对应的值,将满足条件的值装入新数组,值对应数组的索引数++。
else
{
array[i] = left[leftIndex];
leftIndex++;
}
}
return array;
}
/*
* 数组分割方法:负责数组的递归分割。
*/
public static int[] Merge(params int[] array)
{
//如果数组长度小于2则直接返回上级
if (array.Length < 2)
{
return array;
}
//确定分界点mid
int mid = array.Length / 2;
//创建左右数组并以mid为分界点
int[] left = new int[mid];
int[] right = new int[array.Length - mid];
//为左右数组赋值
for (int i = 0; i < array.Length; i++)
{
if (i < mid)
{
left[i] = array[i];
}
else
{
right[i - mid] = array[i];
}
}
//递归调用
//这里按照计算规则会先调用小括号里面的merge方法,直到分割到最小向上返回后再调用Sort。
return Sort(Merge(left), Merge(right));
}
}
快速排序
public class QuickSort
{
/*
*快速排序:
* 快速排序中使用的一些变量的意义:
* 基准值tempBase:比较使用的一个基准值,默认以每轮的array[0]为基准值。
* left:array数组的左端
* right:array数组的右端
* 左右的作用是限定当前可排数组的范围。
* tempLeft:数组左游标初始 = left
* tempRight:数组右游标初始 = right
* 基准值一次只会和一个游标比较,所以游标的位置也是需要换位时的位置,且这个游标位置一定是和基准值对立的。
* 例:初始基准值为array[0],在数组最左端,则比较右游标,否则比较左游标。
*
* 口诀:
* 选取基准
* 产生左右游标
* 左右游标比基准
* 满足则换位
* 排完一次基准定位
* 左右递归直至有序
*/
public static void Sort(int[] array,int left,int right)
{
//递归结束条件:当数组限定左端 >= 数组右端时,说明此时的数组范围中只有一个数,返回即可
if (left >= right)
{
return;
}
//声明并初始化基准值和游标
int tempLeft, tempRight, tempBase;
tempLeft = left;
tempRight = right;
tempBase = array[tempLeft];
//当左右游标不相等时,说明没有排序完,继续排序
while (tempLeft != tempRight)
{
//如果基准值小于(这里条件看想要什么顺序)array[tempRight],说明此值相对于基准值的位置是正确的。
//直接右游标--移动至下一个位置进行比较。
while (tempLeft < tempRight && tempBase < array[tempRight])
{
tempRight--;
}
//当循环跳出时游标数值换位,经过这次换位后,基准值位置变到了右边,所以接下来要和tempLeft来比较。
array[tempLeft] = array[tempRight];
//逻辑和上面一样
while (tempLeft < tempRight && tempBase > array[tempLeft])
{
tempLeft++;
}
array[tempRight] = array[tempLeft];
}
//当跳出顶层循环说明两个游标相等,第一轮比较结束,我们需要为最开始取出来的基准值定位。
array[tempRight] = tempBase;
//递归左半数组和右半数组
Sort(array,left,tempLeft - 1);
Sort(array, tempLeft + 1, right);
}
}
归并和快排
共同点:
- 都用到了递归
- 都将数组分为几个部分
不同点:
- 归并排序先分完,最有向上排序并合并;快速排序时边排序边拆分。
- 归并排序在递归时会产生新数组,快速排序不会。
堆排序
/*
*堆排序
* 流程:
* 构建二叉树,这里我们不会构建一个真正的二叉树,而是通过一些规则来通过数组模拟二叉树
* 大堆顶调整
* 堆顶往后方
* 不停变堆顶
* 关键规则:
* 最大非叶子节点:
* 数组长度/2 - 1
* 父节点和叶子节点:
* 父节点为i
* 左节点2i+1
* 右节点2i+2
*
*/
public class HeapSort
{
//构建大堆顶,利用最大非子叶子节点的计算公式,我们应当从该节点开始调整大堆顶。
//调整完当前节点后,索引值--,调节下一个节点。
private static void BigHeapAdjust(int[] array)
{
for (int i = array.Length / 2 - 1; i >= 0 ; i--)
{
HeapAdjust(array, i, array.Length);
}
}
//将一个节点调整为大堆顶的方法
private static void HeapAdjust(int[] array, int currentRootIndex, int arrayLength)
{
//利用计算公式计算当前节点的两个子节点的索引值
int leftNode = 2 * currentRootIndex + 1;
int rightNode = 2 * currentRootIndex + 2;
//暂存当前根索引
int tempBiggerIndex = currentRootIndex;
//如果左子节点没有超出数组最大长度且子节点值大于根节点值,就需要将这个子节点的索引记录下来
if (leftNode < arrayLength && array[leftNode] > array[tempBiggerIndex])
{
tempBiggerIndex = leftNode;
}
//如果右子节点没有超出数组最大长度且子节点值大于根节点值,就需要将这个子节点的索引记录下来
if (rightNode < arrayLength && array[rightNode] > array[tempBiggerIndex])
{
tempBiggerIndex = rightNode;
}
//当经过上两步判断在根节点不是最大的情况下,我们就得到了当前三者之间的一个最大值索引,这时值互换即可
//如果根节点的值本身就是最大的,我们就不需要交换值了,所以这里多了一步判断。
if (tempBiggerIndex != currentRootIndex)
{
int tempValue = array[currentRootIndex];
array[currentRootIndex] = array[tempBiggerIndex];
array[tempBiggerIndex] = tempValue;
//当值互换了之后,我们需要确定被更换的子节点与其下面的节点三者之间仍满足大堆顶的关系,所以我们需要递归判断一下。
HeapAdjust(array,tempBiggerIndex,arrayLength);
}
}
//堆排序时调用的方法。
public static void Sort(int[] array)
{
//首先构建一个大堆顶。
BigHeapAdjust(array);
//每次取出根节点的值放在数组最后。放置完成后需要对整体判断构建一个新的大堆顶。
//i表示数组长度,一旦置后一个值,需要判断的数组元素个数就需要-1。
for (int i = array.Length - 1; i > 0; i--)
{
int tempValue = array[0];
array[0] = array[i];
array[i] = tempValue;
HeapAdjust(array,0,i);
}
}
}
冒泡排序
public class BubbleSort
{
public static void Sort(int[] array)
{
//一共有array.Length个数需要比较,但我们只需要比较array.Length-1轮。
//因为最有一个数不需要比较,直接有序
for (int i = 0; i < array.Length - 1; i++)
{
//每进行完成一轮未排列区的待排元素就会减少一个,所以我们需要在array.Length - 1的基础上再减去一个i轮数
for (int j = 0; j < array.Length - 1 - i; j++)
{
//冒泡排序的核心是就是两两排序,较大者后移
if (array[j] > array[j + 1])
{
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
}
}
选择排序
选择排序是冒泡排序的升级版,选择排序中每次比较我们只会记录最大值,最后才会做交换。
public class SelectSort
{
public static void Sort(int[] array)
{
//一共有array.Length个数需要比较,但我们只需要比较array.Length-1轮。
//因为最有一个数不需要比较,直接有序
for (int i = 0; i < array.Length - 1; i++)
{
int bigIndex = 0;
//每进行完成一轮未排列区的待排元素就会减少一个,所以我们需要在array.Length的基础上再减去一个i轮数
for (int j = 1; j < array.Length - i; j++)
{
//选择排序的核心是就是两两排序,记录较大值的索引
if (array[bigIndex] < array[j])
{
bigIndex = j;
}
}
//如果极值索引不在目标位置才需要交换
if (bigIndex != array.Length - 1 - i)
{
int temp = array[bigIndex];
array[bigIndex] = array[array.Length - 1 - i];
array[array.Length - 1 - i] = temp;
}
}
}
}