排序算法简介
1、定义:将一组无序或部分有序的数据按照特定规则重新排列
2、分类:内部排序和外部排序
3、时间复杂度:O(n^2)、O(nlogn)、O(n)
=列表=
插入排序系列
1、直接插入排序:每次取未排序序列的第一个元素,在已排序序列中从后往前扫描,找到合适位置插入
#include <stdio.h>
void insertionSort(int arr[], int n) {
int i, key, j;
for (i = 1; i < n; i++) {
key = arr[i];
j = i - 1;
/* 将比 key 大的元素向右移动 */
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j = j - 1;
}
arr[j + 1] = key;
}
}
int main() {
int arr[] = {12, 11, 13, 5, 6};
int n = sizeof(arr) / sizeof(arr[0]);
insertionSort(arr, n);
printf("排序后的数组:\n");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
2、希尔排序:对直接插入排序的改进,先把要排序的一组数按某个增量d分成若干组,对每组进行直接插入排序
#include <stdio.h>
void shellSort(int arr[], int n) {
int gap, i, j, temp;
for (gap = n / 2; gap > 0; gap /= 2) {
for (i = gap; i < n; i++) {
temp = arr[i];
for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
arr[j] = arr[j - gap];
}
arr[j] = temp;
}
}
}
int main() {
int arr[] = {12, 11, 13, 5, 6};
int n = sizeof(arr) / sizeof(arr[0]);
shellSort(arr, n);
printf("排序后的数组:\n");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
=列表=
选择排序系列
1、直接选择排序:每次选择未排序序列中最小(大)的数放在已排序序列的末尾
#include <stdio.h>
void selectionSort(int arr[], int n) {
int i, j, min_idx;
for (i = 0; i < n - 1; i++) {
min_idx = i;
for (j = i + 1; j < n; j++) {
if (arr[j] < arr[min_idx]) {
min_idx = j;
}
}
/* 将找到的最小元素交换到已排序部分的末尾 */
int temp = arr[min_idx];
arr[min_idx] = arr[i];
arr[i] = temp;
}
}
int main() {
int arr[] = {12, 11, 13, 5, 6};
int n = sizeof(arr) / sizeof(arr[0]);
selectionSort(arr, n);
printf("排序后的数组:\n");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
2、堆排序:利用二叉堆这种数据结构来实现选择排序
#include <stdio.h>
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
void heapify(int arr[], int n, int i) {
int largest = i;
int l = 2 * i + 1;
int r = 2 * i + 2;
if (l < n && arr[l] > arr[largest]) {
largest = l;
}
if (r < n && arr[r] > arr[largest]) {
largest = r;
}
if (largest != i) {
swap(&arr[i], &arr[largest]);
heapify(arr, n, largest);
}
}
void heapSort(int arr[], int n) {
int i;
for (i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i);
}
for (i = n - 1; i >= 0; i--) {
swap(&arr[0], &arr[i]);
heapify(arr, i, 0);
}
}
int main() {
int arr[] = {12, 11, 13, 5, 6};
int n = sizeof(arr) / sizeof(arr[0]);
heapSort(arr, n);
printf("排序后的数组:\n");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
交换排序系列
1、冒泡排序:反复交换相邻的未按次序排列的元素
#include <stdio.h>
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
void bubbleSort(int arr[], int n) {
int i, j;
for (i = 0; i < n - 1; i++) {
for (j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
swap(&arr[j], &arr[j + 1]);
}
}
}
}
int main() {
int arr[] = {12, 11, 13, 5, 6};
int n = sizeof(arr) / sizeof(arr[0]);
bubbleSort(arr, n);
printf("排序后的数组:\n");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
2、快速排序:利用分治思想,选一个基准值,将小于它的放在左边,大于它的放在右边,在对左右两边递归进行快速排序
#include <stdio.h>
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int partition(int arr[], int low, int high) {
int pivot = arr[high];
int i = (low - 1);
for (int j = low; j <= high - 1; j++) {
if (arr[j] < pivot) {
i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]);
return (i + 1);
}
void quickSort(int arr[], int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
int main() {
int arr[] = {12, 11, 13, 5, 6};
int n = sizeof(arr) / sizeof(arr[0]);
quickSort(arr, 0, n - 1);
printf("排序后的数组:\n");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
归并排序系列
1、二路归并排序:将待排记录序列分成若干个长度为1的子序列,两两合并形成有序序列
#include <stdio.h>
#include <stdlib.h>
void merge(int arr[], int left[], int left_size, int right[], int right_size) {
int i = 0, j = 0, k = 0;
while (i < left_size && j < right_size) {
if (left[i] <= right[j]) {
arr[k++] = left[i++];
} else {
arr[k++] = right[j++];
}
}
while (i < left_size) {
arr[k++] = left[i++];
}
while (j < right_size) {
arr[k++] = right[j++];
}
}
void mergeSort(int arr[], int n) {
if (n <= 1) {
return;
}
int mid = n / 2;
int *left = (int *) malloc(mid * sizeof(int));
int *right = (int *) malloc((n - mid) * sizeof(int));
for (int i = 0; i < mid; i++) {
left[i] = arr[i];
}
for (int j = 0; j < n - mid; j++) {
right[j] = arr[mid + j];
}
mergeSort(left, mid);
mergeSort(right, n - mid);
merge(arr, left, mid, right, n - mid);
free(left);
free(right);
}
int main() {
int arr[] = {12, 11, 13, 5, 6};
int n = sizeof(arr) / sizeof(arr[0]);
mergeSort(arr, n);
printf("排序后的数组:\n");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
2、多路归并排序:将待排记录序列划分成多个子序列进行排序,再将排好序的子序列合并成最终序列
#include <stdio.h>
#include <stdlib.h>
void merge(int *arr[], int k, int result[]) {
int i, j, min_idx;
for (i = 0; i < k; i++) {
if (*arr[i] != -1) {
min_idx = i;
break;
}
}
for (j = i + 1; j < k; j++) {
if ((*arr[j] < *arr[min_idx]) && (*arr[j] != -1)) {
min_idx = j;
}
}
if (*arr[min_idx] == -1) {
return;
}
result[0] = *arr[min_idx];
arr[min_idx]++;
merge(arr, k, result + 1);
}
void mergeSort(int *arr[], int low, int high, int k, int result[]) {
if (low == high) {
result[0] = *arr[low];
return;
}
if (low > high) {
return;
}
int mid = (low + high) / 2;
int *temp1 = (int *) malloc(k * sizeof(int));
int *temp2 = (int *) malloc(k * sizeof(int));
mergeSort(arr, low, mid, k, temp1);
mergeSort(arr, mid + 1, high, k, temp2);
merge((int *[]) {temp1, temp2}, 2, result);
free(temp1);
free(temp2);
}
int main() {
int k = 3; // 多路归并数
int n = 10; // 数组长度
int *arr[k];
for (int i = 0; i < k; i++) {
arr[i] = (int *) malloc(n / k * sizeof(int));
for (int j = 0; j < n / k; j++) {
arr[i][j] = rand() % 100; // 随机生成10以内的整数
}
}
int result[n];
mergeSort(arr, 0, k - 1, n / k, result);
printf("排序后的数组:\n");
for (int i = 0; i < n; i++) {
printf("%d ", result[i]);
}
for (int i = 0; i < k; i++) {
free(arr[i]);
}
return 0;
}
=列表=
快速排序系列
1、普通快排:以数组最后一个元素作为基准,然后一趟排序将待排序列分割成两部分
#include <stdio.h>
#include <stdlib.h>
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int partition(int arr[], int low, int high) {
int pivot = arr[high];
int i = (low - 1);
for (int j = low; j <= high - 1; j++) {
if (arr[j] < pivot) {
i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]);
return (i + 1);
}
void quickSort(int arr[], int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
int main() {
int arr[] = {12, 11, 13, 5, 6};
int n = sizeof(arr) / sizeof(arr[0]);
quickSort(arr, 0, n - 1);
printf("排序后的数组:\n");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
2、三路快排:将待排序列分成三个部分:小于基准值、等于基准值、大于基准值
#include <stdio.h>
#include <stdlib.h>
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
void threeWayQuickSort(int arr[], int low, int high) {
if (low >= high) {
return;
}
int lt = low, gt = high, i = low + 1;
int pivot = arr[low];
while (i <= gt) {
if (arr[i] < pivot) {
swap(&arr[lt++], &arr[i++]);
} else if (arr[i] > pivot) {
swap(&arr[i], &arr[gt--]);
} else {
i++;
}
}
threeWayQuickSort(arr, low, lt - 1);
threeWayQuickSort(arr, gt + 1, high);
}
int main() {
int arr[] = {2, 1, 2, 5, 0, 5, 8, 7};
int n = sizeof(arr) / sizeof(arr[0]);
threeWayQuickSort(arr, 0, n - 1);
printf("排序后的数组:\n");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
=列表=
排序算法优化
1、时间复杂度上的优化
排序优化算法可以有效地提高排序算法的效率,其中比较常见的包括桶排序、计数排序和基数排序。以下是它们的详细介绍以及C语言实现示例:
- 桶排序
桶排序算法将元素分配到不同的桶中,并在每个桶内部使用其他排序算法(如插入排序)对元素进行排序。桶的数量通常与输入元素数量相同,并且预先知道元素范围,以便确定每个元素应该属于哪个桶。
时间复杂度:O(n + k),其中k为桶的数量。
代码:
#include <stdio.h>
#include <stdlib.h>
void bucketSort(int arr[], int n) {
int max_val = arr[0];
for (int i = 1; i < n; ++i) {
if (arr[i] > max_val) {
max_val = arr[i];
}
}
int *buckets = (int *) malloc((max_val + 1) * sizeof(int));
for (int i = 0; i <= max_val; ++i) {
buckets[i] = 0;
}
for (int i = 0; i < n; ++i) {
++buckets[arr[i]];
}
int i = 0;
for (int j = 0; j <= max_val; ++j) {
while (buckets[j]) {
arr[i++] = j;
--buckets[j];
}
}
free(buckets);
}
int main() {
int arr[] = {5, 2, 9, 4, 7, 6, 1, 3, 8};
int n = sizeof(arr) / sizeof(int);
bucketSort(arr, n);
printf("排序后的数组:\n");
for (int i = 0; i < n; ++i) {
printf("%d ", arr[i]);
}
return 0;
}
在上面的代码中,我们首先找到输入元素的最大值,并使用它来确定需要多少个桶。然后,我们初始化每个桶的计数器为0,并将每个元素分配到其对应的桶中。接着,我们遍历每个桶中的元素,并将它们放回原始数组中,即可得到已排序的数组。
- 计数排序
计数排序算法通过统计每个元素出现的次数来进行排序,并使用前缀和技术来确定每个元素的最终位置。与桶排序不同,计数排序仅仅适用于非负整数。
时间复杂度:O(n + k),其中k为元素范围。
代码:
#include <stdio.h>
#include <stdlib.h>
void countingSort(int arr[], int n) {
int max_val = arr[0];
for (int i = 1; i < n; ++i) {
if (arr[i] > max_val) {
max_val = arr[i];
}
}
int *count = (int *) malloc((max_val + 1) * sizeof(int));
for (int i = 0; i <= max_val; ++i) {
count[i] = 0;
}
for (int i = 0; i < n; ++i) {
++count[arr[i]];
}
for (int i = 1; i <= max_val; ++i) {
count[i] += count[i - 1];
}
int *result = (int *) malloc(n * sizeof(int));
for (int i = n - 1; i >= 0; --i) {
result[--count[arr[i]]] = arr[i];
}
for (int i = 0; i < n; ++i) {
arr[i] = result[i];
}
free(count);
free(result);
}
int main() {
int arr[] = {5, 2, 9, 4, 7, 6, 1, 3, 8};
int n = sizeof(arr) / sizeof(int);
countingSort(arr, n);
printf("排序后的数组:\n");
for (int i = 0; i < n; ++i) {
printf("%d ", arr[i]);
}
return 0;
}
在上面的代码中,我们首先找到输入元素的最大值,并使用它来确定需要多少个桶。然后,我们初始化每个计数器为0,并将每个元素的计数器加1。接着,我们使用前缀和技术计算出每个元素在已排序数组中的最终位置,并将每个元素放入结果数组中。最后,我们将结果数组复制回原始数组中。
- 基数排序
基数排序算法按照数字位(例如个位、十位、百位等)进行排序,具体而言,我们首先按照最低位进行排序,然后按照次低位进行排序,以此类推,直到所有数字位都排好序。
时间复杂度:O(d * n),其中d为最大数字位数,n为元素数量。
代码:
#include <stdio.h>
#include <stdlib.h>
void radixSort(int arr[], int n) {
int max_val = arr[0];
for (int i = 1; i < n; ++i) {
if (arr[i] > max_val) {
max_val = arr[i];
}
}
for (int exp = 1; max_val / exp > 0; exp *= 10) {
int *count = (int *) malloc(10 * sizeof(int));
for (int i = 0; i < 10; ++i) {
count[i] = 0;
}
for (int i = 0; i < n; ++i) {
++count[(arr[i] / exp) % 10];
}
for (int i = 1; i < 10; ++i) {
count[i] += count[i - 1];
}
int *result = (int *) malloc(n * sizeof(int));
for (int i = n - 1; i >= 0; --i) {
result[--count[(arr[i] / exp) % 10]] = arr[i];
}
for (int i = 0; i < n; ++i) {
arr[i] = result[i];
}
free(count);
free(result);
}
}
int main() {
int arr[] = {329, 457, 657, 839, 436, 720, 355};
int n = sizeof(arr) / sizeof(int);
radixSort(arr, n);
printf("排序后的数组:\n");
for (int i = 0; i < n; ++i) {
printf("%d ", arr[i]);
}
return 0;
}
在上面的代码中,我们首先找到输入元素的最大值,并使用它来确定需要遍历多少个数字位(例如个位、十位等)。然后,对于每个数字位,我们初始化一个计数器数组,并将每个元素分配到其对应的计数器中。接着,我们使用前缀和技术计算出每个元素在已排序数组中的最终位置,并将每个元素放入结果数组中。最后,我们将结果数组复制回原始数组中。
2、空间复杂度上的优化:原地排序
原地排序是指在排序过程中只使用原始输入数组所占用的空间,而不需要额外申请存储空间。这种方法在排序大数据集时非常有用,因为它可以避免占用过多的内存空间。
代码:
#include <stdio.h>
void swap(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int partition(int arr[], int low, int high)
{
int pivot = arr[high];
int i = (low - 1);
for (int j = low; j <= high- 1; j++)
{
if (arr[j] < pivot)
{
i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]);
return (i + 1);
}
void quickSort(int arr[], int low, int high)
{
if (low < high)
{
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
int main()
{
int arr[] = {10, 7, 8, 9, 1, 5};
int n = sizeof(arr) / sizeof(arr[0]);
quickSort(arr, 0, n - 1);
printf("Sorted array: ");
for (int i=0; i < n; i++)
printf("%d ", arr[i]);
printf("\n");
return 0;
}
3、常数级别的优化:插入排序优化、快速排序的三数取中
- 插入排序优化
插入排序是一个简单而有效的排序算法,但是当待排序数组规模较大时,它的效率会降低。一种常见的优化方式是,在每次插入操作之前,使用二分查找来寻找插入位置。
以下是带有二分查找优化的插入排序的C语言实现:
void binaryInsertionSort(int arr[], int n)
{
for (int i = 1; i < n; i++)
{
int key = arr[i];
int j = i - 1;
int pos = binarySearch(arr, key, 0, j);
while (j >= pos)
{
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}
int binarySearch(int arr[], int key, int left, int right)
{
while (left <= right)
{
int mid = left + (right - left) / 2;
if (arr[mid] == key)
return mid + 1;
else if (arr[mid] < key)
left = mid + 1;
else
right = mid - 1;
}
return left;
}
在这个例子中,binaryInsertionSort() 函数使用二分查找来确定插入位置,从而减少了比较的次数。binarySearch() 函数实现了二分查找算法。
- 快速排序的三数取中
快速排序是一种常用的排序算法,但它在某些情况下可能会出现最坏时间复杂度 O(n²)。为了避免这种情况,可以使用三数取中的方法来选择枢轴元素。
以下是使用三数取中优化的快速排序的C语言实现:
void quickSort(int arr[], int low, int high)
{
if (low < high)
{
int pivotIndex = getPivotIndex(arr, low, high);
int pi = partition(arr, low, high, pivotIndex);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
int getPivotIndex(int arr[], int low, int high)
{
int mid = (low + high) / 2;
if (arr[low] > arr[mid])
swap(&arr[low], &arr[mid]);
if (arr[low] > arr[high])
swap(&arr[low], &arr[high]);
if (arr[mid] > arr[high])
swap(&arr[mid], &arr[high]);
return mid;
}
在这个例子中,getPivotIndex() 函数使用三数取中的方式来确定枢轴元素。它首先计算数组的中间位置,然后选取该位置、第一个位置和最后一个位置上的元素,并将它们按从小到大的顺序排列。最后,它返回中间位置上的元素作为枢轴元素。通过这种优化方式,可以使快速排序的效率更加稳定,并且避免了最坏情况的出现。
希望这篇博客对各位大佬有所帮助,有任何的不足希望大佬多多指教,小编会积极纠正哦!