三、选择排序
1. 简单选择排序
在遍历序列时,找到其中最小的值,其最终位置应该在序列的最前面(升序)。
据此,可以在每一趟中选取当前趟的子序列中最小的元素,与子序列的首元素交换。子序列之前的序列经前几趟的选择,已经有序。当剩余子序列为空时,排序完成。(其实为1时就可以结束)
void SelectSort(int nums[], int n)
{
for(int i = 0;i < n - 1;i++) { // 每趟选择
int min = i; // 记录最小下标
for(int j = i + 1;j < n;j++){ // 遍历子序列
if(nums[j] < nums[min]){ // 更新最小下标
min = j;
}
}
Swap(&nums[min], &nums[i]);
}
}
时间复杂度:O( n2)
空间复杂度:O(1)
稳定性:不稳定
2. 堆排序
堆是一种特殊的数据结构。可以看作一棵由数组表示的完全二叉树。该完全二叉树有以下特点:
- 根节点的值大于(或小于)其余所有节点的值(分为大根堆和小根堆)
- 左右子树也是堆
根据堆的性质,可以快速选择出一个最大或最小值,移动至序列的正确位置。
// 注意:堆排序的序列与完全二叉树对应,因此必须将序列变为下标从1开始
void HeapSort(int nums[], int n)
{
int heap[n + 1]; // 数组化为堆数组
for(int i = 0;i < n;i++) {
heap[i + 1] = nums[i];
}
BuildMaxHeap(heap, n); // 建堆
for(int i = n;i > 1;i--) { // 选择堆顶元素进行排序
Swap(&heap[i], &heap[1]); // 将堆顶与堆尾进行对换(最大元素移到最终位置)
HeapAdjust(heap, 1, i - 1); // 堆调整,使重新成堆
}
for(int i = 0;i < n;i++) {
nums[i] = heap[i + 1]; // 堆数组写回数组
}
}
void BuildMaxHeap(int heap[], int n) // 建大根堆
{
for(int i = n / 2;i >0;i--) { // 对所有非叶子结点做一次调整,可形成堆
HeapAdjust(heap, i, n);
}
}
void HeapAdjust(int heap[], int k, int n) // 堆调整
{
heap[0] = heap[k]; // 调整元素暂存至0位置(留空)
for(int i = 2 * k;i <= n;i *= 2) { // 在子树中筛选
if(i < n && heap[i] < heap[i + 1]) { // 选中较大的子结点
i++;
}
if(heap[i] <= heap[0]) {
break; // 调整时,子树认为已经是堆,若0位置比子树的根大,则已经是最大,无需筛选
}
else { // 若查到更大的元素,则交换,并继续向下筛选(0位置的交换可以等到筛选完成后填入)
heap[k] = heap[i];
k = i;
}
}
heap[k] = heap[0]; // k记录最后一个交换的位置,其值为被交换下来的原k位置,暂存0位置的值
}
时间复杂度:O(n·log n)
空间复杂度:O(1)
稳定性:不稳定
四、归并排序
1. 二路归并排序
已知两个有序序列,可以在线性时间O(n)下完成这两个序列的合并。
利用分治思想,将序列划分为两个子序列,子序列先有序化后再进行原序列的有序化,子序列可再划分,由此递归可以完成排序。
// copyNums为辅助数组空间
void BiMergeSort(int nums[], int n)
{
int copyNums[n];
BiMergeSortRec(nums, 0, n - 1, copyNums);
}
void BiMergeSortRec(int nums[], int low, int high, int copyNums[]) // 归并递归函数
{
if(low < high) {
int mid = (low + high) / 2;
BiMergeSortRec(nums, low, mid, copyNums);
BiMergeSortRec(nums, mid + 1, high, copyNums);
BiMerge(nums, low, mid, high, copyNums);
}
}
void BiMerge(int nums[], int low, int mid, int high, int copyNums[]) // 单次归并
{
int i, j, k;
for(k = low;k <= high;k++) {
copyNums[k] = nums[k]; // 将需要归并的整个区间拷贝到辅助数组
}
for(i = low, j = mid + 1, k = low;i <= mid && j <= high;k++) {
// 设有三个指针,i遍历左侧区间序列,j遍历右侧,k遍历原数组(按序填入)
if(copyNums[i] <= copyNums[j]) { // =条件保证稳定性
nums[k] = copyNums[i++];
}
else {
nums[k] = copyNums[j++];
}
}
// 以下将未遍历完的序列填入,两个while只会执行一个
while(i <= mid) {
nums[k++] = copyNums[i++];
}
while(j <= high) {
nums[k++] = copyNums[j++];
}
}
时间复杂度:O(n·log n)
空间复杂度:O(n)
稳定性:稳定