直接插入排序
每次选择无序区间的第一个元素,在有序区间内选择合适的位置插入
插入排序,初始数据越接近有序,时间效率越高
//直接插入排序
//arr[] = {6,9,3,12,8,1};
public static void insertSort(int[] arr) {
for (int i = 0; i < arr.length; i++) {
int tmp = arr[i];
int j;
for(j = i - 1; j >= 0; j--) {
if(arr[j] > tmp) {
arr[j + 1] = arr[j];
} else {
break;
}
}
arr[j + 1] = tmp;
}
}
//执行结果
[1, 3, 6, 8, 9, 12]
性能分析
-
时间复杂度
- 最好:O(n) 数据有序
- 平均:O(n^2)
- 最坏:O(n^2) 数据逆序 空间复杂度
- O(1) 稳定性
- 稳定
希尔排序
希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。
- 希尔排序是对直接插入排序的优化;
- 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
//初始arr {9,1,2,5,7,4,8,6,3,5}
public static void hellSort(int[] arr) {
int[] gap = {5,3,1};
for (int i = 0; i < gap.length; i++) {
insertSortGap(arr,gap[i]);
}
}
public static void insertSortGap(int[] arr,int gap) {
for (int i = 1; i < arr.length; i++) {
int tmp = arr[i];
int j;
for (j = i - gap; j >= 0 ; j -= gap) {
if(arr[j] > tmp) {
arr[j + gap] = arr[j];
} else {
break;
}
}
arr[j + gap] = tmp;
}
}
//执行结果
[1, 2, 3, 4, 5, 5, 6, 7, 8, 9]
性能分析
-
时间复杂度
- 最好:O(n)
- 最坏:O(n^2) 空间复杂度
- O(1) 稳定性
- 不稳定
选择排序
每一次从无序区间选出最大(或最小)的一个元素,存放在无序区间的最后(或最前),直到全部待排序的数据元素排完。
//选择排序
//初始数据 6,9,3,2,8,1,5
public static void selectSort(int[] arr){
for (int i = 0; i < arr.length; i++) {
for (int j = i + 1; j < arr.length; j++) {
if (arr[i] > arr[j]) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
}
}
//执行结果
[1, 2, 3, 5, 6, 8, 9]
性能分析
-
时间复杂度
- O()n^2 数据不敏感 空间复杂度
- O(1) 数据不敏感 稳定性
- 不稳定
冒泡排序
public static void bubbleSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
boolean f = true;
for (int j = 0; j < arr.length - 1 - i; j++) {
if(arr[j] > arr[j + 1]) {
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
f =false;
}
}
//如果 f = true 的话说明数组本身就是有序的,直接跳出即可
if(f) break;
}
}
性能分析
-
时间复杂度
- 最好:O(n)
- 最坏:O(n^2) 空间复杂度
- O(1) 稳定性
- 不稳定
堆排序
排升序要建大堆;排降序要建小堆
//堆排序 (大根堆)
//初始数组 27,15,19,18,28,34,65,49,25,37
public static void heap(int[] arr) {
for(int i = (arr.length - 1 - 1) / 2; i >= 0; i--) {
heapSort(arr,i,arr.length);
}
}
public static void heapSort(int[] arr,int parent,int len) {
//孩子节点 = 父母节点 * 2 + 1
int child = parent * 2 + 1;
while(child < len) {
//child + 1 判断是否有右节点
if(child + 1 < len && arr[child] < arr[child + 1]) {
child++;
}
if(arr[child] > arr[parent]) {
int tmp = arr[child];
arr[child] = arr[parent];
arr[parent] = tmp;
parent = child;
child = parent * 2 +1;
} else {
//因为是从最后一颗子树建堆的,所以上边建好下边一定是大根堆,直接跳出。
break;
}
}
}
//执行结果
[65, 49, 34, 25, 37, 27, 19, 18, 15, 28]
性能分析
-
时间复杂度
- O(n * log(n)) 空间复杂度
- O(1) 稳定性
- 不稳定
快速排序
- 从待排序区间选择一个数,作为基准值(pivot);
- Partition: 遍历整个待排序区间,将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的(可以包含相等的)放到基准值的右边;
- 采用分治思想,对左右两个小区间按照同样的方式处理,直到小区间的长度 == 1,代表已经有序,或者小区间的长度 == 0,代表没有数据。
//快速排序
//初始数组 6,1,2,7,9,3,4,5,10,8
public static void quickSort(int[] arr,int left,int right) {
if(left < right) {
//拿到基准值
int piv = getPivot(arr,left,right);
//递归左边
quickSort(arr,left,piv - 1);
//递归右边
quickSort(arr,piv + 1,right);
}
}
public static int getPivot(int[] arr,int left,int right) {
int tmp = arr[left];
while(left < right) {
//找右边比tmp小的
while(left < right && arr[right] > tmp) {
right--;
}
arr[left] = arr[right];
//找左边比tmp大的
while(left < right && arr[left] < tmp) {
left++;
}
arr[right] = arr[left];
}
//当left = right
arr[left] = tmp;
//返回基准值
return left;
}
//执行结果
[1, 2, 3, 4, 5, 6, 9, 7, 8, 10]
性能分析
-
时间复杂度
- 最好:O(n * log(n))
- 最坏:O(n^2) 空间复杂度
- 最好:O(log(n))
- 最坏:O(n) 稳定性
- 不稳定
归并排序
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
//归并排序
//初始数据 10,6,7,1,3,9,4,2
public static void mergeSort(int[] arr,int left, int right) {
if(left >= right) return;
int mid = (right + left) / 2;
mergeSort(arr,left,mid);
mergeSort(arr,mid + 1,right);
//进行合并
merge(arr,left,mid,right);
}
public static void merge(int[] arr,int start,int mid,int end) {
int s1 = start;
int s2 = mid + 1;
//定义一个新数组合并原数组
int[] ret = new int[end - start + 1];
int k = 0;
while(s1 <= mid && s2 <= end) {
if(arr[s1] <= arr[s2]) {
ret[k++] = arr[s1++];
} else {
ret[k++] = arr[s2++];
}
}
//有可能第一段还有数据
while(s1 <= mid) {
ret[k++] = arr[s1++];
}
//有可能第二段还有数据
while(s2 <= end) {
ret[k++] = arr[s2++];
}
//将ret中的数据接在a原数组中
for (int i = 0; i < ret.length; i++) {
arr[i + start] = ret[i];
}
}
//执行结果
[1, 2, 3, 4, 6, 7, 9, 10]
性能分析
-
时间复杂度
- O(n * log(n)) 空间复杂度
- O(n) 稳定性
- 稳定
总结
排序方法 | 最好 | 平均 | 最坏 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
插入排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
选择排序 | O(n^2 | O(n^2) | O(n^2) | O(1) | 不稳定 |
希尔排序 | O(n) | O(n^1.3) | O(n^2) | O(1) | 不稳定 |
堆排序 | O(n * log(n)) | O(n * log(n)) | O(n * log(n)) | O(1) | 不稳定 |
快速排序 | O(n * log(n)) | O(n * log(n)) | O(n^2) | O(log(n)) ~ O(n) | 不稳定 |
归并排序 | O(n * log(n)) | O(n * log(n)) | O(n * log(n)) | O(n) | 稳定 |