前言
“排序” 很重要:
- 需求上需要实现排序:例如有一个排行榜要按玩家的等级进行排序;
- 是其他算法的基础:例如二分查找必须对有序的数组才有效。
排序算法很多,接下来就介绍十种经典排序算法:
冒泡排序
算法思想:相邻元素两两比较,每轮遍历得到一个最值
public void BubbleSort(int[] arr)
{
//优化点:若一次排序中未发生交换,则已是有序的,可直接返回
bool swapped = false;
for (int i = 0; i < arr.Length - 1; i++)
{
for (int j = 0; j < arr.Length - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
}
}
if (!swapped) return;
}
}
选择排序
算法思想:一个位置的元素与后续所有元素比较,遍历完毕后该位置上的元素就是最值
public void SelectionSort(int[] arr)
{
for (int i = 0; i < arr.Length - 1; i++)
{
int smallIndex = i;
for (int j = i + 1; j < arr.Length; j++)
{
if (arr[smallIndex] > arr[j])
smallIndex = j;
}
if (smallIndex != i)
{
int temp = arr[i];
arr[i] = arr[smallIndex];
arr[smallIndex] = temp;
}
}
}
插入排序
算法思想:和摸扑克牌一样,构造有序数组,每次都往里面插
public void InsertionSort(int[] arr)
{
for (int i = 1; i < arr.Length; i++)
{
int tempValue = arr[i];
for (int j = i - 1; j >= 0; j--)
{
if (tempValue < arr[j])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
else
break;
}
}
}
希尔排序
算法思想:将数组分为n/2,n/4,...直至1个子数组,每个数组各自用插入排序
public void ShellSort(int[] arr)
{
for (int gap = arr.Length / 2; gap >= 1; gap = gap / 2)
{
for (int i = 0; i < gap; i++)
{
for (int j = i + gap; j < arr.Length; j = j + gap)
{
int tempValue = arr[j];
for (int k = j - gap; k >= i; k = k - gap)
{
if (arr[k] > tempValue)
{
int temp = arr[k + gap];
arr[k + gap] = arr[k];
arr[k] = temp;
}
else
break;
}
}
}
}
}
归并排序
算法思想:分治法,将数组一分为二,左侧和右侧都是有序的,然后合并为一个有序的,递归实现
public List<int> MergeSort(int[] arr)
{
int mid = arr.Length / 2;
if (mid == 0)
{
return new List<int>() { arr[0] };
}
List<int> left = new List<int>();
List<int> right = new List<int>();
for (int i = 0; i < mid; i++)
left.Add(arr[i]);
for (int i = mid; i < arr.Length; i++)
right.Add(arr[i]);
left = MergeSort(left.ToArray());
right = MergeSort(right.ToArray());
return MergeList(left, right);
}
public List<int> MergeList(List<int> a, List<int> b)
{
List<int> temp = new List<int>();
while (a.Count > 0 && b.Count > 0)
{
if (a[0] < b[0])
{
temp.Add(a[0]);
a.RemoveAt(0);
}
else
{
temp.Add(b[0]);
b.RemoveAt(0);
}
}
if (a.Count > 0)
{
while (a.Count > 0)
{
temp.Add(a[0]);
a.RemoveAt(0);
}
}
if (b.Count > 0)
{
while (b.Count > 0)
{
temp.Add(b[0]);
b.RemoveAt(0);
}
}
return temp;
}
快速排序
算法思想:取一个基准值,将数组中小于与大于该基准的元素分别放于该基准值的两侧,同理再对已划分的两区域再取基准,再分布元素至其两侧
(分布两侧具体实现:左索引递增直至大于基准值,右索引递减直至小于基准值,交换两索引的元素,继续直至两索引相等)
public void QuickSort(int[] arr, int left = 0, int right = -1)
{
right = right == -1 ? arr.Length - 1 : right;
int midIndex = left;
if (left >= right)
return;
int leftIndex = left + 1;
int rightIndex = right;
while (leftIndex < rightIndex)
{
while (leftIndex < rightIndex && arr[leftIndex] < arr[midIndex])
leftIndex++;
while (leftIndex < rightIndex && arr[rightIndex] >= arr[midIndex])
rightIndex--;
swap(arr, leftIndex, rightIndex);
}
if (arr[leftIndex] < arr[midIndex])
{
swap(arr, midIndex, leftIndex);
midIndex = leftIndex;
}
else
{
swap(arr, midIndex, leftIndex - 1);
midIndex = leftIndex - 1;
}
QuickSort(arr, left, midIndex);
QuickSort(arr, midIndex + 1, right);
}
public void swap(int[] arr, int i, int j)
{
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
堆排序
算法思想:构造大顶堆/小顶堆,取最值与堆末尾元素互换,重构长度减一的堆,重复上述操作
(构造大顶堆:取根节点,其×2+1与×2为其孩子节点,取最大的值替换其根节点,从树的末尾arr/2依次这么操作)
public void HeapSort(int[] arr)
{
int[] tempTree = new int[arr.Length + 1];
for (int i = 0; i < arr.Length; i++)
{
tempTree[i + 1] = arr[i];
}
// 构造大顶堆
for (int j = arr.Length / 2; j > 0; j--)
{
Restore(tempTree, j, arr.Length);
}
for (int k = arr.Length; k > 1; k--)
{
int temp = tempTree[k];
tempTree[k] = tempTree[1];
tempTree[1] = temp;
Restore(tempTree, 1, k - 1);
}
for (int h = 0; h < arr.Length; h++)
{
arr[h] = tempTree[h + 1];
}
}
public void Restore(int[] arr, int root, int length)
{
if (root > length / 2) return;
int maxChildIndex = root * 2 + 1 > length ? root * 2 : (arr[root * 2] > arr[root * 2 + 1] ? root * 2 : root * 2 + 1);
if (arr[maxChildIndex] > arr[root])
{
int temp = arr[maxChildIndex];
arr[maxChildIndex] = arr[root];
arr[root] = temp;
root = maxChildIndex;
Restore(arr, root, length);
}
}
计数排序
算法思想:拿到数组中最大的值,构造一个长度为最大值的数组,每个元素都为0次,遍历待排序数组,给新数组对应的索引的值加1,最终遍历新数组反推出排序数组
桶排序
算法思想:其实算是计数排序的优化版,将一个数组分为n个桶,桶的分法要保证一个桶中的最小元素大于另一个桶中的最大元素,对每个桶内部单独进行排序,比如:按十位来分桶,每个桶内用计数排序
public int[] BucketSort(int[] arr, int bucketMax, int bucketCount)
{
List<List<int>> bucket = new List<List<int>>();
for (int i = 0; i < bucketCount; i++)
{
bucket.Add(new List<int>());
}
for (int j = 0; j < arr.Length; j++)
{
int index = Math.Min(arr[j] / (bucketMax / bucketCount), bucketCount - 1);
bucket[index].Add(arr[j]);
int tempValue = arr[j];
for (int k = bucket[index].Count - 2; k >= 0; k--)
{
if (bucket[index][k] > tempValue)
{
int temp = bucket[index][k];
bucket[index][k] = tempValue;
bucket[index][k + 1] = temp;
}
else
break;
}
}
List<int> result = new List<int>();
for (int m = 0; m < bucket.Count; m++)
{
result.AddRange(bucket[m].ToArray());
}
return result.ToArray();
}
基数排序
算法思想:其实也是桶排序的一种,桶的划分方式为基数(个位,十位,百位..)罢了
public int[] RadixSort(int[] arr, int max)
{
int radix = 0;
while (Math.Pow(10, radix) < max)
{
List<List<int>> bucket = new List<List<int>>();
for (int i = 0; i < 10; i++)
{
bucket.Add(new List<int>());
}
for (int j = 0; j < arr.Length; j++)
{
int index = arr[j] / (int)Math.Pow(10, radix) % 10;
bucket[index].Add(arr[j]);
}
List<int> temp = new List<int>();
for (int k = 0; k < bucket.Count; k++)
{
temp.AddRange(bucket[k].ToArray());
}
arr = temp.ToArray();
radix++;
}
return arr;
}