十大排序算法的特点及应用场景
一.十大经典排序算法介绍
1. 冒泡排序(Bubble Sort)
原理:通过重复遍历要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复进行的,直到没有再需要交换的元素为止。
特点:简单但效率低,适合小数据量的排序。
2. 选择排序(Selection Sort)
原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
特点:不稳定排序,时间复杂度为O(n^2),但不需要额外的存储空间。
3. 插入排序(Insertion Sort)
原理:通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
特点:适合少量数据的排序,时间复杂度为O(n^2),但在部分已排序的数据集上效率较高。
4. 希尔排序(Shell Sort)
原理:是插入排序的一种更高效的改进版本。希尔排序通过将原来要排序的数组元素按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的元素越来越多,当增量减至1时,整个文件恰被分成一组,算法终止。
特点:是第一个突破O(n^2)的排序算法,但时间复杂度依赖于增量序列的选择。
5. 归并排序(Merge Sort)
原理:采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
特点:稳定排序,时间复杂度为O(n log n),但需要额外的存储空间。
6. 快速排序(Quick Sort)
原理:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
特点:平均时间复杂度为O(n log n),但在最坏情况下(如数组已经有序)时间复杂度为O(n^2)。
7. 堆排序(Heap Sort)
原理:是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子节点的键值或索引总是小于(或者大于)它的父节点。
特点:不稳定排序,时间复杂度为O(n log n),不需要额外的存储空间。
8. 计数排序(Counting Sort)
原理:不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
特点:稳定排序,时间复杂度为O(n+k),其中k是整数的范围。
9. 桶排序(Bucket Sort)
原理:将数组分到有限数量的桶里。每个桶再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。
特点:平均时间复杂度为O(n+k),其中k是桶的数量。
10. 基数排序(Radix Sort)
原理:是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。
特点:稳定排序,时间复杂度为O(nk),其中n是排序元素个数,k是数字位数。
每种排序算法都有其适用的场景和优缺点,选择合适的排序算法可以大大提高程序的效率。
二.算法效率对比
三.综合效率对比建议
1.如果数据量小,用哪种排序方法都可以,效率差别不大
2.如果数据量大;内存充裕建议用快速排序,内存不充裕建议用堆排序。
四.代码示例
1.快速排序c代码
#include <stdio.h>
#include <stdlib.h>
typedef struct _Range {
int start, end;
} Range;
Range new_Range(int s, int e) {
Range r;
r.start = s;
r.end = e;
return r;
}
void swap(int* x, int* y) {
int t = *x;
*x = *y;
*y = t;
}
void quick_sort(int arr[], const int len) {
if (len <= 0)
return; // 避免len等於負值時引發段錯誤(Segment Fault)
// r[]模擬列表,p為數量,r[p++]為push,r[--p]為pop且取得元素
Range r[len];
int p = 0;
r[p++] = new_Range(0, len - 1);
while (p) {
Range range = r[--p];
if (range.start >= range.end)
continue;
int mid = arr[(range.start + range.end) / 2]; // 選取中間點為基準點
int left = range.start, right = range.end;
do {
while (arr[left] < mid) ++left; // 檢測基準點左側是否符合要求
while (arr[right] > mid) --right; //檢測基準點右側是否符合要求
if (left <= right) {
swap(&arr[left], &arr[right]);
left++;
right--; // 移動指針以繼續
}
} while (left <= right);
if (range.start < right) r[p++] = new_Range(range.start, right);
if (range.end > left) r[p++] = new_Range(left, range.end);
}
}
int main() {
int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 };
int len = (int)sizeof(arr) / sizeof(*arr);
quick_sort(arr, len);
int i;
printf("start:\r\n");
for (i = 0; i < len; i++)
printf("%d ", arr[i]);
printf("\n");
return 0;
}
2.堆排序c代码
#include <stdio.h>
#include <stdlib.h>
void swap(int *a, int *b) {
int temp = *b;
*b = *a;
*a = temp;
}
void max_heapify(int arr[], int start, int end) {
// 建立父節點指標和子節點指標
int dad = start;
int son = dad * 2 + 1;
while (son <= end) { // 若子節點指標在範圍內才做比較
if (son + 1 <= end && arr[son] < arr[son + 1]) // 先比較兩個子節點大小,選擇最大的
son++;
if (arr[dad] > arr[son]) //如果父節點大於子節點代表調整完畢,直接跳出函數
return;
else { // 否則交換父子內容再繼續子節點和孫節點比較
swap(&arr[dad], &arr[son]);
dad = son;
son = dad * 2 + 1;
}
}
}
void heap_sort(int arr[], int len) {
int i;
// 初始化,i從最後一個父節點開始調整
for (i = len / 2 - 1; i >= 0; i--)
max_heapify(arr, i, len - 1);
// 先將第一個元素和已排好元素前一位做交換,再重新調整,直到排序完畢
for (i = len - 1; i > 0; i--) {
swap(&arr[0], &arr[i]);
max_heapify(arr, 0, i - 1);
}
}
int main() {
int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 };
int len = (int) sizeof(arr) / sizeof(*arr);
heap_sort(arr, len);
int i;
for (i = 0; i < len; i++)
printf("%d ", arr[i]);
printf("\n");
return 0;
}
参考资料:
https://www.runoob.com/w3cnote/ten-sorting-algorithm.html