2989 字
15 分钟
排序算法

排序算法#

选择排序#

从头开始,以当前i的后一位为待排序元素,默认是最小元素 ,每次从未排序的序列里将选择最小的元素选择出来。

select.gif

//选择排序
public static void SelectionSort(int[] arr)
{
Console.WriteLine("选择排序");
for (int i = 0; i < arr.Length - 1; i++)
{
int minIndex = i;
for (int j = i + 1; j < arr.Length; j++)
{
if (arr[j] < arr[minIndex])
{
minIndex = j;
}
}
Swap(ref arr[i], ref arr[minIndex]);
}
}

冒泡排序#

从头开始,每轮比较也是从头开始,每次都是以临近两个元素之间交换,每轮比较的序列长度是length - i 个。

bubble.gif

//冒泡排序
public static void BubbleSort(int[] arr)
{
Console.WriteLine("冒泡排序");
for (int i = 0; i < arr.Length - 1; i++)
{
for (int j = 0; j < arr.Length - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
Swap(ref arr[j], ref arr[j + 1]);
}
}
}
}

插入排序#

从头开始,每次读取已排好序列的下一位元素,通过不断比较上一位元素大小,不断向前插入,直到合适位置。

insert.gif

//插入排序
public static void InsertionSort(int[] arr)
{
Console.WriteLine("插入排序");
for (int i = 1; i < arr.Length; i++)
{
int temp = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > temp)
{
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = temp;
}
}

希尔排序#

希尔排序的本质是优化版的插入排序,先将序列分组,gap为总长度的一半,元素下标 % gap 数值相同的为一组,组内使用插入排序,排序之后继续分组继续排序,直到gap为1排序完成之后就是最终结果。

图片来源于网络

//希尔排序
public static void ShellSort(int[] arr)
{
Console.WriteLine("希尔排序");
// 初始化间隔值为数组长度的一半
int gap = arr.Length / 2;
// 当间隔大于0时持续分组排序
while (gap > 0)
{
// 从gap位置开始遍历数组
for (int i = gap; i < arr.Length; i++)
{
// 保存当前待插入元素
int temp = arr[i];
// 初始化比较位置(前一个间隔位置的索引)
int j = i - gap;
// 在子序列中进行插入排序
while (j >= 0 && arr[j] > temp)
{
// 将较大元素后移间隔位
arr[j + gap] = arr[j];
// 向前检查同子序列的元素
j -= gap;
}
// 插入元素到正确位置
arr[j + gap] = temp;
}
// 缩小间隔(常见缩减方式为折半)
gap /= 2;
}
}

归并排序#

采用递归的思想,先分再和,通过递归拆解,直到无法继续拆解之后,比较两个子数组,选最小的元素放在新数组的前面,直到排序完成,再经过递归两两组合继续比较,直到所有排序完成。

merge.gif

计算顺序如下

image.png

#region 归并排序
//归并排序
// 归并排序入口方法
public static void MergeSort(int[] arr)
{
//Console.WriteLine("归并排序");
if (arr == null || arr.Length < 2) return; // 边界条件检查
// 调用递归排序方法,传入数组和边界索引
Sort(arr, 0, arr.Length - 1);
}
// 递归分解方法
private static void Sort(int[] arr, int left, int right)
{
//Console.WriteLine("拆分阶段left:"+left+","+"right:"+right);
// 递归终止条件
if (left >= right)
{
//Console.WriteLine("数组长度小于2,无法拆分");
return;
}
// 计算中间点(防止整数溢出)
int mid = left + (right - left) / 2;
// 递归排序左半部分
Sort(arr, left, mid);
// 递归排序右半部分
Sort(arr, mid + 1, right);
// 合并两个有序部分
Merge(arr, left, mid, right);
}
// 合并两个有序子数组
private static void Merge(int[] arr, int left, int mid, int right)
{
//Console.WriteLine("合并阶段left:"+left+","+"right:"+right);
// 创建临时数组存放合并结果
int[] temp = new int[right - left + 1];
int i = left; // 左子数组起始索引
int j = mid + 1; // 右子数组起始索引
int k = 0; // 临时数组索引
// 合并两个子数组
while (i <= mid && j <= right)
{
// 比较两个子数组当前元素,取较小者
temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
}
// 处理剩余元素(只会有一个子数组有剩余)
while (i <= mid) temp[k++] = arr[i++];
while (j <= right) temp[k++] = arr[j++];
// 将排序结果复制回原数组
Array.Copy(temp, 0, arr, left, temp.Length);
for (int a = 0; a < temp.Length; a++)
{
Console.Write(temp[a]+" ");
}
Console.WriteLine();
}
#endregion
测试数据 int[] testData = [54, 23, 32, 86, 75, 3, 212, 443, 31, 97 , 100];
排序顺序打印结果
23 54
23 32 54
75 86
3 75 86
3 23 32 54 75 86
212 443
31 212 443
97 100
31 97 100 212 443
3 23 31 32 54 75 86 97 100 212 443
数据量:11
排序耗时:4 毫秒
测试数据 int[] testData = [54, 23, 32, 86, 75, 3, 212, 443, 31, 97];
排序顺序打印结果
23 54
23 32 54
75 86
23 32 54 75 86
3 212
3 212 443
31 97
3 31 97 212 443
3 23 31 32 54 75 86 97 212 443
数据量:10
排序耗时:3 毫秒
前5个元素: 3, 23, 31, 32, 54
后5个元素: 75, 86, 97, 212, 443

