文章目录
- 插入排序
- 冒泡排序
- 选择排序
- 归并排序
- 希尔排序
提示:本文分析算法复杂度时,默认目标是n个元素升序排序,代码注释已经写好,就不单独拎出来了
插入排序
插入排序就是把待排序序列的第一个元素看作是有序序列,把第二个元素到最后一个元素看作是乱序序列,然后循环乱序序列,将扫描到的每个元素插入到有序序列中,每次循环都要保证有序序列是有序的,循环结束,排序完成!
动画演示:
特征:
将元素划分为已排序和未排序两部分,遍历未排序元素,插入到已排序元素中,每次插入确保已排序元素都是有序的
时间复杂度分析:
最好:有序,时间复杂度O(n)
最坏:无序,时间复杂度O(n2)
平均:是输入规模的二次函数
总结:
因而,插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,例如,量级小于千;或者若已知输入元素大致上按照顺序排列,那么插入排序还是一个不错的选择。 插入排序在工业级库中也有着广泛的应用,在STL的sort算法和stdlib的qsort算法中,都将插入排序作为快速排序的补充,用于少量元素的排序(通常为8个或以下)
插入排序Java实现
/**
* 插入排序
* @param arr
*/
public static void InsertionSort(int[] arr) {
// 用于两数交换的临时变量
int temp;
// 从第二个数,即下标1开始依次遍历
for (int i = 1; i < arr.length; i++) {
// 每次遍历,需要和前面已经排好序的元素挨个比较,找到自己的位置
for (int j = i - 1; j >= 0; j--) {
// 判断前一个是否比后一个大,大的话就交换,从而找到合适的位置,升序
if (arr[j] > arr[j + 1]) {
// 交换两数位置
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
冒泡排序
比较相邻的元素,如果第一个比第二个大,就交换他们两个,循环对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。完成后,最后的元素就是最大的数。针对所有的元素重复以上的步骤,除了已经确定的元素。持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较,排序完成!
动画演示:
特征:
两两比较,一趟得出一个极值
时间复杂度分析:
最好:最优时间复杂度O(n)
最坏:最坏时间复杂度O(n2)
平均:是输入规模的二次函数
冒泡排序Java实现
/**
* 冒泡排序
* @param arr
*/
public static void BubbleSort(int[] arr) {
// 记录数组长度
int len = arr.length;
// 临时变量
int temp;
// 循环长度-1次
for (int i = 0; i < len - 1; i++) {
// 每次都会确定一个极值,所以使用 j < len - i - 1
// 避免不必要的循环
for (int j = 0; j < len - i - 1; j++) {
// 判断
if (arr[j] > arr[j + 1]) {
// 交换
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
选择排序
选择排序(Selection sort)是一种简单直观的排序算法,原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,循环直到全部待排序的数据元素的个数为零。排序结束!选择排序是不稳定的排序方法
动画演示:
特征:
一趟计算出一个极大值或极小值,循环直到全部确定,排序完成!判断较多,元素换位较少
时间复杂度分析:
最好:有序,时间复杂度O(n2)
最坏:无序,时间复杂度O(n2)
平均:是输入规模的二次函数
总结:
由于交换所需CPU时间比比较所需的CPU时间多,n值较小时,选择排序比冒泡排序快
选择排序Java实现
/**
* 选择排序
* @param arr
*/
public static void SelectSort(int[] arr) {
int minIndex;
int len = arr.length;
for (int i = 0; i < len; i++) {
minIndex = i;
//遍历找出未排序中的元素中最小值下标
for (int j = i; j < len; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
//若最小值下标与未排序中最左侧下标不一致则交换
if (minIndex != i) {
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
}
归并排序
归并排序是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序
动画演示:
特征:
分治思想
时间复杂度分析:
最好:有序,时间复杂度O(nlog(n))
最坏:无序,时间复杂度O(nlog(n))
平均:时间复杂度O(nlog(n))
总结:
速度仅次于快速排序,为稳定排序算法,一般用于对总体无序,但是各子项相对有序的数列,归并排序的比较次数小于快速排序的比较次数,移动次数一般多于快速排序的移动次数
归并排序Java迭代实现
/**
* 归并排序
* @return
*/
public static void merge_sort(int[] arr) {
int[] orderedArr = new int[arr.length];
for (int i = 2; i < arr.length * 2; i *= 2) {
for (int j = 0; j < (arr.length + i - 1) / i; j++) {
int left = i * j;
int mid = left + i / 2 >= arr.length ? (arr.length - 1) : (left + i / 2);
int right = i * (j + 1) - 1 >= arr.length ? (arr.length - 1) : (i * (j + 1) - 1);
int start = left, l = left, m = mid;
while (l < mid && m <= right) {
if (arr[l] < arr[m]) {
orderedArr[start++] = arr[l++];
} else {
orderedArr[start++] = arr[m++];
}
}
while (l < mid)
orderedArr[start++] = arr[l++];
while (m <= right)
orderedArr[start++] = arr[m++];
System.arraycopy(orderedArr, left, arr, left, right - left + 1);
}
}
}
希尔排序
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序,排序完成!
动画演示:
特征:
拆分
时间复杂度分析:
最好:有序,时间复杂度O(n)
最坏:无序,时间复杂度O(nlog2(n))
总结:
希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小,插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比o(n^2)好一些
希尔排序Java实现
/**
* 希尔排序(插入排序升级版)
* @param arr
*/
public static void shellSort(int[] arr) {
int length = arr.length;
int temp;
for (int step = length / 2; step >= 1; step /= 2) {
for (int i = step; i < length; i++) {
temp = arr[i];
int j = i - step;
while (j >= 0 && arr[j] > temp) {
arr[j + step] = arr[j];
j -= step;
}
arr[j + step] = temp;
}
}
}