十大经典排序算法都是基于数组和链表数据结构实现。包括:
1、冒泡排序
2、选择排序
3、插入排序
4、希尔排序
5、归并排序
6、快速排序
7、堆排序
8、计数排序
9、桶排序
10、基数排序
排序算法分为内部排序和外部排序。内部排序指数据可以全部加载在内存中,且可以在内存中进行排序。而外部排序因为数据量非常大,不能一次全部加载到内存中,需要依靠磁盘等持久化存储介质。
关于时间复杂度和空间复杂度
关于时间复杂度可以用主定理进行严格推导。
比如二叉树遍历-前序、中序、后序时间复杂度为O(n),n表示二叉树节点个数。为什么是O(n),因为每个节点都遍历且仅遍历一次。
图的遍历:时间复杂度是多少?O(n),n是图的节点总数。
搜索算法:DFS、BFS时间复杂度是多少?O(n),n是搜索空间节点总数。
平方阶 (O(n2)) 排序:各类简单排序:直接插入、直接选择和冒泡排序。
线性对数阶 (O(nlog2n)) 排序:快速排序、堆排序和归并排序;
O(n1+§)) 排序:§ 是介于 0 和 1 之间的常数。 希尔排序
线性阶 (O(n)) 排序:基数排序,此外还有桶、箱排序。
关于稳定性
稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序。
不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。
名词解释:
n:数据规模
k:"桶"的个数
In-place:占用常数内存,不占用额外内存
Out-place:占用额外内存
稳定性:排序后 2 个相等键值的顺序和排序之前它们的顺序相同
以下是十大排序算法的时间复杂度和空间复杂度
冒泡排序
1.算法步骤
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
2.动态演示
3.什么时候最快
当输入的数据已经是正序时(都已经是正序了,我还要你冒泡排序有何用啊)。
4.什么时候最慢
当输入的数据是反序时(写一个 for 循环反序输出数据不就行了,干嘛要用你冒泡排序呢,我是闲的吗)。
5.实现方式
5.1 Java实现
public class BubbleSort {
public int [] sort(int [] sourceArray) throws Exception {
int [] arr = Arrays.copyOf(sourceArray, sourceArray.length);
for(int i = 1; i < arr.length; i++) {
// 设定一个标记,若为true,则表示此次循环没有进行交换,也就是待排序列已经有序,排序已经完成。
boolean flag = true;
for( int j = 0; j < arr.length - 1; j++) {
if(arr[j] > arr[j + 1]){
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flag = false;
}
}
if(flag) {
break;
}
}
return arr;
}
}
5.2 C++实现
#include <iostream>
using namespace std;
template <typename T>
void bubble_sort(T arr[], int len) {
int i, j;
for (i = 0; i < len - 1; i++) {
for (j = 0; j < len - 1; j++) {
if (arr[j] > arr[j + 1])
swap(arr[j], arr[j + 1]);
}
}
}
int main()
{
int arr[] = { 61, 17, 29, 22, 34, 60, 72, 21, 50, 1, 62 };
int len = (int)sizeof(arr) / sizeof(*arr);
bubble_sort(arr, len);
for (int i = 0; i < len; i++) {
cout << arr[i] << ' ';
}
cout << endl;
float arrf[] = { 17.5, 19.1, 0.6, 1.9, 10.5, 12.4, 3.8, 19.7, 1.5, 25.4, 28.6, 4.4, 23.8, 5.4 };
len = (float) sizeof(arrf) / sizeof(*arrf);
bubble_sort(arrf, len);
for (int i = 0; i < len; i++) {
cout << arrf[i] << ' ' << endl;
}
return 0;
}
选择排序
1.算法步骤
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
重复第二步,直到所有元素均排序完毕。、
2.动图演示
3.实现方式
3.1 java实现
public class SelectionSort {
public int [] sort (int [] sourceArray) throws Exception {
int [] arr = Arrays.copyOf(sourceArray, sourceArray.length);
//总共要经过N-1轮比较
for( int i = 0; i < arr.length - 1; i++) {
int min = i;
//每轮需要比较的次数N-i
for(int j = i + i; j < arr.length; j++) {
if(arr[j] < arr[min]) {
//记录目前能找到的最小元素的下标
min = j;
}
}
//将找到的最小值和i位置所在的值进行交换
if(i != min) {
int tmp = arr[i];
arr[i] = arr[min];
arr[min] = tmp;
}
}
return arr;
}
}
3.2 C++实现
template <typename S>
void selection_sort(std::vector<S>& arr) {
for (int i = 0; i < arr.size() - 1; i++) {
int min = i;
for (int j = i + 1; j < arr.size(); j++)
if (arr[j] < arr[min])
min = j;
std::swap(arr[i], arr[min]);
}
}
插入排序
1. 算法步骤
插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。
将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。),这个与打扑克一样的道理。
2. 动图演示
3.实现方式
3.1 Java实现
public class InsertSort {
public int[] sort(int [] sourceArray) throws Exception {
对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
//从下标为1的元素开始选择合适的位置插入,因为下标为0的只有一个元素,默认是有序的
for(int i = 1; i < arr.length ; i ++) {
//记录要插入的数据
int tmp = arr[i];
//从已经排序的序列最右边的开始比较,找到比其小的数
int j = i;
while (j > 0 && tmp < arr[j -1]) {
arr[j] = arr[j -1];//移动到后一个值,当前的值保存在tmp中
j --;//并记录要插入的位置
}
//存在比其小的数,插入
if(j != i) {
arr[j] = tmp;
}
}
return arr;
}
public static void main(String [] args) throws Exception {
InsertSort sort = new InsertSort();
sort.sort(new int[] {10,20,24,265,23});
}
}
3.2 C++实现
void insert_sort(T arr[], int len) {
for (int i = 1; i < len; i++) {
int tmp = (int)arr[i];
int j = i - 1;
while (j >= 0 && tmp < arr[j]) {
arr[j + 1] = arr[j];//往后移
j--;
}
arr[j + 1] = tmp;
}
}
希尔排序
希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。
1. 算法步骤
选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
按增量序列个数 k,对序列进行 k 趟排序;
每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
2. 动图演示
3.实现方式
3.1 Java实现
public class ShellSort implements IArraySort {
@Override
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
int gap = 1;
while (gap < arr.length) {
gap = gap * 3 + 1;
}
while (gap > 0) {
for (int i = gap; i < arr.length; i++) {
int tmp = arr[i];
int j = i - gap;
while (j >= 0 && arr[j] > tmp) {
arr[j + gap] = arr[j];
j -= gap;
}
arr[j + gap] = tmp;
}
gap = (int) Math.floor(gap / 3);
}
return arr;
}
}
3.2 C++实现
template<typename T>
void shell_sort(T array[], int length) {
int h = 1;
while (h < length / 3) {
h = 3 * h + 1;
}
while (h >= 1) {
for (int i = h; i < length; i++) {
for (int j = i; j >= h && array[j] < array[j - h]; j -= h) {
std::swap(array[j], array[j - h]);
}
}
h = h / 3;
}
}
归并排序
1.算法步骤
归并法是采用分治法的一个非常典型的应用。算法思路如下。
- 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
- 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
- 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
- 重复步骤 3 直到某一指针达到序列尾;
- 将另一序列剩下的所有元素直接复制到合并序列尾。
2.动图演示
3.实现方式
3.1 Java实现
public class MergeSort {
public int [] sort(int [] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int [] arr = Arrays.copyOf(sourceArray, sourceArray.length);
if(arr.length < 2) {
return arr;
}
int mid = (int) Math.floor(arr.length/2);
int [] left = Arrays.copyOfRange(arr, 0, mid);
int [] right = Arrays.copyOfRange(arr, mid, arr.length);
return merge(sort(left), sort(right));
}
private int[] merge(int[] left, int[] right) {
int [] result = new int[left.length + right.length];
int i = 0;
while (left.length > 0 && right.length >0) {
if(left[0] < right[0]) {
result[i++] = left[0];
left = Arrays.copyOfRange(left, 1, left.length);
} else {
result[i++] = right[0];
right = Arrays.copyOfRange(right, 1, right.length);
}
}
while (left.length > 0) {
result[i++] = left[0];
left = Arrays.copyOfRange(left, 1, left.length);
}
while (right.length > 0) {
result[i++] = right[0];
right = Arrays.copyOfRange(right, 1, right.length);
}
return result;
}
public static void main(String [] args) throws Exception {
MergeSort sort = new MergeSort();
int [] rs = sort.sort(new int[] {10,9,24,265,23});
}
}
3.2 C++实现
template<typename T>
void merge_sort(T arr[], int len) {
T *a = arr
T *b = new T[len];
for (int seg = 1; seg < len; seg += seg) {
for (int start = 0; start < len; start += seg + seg) {
int low = start, mid = mid(start + seg, len), high = min(start + seg + seg, len);
int k = low;
int start1 low, end1 = mid;
int start2 mid, end2 = high;
while (start1 < end1 && start2 < end2)
{
b[k++] = a[start1] < a[start2] ? a[start++] : a[start2++];
}
while (start1 < end1)
{
b[k++] = a[start1++];
}
while (start2 < end2)
{
b[k++] = a[start2++];
}
T* temp = a;
a = b;
b = temp;
}
if (a != arr) {
for (int i = 0; i < len; i++) {
b[i] = a[i];
}
b = a;
}
delete[] b;
}
}
快速排序
快速排序使用分治法策略来把一个串行分为两个子串行,本质上来看,快速排序应该算是在冒泡排序的基础上递归分治法。快速排序就是快。
虽然快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。
1.算法步骤
从数列中挑出一个元素,称为 “基准”(pivot);
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
2. 动图演示
3.1 java实现
public class QuickSort {
public int [] sort(int [] sourceArray) throws Exception {
//对arr进行拷贝,不改变参数内容
int [] arr = Arrays.copyOf(sourceArray, sourceArray.length);
return quickSort(arr, 0 , arr.length -1);
}
private int[] quickSort(int[] arr, int left, int right) {
if(left < right) {
int partitionIndex = partition(arr, left, right);
quickSort(arr, left, partitionIndex -1);
quickSort(arr, partitionIndex + 1, right);
}
return arr;
}
private int partition(int[] arr, int left, int right) {
//设定基准值
int pivot = left;
int index = pivot + 1;
for( int i = index ; i <= right ; i++) {
if(arr[i] < arr[pivot]) {
swap(arr, i , index);
index++;
}
}
swap(arr, pivot, index -1);
return index - 1;
}
private void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
public static void main(String [] args) throws Exception {
QuickSort sort = new QuickSort();
int [] rs = sort.sort(new int[] {10,9,24,265,23});
}
}
3.2 C++实现
// 参考:http://www.dutor.net/index.php/2011/04/recursive-iterative-quick-sort/
struct Range {
int start, end;
Range(int s = 0, int e = 0) {
start = s, end = e;
}
};
template <typename T> // 整數或浮點數皆可使用,若要使用物件(class)時必須設定"小於"(<)、"大於"(>)、"不小於"(>=)的運算子功能
void quick_sort(T arr[], const int len) {
if (len <= 0)
return; // 避免len等於負值時宣告堆疊陣列當機
// r[]模擬堆疊,p為數量,r[p++]為push,r[--p]為pop且取得元素
Range r[len];
int p = 0;
r[p++] = Range(0, len - 1);
while (p) {
Range range = r[--p];
if (range.start >= range.end)
continue;
T mid = arr[range.end];
int left = range.start, right = range.end - 1;
while (left < right) {
while (arr[left] < mid && left < right) left++;
while (arr[right] >= mid && left < right) right--;
std::swap(arr[left], arr[right]);
}
if (arr[left] >= arr[range.end])
std::swap(arr[left], arr[range.end]);
else
left++;
r[p++] = Range(range.start, left - 1);
r[p++] = Range(left + 1, range.end);
}
}
堆排序
堆排序是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子节点的健值或索引总是小于)(或等于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法。
- 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列。
- 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排序。
堆排序的平均时间复杂度为O(nlogn)。
1. 算法步骤
创建一个堆 H[0……n-1];
把堆首(最大值)和堆尾互换;
把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;
重复步骤 2,直到堆的尺寸为 1。
2. 动图演示
3.实现方式
3.1 java实现
public class HeapSort implements IArraySort {
@Override
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
int len = arr.length;
buildMaxHeap(arr, len);
for (int i = len - 1; i > 0; i--) {
swap(arr, 0, i);
len--;
heapify(arr, 0, len);
}
return arr;
}
private void buildMaxHeap(int[] arr, int len) {
for (int i = (int) Math.floor(len / 2); i >= 0; i--) {
heapify(arr, i, len);
}
}
private void heapify(int[] arr, int i, int len) {
int left = 2 * i + 1;
int right = 2 * i + 2;
int largest = i;
if (left < len && arr[left] > arr[largest]) {
largest = left;
}
if (right < len && arr[right] > arr[largest]) {
largest = right;
}
if (largest != i) {
swap(arr, i, largest);
heapify(arr, largest, len);
}
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
3.2 C++实现
#include <iostream>
#include <algorithm>
using namespace std;
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) {
// 初始化,i從最後一個父節點開始調整
for (int i = len / 2 - 1; i >= 0; i--)
max_heapify(arr, i, len - 1);
// 先將第一個元素和已经排好的元素前一位做交換,再從新調整(刚调整的元素之前的元素),直到排序完畢
for (int 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);
for (int i = 0; i < len; i++)
cout << arr[i] << ' ';
cout << endl;
return 0;
}
计数排序
桶排序
基数排序
参考:https://www.runoob.com/w3cnote/ten-sorting-algorithm.html