快速排序#

也是采用递归的思想,选取第一个或者任意一个元素为基准,以当前排序序列最左边为Left,最右边为Right,先让Right与基准元素大小,如果大则Right继续左移,如果小于或等于则停下来,再以同样方式让Left右移,如果大于或等于则停下来,当Left比Right小时则交换元素,直到Left和Right相遇停下来,此时i指向的是上一次作为基准元素的下标,这个序列的这个元素(i指向元素)左边所有元素一定比这个元素小,右边所有元素一定比这个元素大,以这个下标为分隔点,不包括这个元素,分割为两个待排序的子序列,继续递归排序,直到Left和Right指向同一元素。

private static void QuickSort(int[] arr, int left, int right)
{
if (left >= right) return;
// 选择中间元素作为基准,并交换到左端(挖坑法需要从端点开始)
int mid = left + (right - left) / 2;
Swap(ref arr[left], ref arr[mid]); // 将基准移到左端
int pivot = arr[left];
// 执行挖坑法分区
int partitionIndex = HolePartition(arr, left, right, pivot);
// 递归处理子数组
QuickSort(arr, left, partitionIndex - 1);
QuickSort(arr, partitionIndex + 1, right);
}
// 挖坑法分区方法
private static int HolePartition(int[] arr, int left, int right, int pivot)
{
int hole = left; // 初始坑位在左端
while (left < right)
{
// 右侧找小:从右向左找第一个小于基准的元素
while (left < right && arr[right] >= pivot)
{
right--;
}
arr[hole] = arr[right]; // 填坑
hole = right; // 新坑位
// 左侧找大:从左向右找第一个大于基准的元素
while (left < right && arr[left] <= pivot)
{
left++;
}
arr[hole] = arr[left]; // 填坑
hole = left; // 新坑位
}
arr[hole] = pivot; // 基准填入最后坑位
return hole;
}
#endregion

堆排序#

堆排序是建立在堆数据结构之上的。堆是一种完全二叉树,分为大根堆和小根堆,树的每一层都大于等于或者小于等于下一层,层内之间没有大小的排序。通常堆的第一个元素取顺序表下标为1的位置,层序构建初始的完全二叉树。

堆排序可以简单分为两个过程,首先是构建堆结构。使用递归的方式,从最后一个非叶子右节点开始,逐步向前构建一个大根堆,直到根节点结束,此时交换根节点和最后一个叶子节点位置,再把最后一个叶子节点移除,倒数第一个数就算排序完成,循环往复,直到排序完成。

//堆排序
#region 堆排序
public static void HeapSort(int[] arr)
{
Console.WriteLine("堆排序");
// 构建初始大顶堆
for (int i = arr.Length / 2 - 1; i >= 0; i--)
{
Heapify(arr, arr.Length, i);
}
// 依次提取最大元素到数组末尾
for (int i = arr.Length - 1; i > 0; i--)
{
Swap(ref arr[0], ref arr[i]); // 将堆顶元素移到末尾
Heapify(arr, i, 0); // 调整剩余元素的堆结构
}
}
private static void Heapify(int[] arr, int heapSize, int rootIndex)
{
int largest = rootIndex; // 初始化最大元素为根节点
int leftChild = 2 * rootIndex + 1;
int rightChild = 2 * rootIndex + 2;
// 左子节点存在且大于根节点
if (leftChild < heapSize && arr[leftChild] > arr[largest])
{
largest = leftChild;
}
// 右子节点存在且大于当前最大值
if (rightChild < heapSize && arr[rightChild] > arr[largest])
{
largest = rightChild;
}
// 如果最大值不是根节点,则交换并递归调整
if (largest != rootIndex)
{
Swap(ref arr[rootIndex], ref arr[largest]);
Heapify(arr, heapSize, largest);
}
}
#endregion

