排序算法
1.冒泡排序
冒泡排序基本思想是通过重复地交换相邻元素,将较大的元素逐渐“冒泡”到数组的末尾,而较小的元素则逐渐“浮”到数组的前面。冒泡排序的核心思想是比较相邻的元素,如果它们的顺序不符合排序要求,则交换它们的位置,重复这个过程直到整个数组排序完成。
// 比较a和b,如果a小于b,则返回1,否则返回0
int IsSmaller(int a, int b)
{
if (a < b)
return 1;
else
return 0;
}
// 比较a和b,如果a大于b,则返回1,否则返回0
int IsBiggr(int a, int b)
{
if (a < b)
return 0;
else
return 1;
}
// 冒泡排序函数,使用传入的比较规则Rule对整数数组a进行排序
void BubbleSort(int *a, int len, int (*Rule)(int, int))
{
for (int i = 0; i < len - 1; i++)
{
int flag = 0; // 标志位,用于判断是否发生交换操作
for (int j = 0; j < len - i - 1; j++)
{
// 使用传入的比较规则Rule比较a[j]和a[j+1],根据规则决定是否交换它们的位置
if (Rule(a[j], a[j + 1]))
{
// 交换a[j]和a[j+1]的位置
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
flag = 1; // 设置标志位,表示发生了交换操作
}
}
// 如果一轮遍历中没有发生交换操作(即flag仍然为0),说明数组已经有序,可以提前结束排序
if (flag == 0)
{
break;
}
}
}
2.快速排序
快速排序的核心是分治策略,通过不断地划分子数组并排序,最终实现整个数组的排序。在每一轮划分中,选择基准元素,并根据基准元素将数组分为两部分。然后,递归地对左边部分和右边部分进行排序,直到子数组长度为1或0时停止递归。
// 快速排序函数,对整数数组a的子数组[start, end]进行排序
void Quicksort(int *a, int start, int end)
{
// 如果子数组只有一个元素或为空,无需排序,直接返回
if (start >= end)
{
return;
}
int temp = a[start]; // 选择第一个元素作为基准元素
int left = start; // 左指针
int right = end; // 右指针
while (left < right)
{
// 从右边开始找到第一个小于基准元素的元素的位置
while (left < right && a[right] >= temp)
{
right--;
}
if (left < right)
{
a[left] = a[right]; // 将找到的小于基准元素的元素移到左边
}
// 从左边开始找到第一个大于等于基准元素的元素的位置
while (left < right && a[left] < temp)
{
left++;
}
if (left < right)
{
a[right] = a[left]; // 将找到的大于等于基准元素的元素移到右边
}
}
a[left] = temp; // 将基准元素放到合适的位置,左边都小于它,右边都大于等于它
// 递归地对基准元素左边和右边的子数组进行排序
Quicksort(a, start, left - 1);
Quicksort(a, left + 1, end);
}
3.插入排序
插入排序的基本思想是维护一个已排序的子数组,初始时只包含第一个元素。然后,逐个将未排序的元素插入到已排序的子数组中,确保每次插入后子数组仍然有序。这个过程重复进行,直到整个数组都排好序。
对于直接插入排序,它从未排序的部分依次选择元素,然后在已排序的部分中找到正确的插入位置,最后插入元素。
对于折半插入排序,它使用二分查找来找到插入位置,从而减少了比较的次数,但仍然需要向后移动元素。
// 直接插入排序
void DirectInsertionSort(int *a, int len)
{
for (int i = 1; i < len; i++)
{
int current = a[i]; // 当前需要插入的元素
int j = i - 1; // 已排序部分的最后一个元素的索引
// 在已排序部分找到合适的插入位置
while (j >= 0 && a[j] > current)
{
a[j + 1] = a[j]; // 向后移动大于当前元素的元素
j--;
}
a[j + 1] = current; // 插入当前元素到正确的位置
}
}
// 折半插入排序
void BinaryInsertionSort(int *a, int len)
{
for (int i = 1; i < len; i++)
{
int current = a[i]; // 当前需要插入的元素
int left = 0; // 已排序部分的左边界
int right = i - 1; // 已排序部分的右边界
// 使用二分查找找到插入位置
while (left <= right)
{
int mid = left + (right - left) / 2; // 计算中间位置
if (a[mid] > current)
right = mid - 1; // 缩小右边界
else
left = mid + 1; // 缩小左边界
}
// 将元素插入到正确的位置
for (int j = i - 1; j >= left; j--)
{
a[j + 1] = a[j]; // 向后移动大于当前元素的元素
}
a[left] = current; // 插入当前元素到正确的位置
}
}
4.选择排序
选择排序的基本思想是维护一个已排序的子数组,初始时为空,然后在未排序的部分中找到最小(或最大)的元素,将其交换到已排序的子数组的末尾。这个过程不断重复,直到整个数组都排好序。
// 选择排序函数1,对整数数组a进行排序
void ChooseSort(int *a, int len)
{
for (int i = 0; i < len; i++)
{
int min = i; // 假设当前位置的元素是最小的
int Minum = a[min]; // 记录最小元素的值
// 在未排序的部分中找到最小的元素及其位置
for (int j = i + 1; j < len; j++)
{
if (a[j] < Minum)
{
Minum = a[j];
min = j;
}
}
// 将最小元素与当前位置的元素交换
int temp = a[i];
a[i] = Minum;
a[min] = temp;
}
}
// 选择排序函数2,对整数数组a进行排序
void ChooseSort2(int *a, int len)
{
int left = 0; // 待排序部分的左边界
int right = len - 1; // 待排序部分的右边界
while (left < right)
{
int min = left; // 假设左边界的元素是最小的
int max = right; // 假设右边界的元素是最大的
// 在未排序的部分中找到最小和最大的元素及其位置
for (int i = left; i <= right; i++)
{
if (a[i] < a[min])
min = i;
if (a[i] > a[max])
max = i;
}
// 将最小元素与左边界的元素交换
int temp = a[left];
a[left] = a[min];
a[min] = temp;
// 如果最大元素的位置与右边界不相同,将最大元素与右边界的元素交换
if (max != right)
{
temp = a[right];
a[right] = a[max];
a[max] = temp;
}
left++; // 更新左边界
right--; // 更新右边界
}
}
5.归并排序
归并排序采用了分治策略。它的基本思想是将待排序的数组分成两个较小的子数组,然后递归地对这两个子数组进行排序,最后将它们合并成一个有序的数组。归并排序包括两个主要步骤:分割(将数组分成两个子数组)和合并(将两个有序的子数组合并成一个有序的数组)。
// 合并两个已排序的子数组
void Merge(int *a, int left, int mid, int right)
{
int n1 = mid - left + 1; // 左子数组的长度
int n2 = right - mid; // 右子数组的长度
int L[n1], R[n2]; // 临时数组用于存储左右子数组的元素
// 将数据复制到临时数组 L[] 和 R[]
for (int i = 0; i < n1; i++)
{
L[i] = a[left + i];
}
for (int j = 0; j < n2; j++)
{
R[j] = a[mid + 1 + j];
}
int i = 0, j = 0, k = left;
// 合并临时数组 L[] 和 R[] 到原数组 a[]
while (i < n1 && j < n2)
{
if (L[i] <= R[j])
{
a[k] = L[i];
i++;
}
else
{
a[k] = R[j];
j++;
}
k++;
}
// 处理剩余的元素(如果有)
while (i < n1)
{
a[k] = L[i];
i++;
k++;
}
while (j < n2)
{
a[k] = R[j];
j++;
k++;
}
}
// 归并排序递归函数
void MergeSort(int *a, int left, int right)
{
if (left < right)
{
int mid = left + (right - left) / 2; // 找到中间索引
// 递归地对左半部分和右半部分进行归并排序
MergeSort(a, left, mid);
MergeSort(a, mid + 1, right);
// 合并两个子数组
Merge(a, left, mid, right);
}
}
6.希尔排序
希尔排序是一种改进的插入排序算法,其基本思想是通过分组排序的方式逐渐减小数组中元素之间的间隔(步长),从而将较小的元素尽早移动到合适的位置。希尔排序的关键思想是通过插入排序的方式,先排序间隔较远的元素,然后逐渐减小间隔,最终进行一次标准的插入排序。
// 对数组a的子数组进行插入排序,起始位置为pos,步长为step
void groupsort(int *a, int n, int pos, int step)
{
int temp, i, j;
// 从数组中的第pos+step个元素开始,每隔step个元素进行遍历
for (i = pos + step; i < n; i += step)
{
temp = a[i]; // 存储当前需要插入的元素
// 在已排序部分中,从当前位置向前寻找插入位置
for (j = i - step; j >= 0; j -= step)
{
// 如果已排序部分的元素大于temp,就将元素向后移动
if (a[j] <= temp)
{
break; // 找到了插入位置,退出循环
}
a[j + step] = temp; // 向后移动元素
}
}
}
// 希尔排序
void shellsort(int *a, int n)
{
if (n < 2)
{
return; // 如果数组长度小于2,无需排序,直接返回
}
int i, step;
// 初始步长为数组长度的一半,逐渐减小步长,直到步长为1
for (step = n / 2; step > 0; step /= 2)
{
// 对每个子数组进行插入排序,子数组的起始位置从0到step-1,步长为step
for (i = 0; i < step; i++)
{
groupsort(a, n, i, step);
}
}
}
7.堆排序
堆排序是一种基于二叉堆数据结构的排序算法。它的基本思想是首先将待排序的数组构建成一个最大堆(或最小堆),然后依次取出堆顶元素并重新调整堆,直到整个数组排序完成。
// 堆排序
void HeapSort(int *a, int n)
{
// 构建最大堆(将数组堆化)
for (int i = n / 2 - 1; i >= 0; i--)
{
Heapify(a, n, i);
}
// 依次取出堆顶元素并重新调整堆
for (int i = n - 1; i > 0; i--)
{
// 交换堆顶元素和当前最后一个元素
int temp = a[0];
a[0] = a[i];
a[i] = temp;
// 重新调整堆,排除已排序的部分
Heapify(a, i, 0);
}
}
// 将以根节点为i的子树堆化成最大堆
void Heapify(int *a, int n, int i)
{
int largest = i; // 初始化最大值为根节点
int left = 2 * i + 1; // 左子节点索引
int right = 2 * i + 2; // 右子节点索引
// 如果左子节点比根节点大,更新最大值
if (left < n && a[left] > a[largest])
{
largest = left;
}
// 如果右子节点比根节点大,更新最大值
if (right < n && a[right] > a[largest])
{
largest = right;
}
// 如果最大值不是根节点,交换根节点和最大值
if (largest != i)
{
int temp = a[i];
a[i] = a[largest];
a[largest] = temp;
// 递归调整被交换的子树
Heapify(a, n, largest);
}
}