在C#中,常见的十大排序算法包括:
1. 冒泡排序(Bubble Sort):
通过多次遍历数组,比较相邻元素并交换,使较大的元素逐渐"浮"到数组末尾。时间复杂度为O(n^2)。
- 优点:实现简单,逻辑清晰。
- 缺点:效率低下,时间复杂度为O(n^2)。
public static void BubbleSort(int[] nums)
{
int n = nums.Length;
bool swapped;
for (int i = 0; i < n - 1; i++)
{
swapped = false;
for (int j = 0; j < n - i - 1; j++)
{
if (nums[j] > nums[j + 1])
{
// 交换元素
int temp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = temp;
swapped = true;
}
}
// 如果没有发生交换,则数组已经有序,提前结束排序
if (!swapped)
break;
}
}
2. 选择排序(Selection Sort):
每次遍历数组,选择最小(或最大)的元素,与当前位置进行交换。时间复杂度为O(n^2)。
- 优点:实现简单。
- 缺点:时间复杂度为O(n^2),对大规模数据排序效率较低。
public static void SelectionSort(int[] nums)
{
int n = nums.Length;
for (int i = 0; i < n - 1; i++)
{
int minIndex = i;
for (int j = i + 1; j < n; j++)
{
if (nums[j] < nums[minIndex])
{
minIndex = j;
}
}
Swap(nums, i, minIndex);
}
}
private static void Swap(int[] nums, int i, int j)
{
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
3. 插入排序(Insertion Sort):
将数组分为已排序和未排序两部分,逐个将未排序的元素插入到已排序部分的正确位置。时间复杂度为O(n^2)。
- 优点:对于小规模或基本有序的数据排序效率较高。
- 缺点:对大规模数据排序效率较低,时间复杂度为O(n^2)。
public static void InsertionSort(int[] nums)
{
int n = nums.Length;
for (int i = 1; i < n; i++)
{
int key = nums[i];
int j = i - 1;
while (j >= 0 && nums[j] > key)
{
nums[j + 1] = nums[j];
j--;
}
nums[j + 1] = key;
}
}
4. 希尔排序(Shell Sort):
插入排序的改进版,将数组分为多个子序列,分别进行插入排序,然后逐步减小子序列的长度。时间复杂度与步长序列的选择有关,平均为O(n^1.3)。
- 优点:对中等规模数据排序效率较高。
- 缺点:实现较为复杂,时间复杂度取决于步长序列的选择。
public static void ShellSort(int[] nums)
{
int n = nums.Length;
int gap = n / 2;
while (gap > 0)
{
for (int i = gap; i < n; i++)
{
int temp = nums[i];
int j = i;
while (j >= gap && nums[j - gap] > temp)
{
nums[j] = nums[j - gap];
j -= gap;
}
nums[j] = temp;
}
gap /= 2;
}
}
5. 归并排序(Merge Sort):
将数组不断分割为两个子数组,递归地对子数组进行排序,然后将已排序的子数组合并。时间复杂度为O(nlogn)。
- 优点:稳定且效率较高,时间复杂度为O(nlogn)。
- 缺点:需要额外的内存空间进行合并操作。
public static void MergeSort(int[] nums)
{
MergeSort(nums, 0, nums.Length - 1);
}
private static void MergeSort(int[] nums, int left, int right)
{
if (left < right)
{
int mid = (left + right) / 2;
MergeSort(nums, left, mid);
MergeSort(nums, mid + 1, right);
Merge(nums, left, mid, right);
}
}
private static void Merge(int[] nums, int left, int mid, int right)
{
int n1 = mid - left + 1;
int n2 = right - mid;
int[] leftArray = new int[n1];
int[] rightArray = new int[n2];
Array.Copy(nums, left, leftArray, 0, n1);
Array.Copy(nums, mid + 1, rightArray, 0, n2);
int i = 0, j = 0;
int k = left;
while (i < n1 && j < n2)
{
if (leftArray[i] <= rightArray[j])
{
nums[k] = leftArray[i];
i++;
}
else
{
nums[k] = rightArray[j];
j++;
}
k++;
}
while (i < n1)
{
nums[k] = leftArray[i];
i++;
k++;
}
while (j < n2)
{
nums[k] = rightArray[j];
j++;
k++;
}
}
6. 快速排序(Quick Sort):
选择一个基准元素,将小于基准的元素放在左边,大于基准的元素放在右边,然后递归地对左右子数组进行排序。时间复杂度为O(nlogn)。
- 优点:平均情况下效率较高,时间复杂度为O(nlogn)。
- 缺点:最坏情况下效率较低,可能导致栈溢出。
public static void QuickSort(int[] nums)
{
QuickSort(nums, 0, nums.Length - 1);
}
private static void QuickSort(int[] nums, int low, int high)
{
if (low < high)
{
int pivotIndex = Partition(nums, low, high);
QuickSort(nums, low, pivotIndex - 1);
QuickSort(nums, pivotIndex + 1, high);
}
}
private static int Partition(int[] nums, int low, int high)
{
int pivot = nums[high];
int i = low - 1;
for (int j = low; j < high; j++)
{
if (nums[j] < pivot)
{
i++;
Swap(nums, i, j);
}
}
Swap(nums, i + 1, high);
return i + 1;
}
private static void Swap(int[] nums, int i, int j)
{
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
7. 堆排序(Heap Sort):
构建最大堆或最小堆,将堆顶元素与末尾元素交换并重新调整堆,重复此过程直到整个数组有序。时间复杂度为O(nlogn)。
- 优点:不受输入数据的初始状态影响,时间复杂度为O(nlogn)。
- 缺点:实现较为复杂,需要维护堆结构。
public static void HeapSort(int[] nums)
{
int n = nums.Length;
// 构建最大堆
for (int i = n / 2 - 1; i >= 0; i--)
{
Heapify(nums, n, i);
}
// 逐个将最大元素移到末尾,并调整堆
for (int i = n - 1; i >= 0; i--)
{
// 将当前最大元素(根节点)与末尾元素交换
int temp = nums[0];
nums[0] = nums[i];
nums[i] = temp;
// 调整堆,使剩余元素重新满足最大堆的性质
Heapify(nums, i, 0);
}
}
private static void Heapify(int[] nums, int n, int i)
{
int largest = i;
int leftChild = 2 * i + 1;
int rightChild = 2 * i + 2;
// 找到左子节点和右子节点中的最大值
if (leftChild < n && nums[leftChild] > nums[largest])
{
largest = leftChild;
}
if (rightChild < n && nums[rightChild] > nums[largest])
{
largest = rightChild;
}
// 如果最大值不是当前节点,则交换并继续调整堆
if (largest != i)
{
int temp = nums[i];
nums[i] = nums[largest];
nums[largest] = temp;
Heapify(nums, n, largest);
}
}
8. 计数排序(Counting Sort):
统计每个元素的出现次数,然后根据统计信息将元素放入正确的位置,实现线性时间复杂度O(n+k),其中k为元素的取值范围。
- 优点:适用于数据范围较小且重复值较多的情况,时间复杂度为O(n+k)。
- 缺点:需要额外的内存空间。
public static void CountingSort(int[] nums)
{
int n = nums.Length;
// 找到待排序数组的最大值和最小值
int minVal = int.MaxValue;
int maxVal = int.MinValue;
for (int i = 0; i < n; i++)
{
if (nums[i] < minVal)
{
minVal = nums[i];
}
if (nums[i] > maxVal)
{
maxVal = nums[i];
}
}
// 创建计数数组,用于记录每个元素的出现次数
int[] count = new int[maxVal - minVal + 1];
// 统计每个元素的出现次数
for (int i = 0; i < n; i++)
{
count[nums[i] - minVal]++;
}
// 根据计数数组重构排序后的数组
int index = 0;
for (int i = 0; i < count.Length; i++)
{
while (count[i] > 0)
{
nums[index] = i + minVal;
index++;
count[i]--;
}
}
}
9. 桶排序(Bucket Sort):
将元素分配到不同的桶中,对每个桶中的元素进行排序,然后按顺序合并各个桶的元素,时间复杂度取决于桶的个数和元素分布的情况。
- 优点:适用于数据分布均匀的情况,时间复杂度为O(n)。
- 缺点:对于数据分布不均匀的情况效率较低。
public static void BucketSort(float[] nums)
{
int n = nums.Length;
// 创建桶数组
List<float>[] buckets = new List<float>[n];
// 将元素分配到不同的桶中
for (int i = 0; i < n; i++)
{
int bucketIndex = (int)(n * nums[i]);
if (buckets[bucketIndex] == null)
{
buckets[bucketIndex] = new List<float>();
}
buckets[bucketIndex].Add(nums[i]);
}
// 对每个桶中的元素进行排序
for (int i = 0; i < n; i++)
{
if (buckets[i] != null)
{
buckets[i].Sort();
}
}
// 合并各个桶中的元素得到最终排序结果
int index = 0;
for (int i = 0; i < n; i++)
{
if (buckets[i] != null)
{
foreach (float num in buckets[i])
{
nums[index] = num;
index++;
}
}
}
}
10. 基数排序(Radix Sort):
根据元素的位值(个位、十位、百位等)进行排序,通过多次迭代和计数排序实现,时间复杂度取决于迭代次数和计数排序的时间复杂度。
- 优点:适用于整数排序,时间复杂度为O(nk),其中k为数字的位数。
- 缺点:需要额外的内存空间。
public static void RadixSort(int[] nums)
{
int n = nums.Length;
// 找到最大值,确定迭代次数
int maxVal = nums[0];
for (int i = 1; i < n; i++)
{
if (nums[i] > maxVal)
{
maxVal = nums[i];
}
}
// 进行基数排序
for (int exp = 1; maxVal / exp > 0; exp *= 10)
{
CountingSortByDigit(nums, n, exp);
}
}
private static void CountingSortByDigit(int[] nums, int n, int exp)
{
int[] output = new int[n];
int[] count = new int[10];
// 统计每个数字的出现次数
for (int i = 0; i < n; i++)
{
int digit = (nums[i] / exp) % 10;
count[digit]++;
}
// 计算累计次数
for (int i = 1; i < 10; i++)
{
count[i] += count[i - 1];
}
// 根据统计信息将元素放入正确的位置
for (int i = n - 1; i >= 0; i--)
{
int digit = (nums[i] / exp) % 10;
output[count[digit] - 1] = nums[i];
count[digit]--;
}
// 将排序后的结果复制回原数组
Array.Copy(output, 0, nums, 0, n);
}
每种排序算法都有自己的适用场景和性能特点,选择合适的排序算法取决于数据规模、数据分布以及对稳定性、内存占用等要求的考量。