基数排序#

基数排序就是将待排序的数字序列按照从个位、十位、百位、千位的数字这样的顺序一遍一遍排,直到最高位的数字排序完。

#region 基数排序
public static void RadixSort(int[] arr)
{
Console.WriteLine("基数排序");
if (arr == null || arr.Length < 2) return;
// 获取最大值确定位数
int max = GetMaxValue(arr);
// 从低位到高位进行排序
for (int exp = 1; max / exp > 0; exp *= 10)
{
CountingSortByDigit(arr, exp);
}
}
private static int GetMaxValue(int[] arr)
{
int max = arr[0];
for (int i = 1; i < arr.Length; i++)
{
if (arr[i] > max) max = arr[i];
}
return max;
}
private static void CountingSortByDigit(int[] arr, int exp)
{
int[] output = new int[arr.Length];
int[] count = new int[10];
// 统计当前位数字出现次数
for (int i = 0; i < arr.Length; i++)
{
count[(arr[i] / exp) % 10]++;
}
// 计算累计位置
for (int i = 1; i < 10; i++)
{
count[i] += count[i - 1];
}
// 构建输出数组(逆序保证稳定性)
for (int i = arr.Length - 1; i >= 0; i--)
{
int digit = (arr[i] / exp) % 10;
output[count[digit] - 1] = arr[i];
count[digit]--;
}
// 复制回原数组
Array.Copy(output, arr, arr.Length);
}
#endregion

计数排序#

计数排序就是先创建一个用于计算数量的序列,序列的起始和结束下标是待排序列的最小值和最大值,统计待排序列的元素出现个数,然后再输出。

在这里插入图片描述

#region 计数排序
public static void CountingSort(int[] arr)
{
Console.WriteLine("计数排序");
if (arr == null || arr.Length < 2) return;
// 获取数值范围
int min = arr[0], max = arr[0];
foreach (int num in arr)
{
if (num < min) min = num;
if (num > max) max = num;
}
// 创建计数数组
int[] count = new int[max - min + 1];
// 统计元素出现次数
foreach (int num in arr)
{
count[num - min]++;
}
// 累加计数(计算最终位置)
for (int i = 1; i < count.Length; i++)
{
count[i] += count[i - 1];
}
// 构建输出数组(反向遍历保证稳定性)
int[] output = new int[arr.Length];
for (int i = arr.Length - 1; i >= 0; i--)
{
int index = arr[i] - min;
output[count[index] - 1] = arr[i];
count[index]--;
}
// 复制回原数组
Array.Copy(output, arr, arr.Length);
}
#endregion

桶排序#

桶排序思想很简单,就是对初始序列进行划分,划分出多个均匀的小区间,小区间内部使用任意算法进行排序。对于桶的划分可以有很多种,桶内排序也可以根据情况选择。

在这里插入图片描述

#region 桶排序
public static void BucketSort(int[] arr)
{
Console.WriteLine("桶排序");
if (arr == null || arr.Length < 2) return;
// 1. 计算数值范围
int min = arr[0], max = arr[0];
foreach (int num in arr)
{
if (num < min) min = num;
if (num > max) max = num;
}
// 2. 动态计算桶参数(每个桶约1000范围)
int bucketSize = 1000;
int bucketCount = (max - min)/bucketSize + 1;
List<List<int>> buckets = new List<List<int>>(bucketCount);
// 3. 初始化桶
for (int i = 0; i < bucketCount; i++)
{
buckets.Add(new List<int>());
}
// 4. 元素分配到桶
foreach (int num in arr)
{
int index = (num - min)/bucketSize;
buckets[index].Add(num);
}
// 5. 各桶使用插入排序(与现有代码风格统一)
int indexArr = 0;
foreach (var bucket in buckets)
{
if(bucket.Count == 0) continue;
// 调用现有的插入排序(需转换为数组)
int[] temp = bucket.ToArray();
InsertionSort(temp);
Array.Copy(temp, 0, arr, indexArr, temp.Length);
indexArr += temp.Length;
}
}
#endregion
排序算法
https://mizuki.mysqil.com/posts/09排序算法/
作者
Pixelmoe
发布于
2024-06-14
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

封面
示例歌曲
示例艺术家
封面
示例歌曲
示例艺术家
0:00 / 0:00