目录
一、概念
1.排序
2.稳定性
3.常见基于比较的排序总览
二、插入排序

2.代码实现
import java.util.Arrays;
public class TestDemo {
public static void insertSort(int[] arr) {
for(int i = 1; i < arr.length; i++) {
int tmp = arr[i];
int j = i-1;
for(; j >= 0; j--){
if(arr[j] > tmp){//第九行
arr[j+1] = arr[j];
}else{
//arr[j+1] = tmp;只要j回退到的时候遇到了比tmp还小的元素就结束比较
break;
}
}
arr[j+1] = tmp;//j<0的情况
}
}
public static void main(String[] args) {
int[] arr = {12, 5, 9, 4, 10};
insertSort(arr);
System.out.println(Arrays.toString(arr));
}
}
- 3.最坏时间复杂度:O(n^2) 平均时间复杂度(指所有可能的输入实例均以等概率出现的情况下,该算法的运行时间):O(n^2)
- 空间复杂度:O(1)
- 稳定性:如果第九行没有=号就是稳定的,有=号就是不稳定的,但是一个稳定的排序可以实现为不稳定的排序,但是本身就不稳定的排序不可以实现为稳定排序,所以我们仍然认为插入排序时稳定的
4.折半插入排序
举个例子:
代码实现:
public static void bsInsertSort(int[] array) {
for (int i = 1; i < array.length; i++) {
int v = array[i];
int left = 0;
int right = i;
// [left, right)
// 需要考虑稳定性
while (left < right) {
int m = (left + right) / 2;
if (v >= array[m]) {
left = m + 1;
} else {
right = m;
}
}
// 搬移
for (int j = i; j > left; j--) {
array[j] = array[j - 1];
}
array[left] = v;
}
}
三、希尔排序
1.原理:希尔排序(Shell's Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因 D.L.Shell 于 1959 年提出而得名。希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止(一般的初次取序列的一半为增量,以后每次减半,直到增量为1。)
注意:分组时,增量序列可以有各种取法,但是我们最好使增量序列中的值没有除1之外的公因子(增量序列不为1的排序称为预排序)
2.过程分析:
3.代码实现(注意此代码和上面过程的分组不同,此代码的分组会简单很多)
import java.util.Arrays;
public class TestDemo {
public static void shell(int[] arr,int gap){
for(int i = gap; i < arr.length; i++){//其实此处如果写i+=5结果也是一样,但是我们需要最后一次的分组的组数为1,所以不能这么写
int tmp = arr[i];
int j = i - gap;
for(; j >= 0; j -= gap){
if(arr[j] > tmp){
arr[j+gap] = arr[j];
}else{
break;
}
}
arr[j + gap] = tmp;
}
}
public static void shellSort(int[] arr){
int gap = arr.length;//此处没有保证每组的组数都为素数
while(gap > 1){
shell(arr,gap);
gap /= 2;
}
shell(arr,1);//保证最后一组是一组
}
/*public static void shellSort(int[] arr){
int[] drr = {5, 3, 1};
for(int i = 0; i < drr.length; i++){
shell(arr,drr[i]);
}
}*/
public static void main(String[] args) {
int[] arr = {12,5,9,34,6,8,33,56,89,0,12,4,22,55,77};
shellSort(arr);
System.out.println(Arrays.toString(arr));
}
}
4.时间复杂度:(和增量有关系)【不是说我们这个代码的时间复杂度是这个】在一定范围之内它是O(n^1.3~n^1.5)
空间复杂度:O(1)
稳定性:不稳定(在比较过程中发生了跳跃式的交换,那么就是不稳定的)
四、选择排序
1.原理:每一次从无序区间选出最大(或最小)的一个元素,存放在无序区间的最后(或最前),直到全部待排序的数据元素排完 。
2.过程分析如下:
3.代码实现
import java.util.Arrays;
public class TestDemo {
public static void selectSort(int[] arr){
int tmp, index;
for(int i = 0; i < arr.length - 1; i++){
index = i;
for(int j = i + 1; j < arr.length; j++){
if(arr[j] < arr[index]){
index = j;
}
tmp = arr[i];
arr[i] = arr[index];
arr[index] = tmp;
}
}
}
public static void main(String[] args) {
int[] arr = {3, 5, 2, 8, 1};
selectSort(arr);
System.out.println(Arrays.toString(arr));
}
}
4.时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:不稳定【6 7 6 2 8此时最小数据为2,2和6交换,6跑到6的后面,因此不稳定】
对数据的有序性不明感
5.双向选择排序

public static void selectSortOP(int[] array) {
int low = 0;
int high = array.length - 1;
// [low, high] 表示整个无序区间
// 无序区间内只有一个数也可以停止排序了
while (low <= high) {
int min = low;
int max = low;
for (int i = low + 1; i <= max; i++) {
if (array[i] < array[min]) {
min = i;
}
if (array[i] > array[max]) {
max = i;
}
}
swap(array, min, low);
if (max == low) {
max = min;
}
swap(array, max, high);
}
}
private void swap(int[] array, int i, int j) {
int t = array[i];
array[i] = array[j];
array[j] = t;
}
五、堆排序
之前我们在数据结构之优先级队列(堆)已经实现过堆排序,这里我们不再重复总结,此处我们总结一下它的时间复杂度、空间复杂度以及稳定性
时间复杂度:O(N*logN)
空间复杂度:O(1)
稳定性:不稳定
对数据的有序性不明感
六、冒泡排序
1.原理:在无序区间,通过相邻数的比较,将最大的数冒泡到无序区间的最后,持续这个过程,直到数组整体有序
2.过程分析:
3.代码实现
import java.util.Arrays;
public class TestDemo {
public static void bubbleSort(int[] arr){
int tmp, flag;
for(int i = 0; i < arr.length - 1; i++){
flag = 1;
for(int j = 0; j < arr.length - 1 - i; j++){
if(arr[j] > arr[j+1]){
tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
flag = 0;
}
}
if(flag == 1){
break;
}
}
}
public static void main(String[] args) {
int[] arr = {7, 3, 66, -5, 22};
bubbleSort(arr);
System.out.println(Arrays.toString(arr));
}
}
4.时间复杂度:O(n^2) 最好的情况:O(n)【有序的情况】
空间复杂度:O(1)
稳定性:稳定
七、快速排序
1.原理:

(2)Hoare 法:
①先找一个基准值,一般为第一个元素
②从后往前找第一个比基准值小的元素,从前往后找第一个比基准值大的元素,然后交换,用while循环直到找的两个位置相遇,交换基准值和相遇位置的元素
(3)前后遍历法
3.代码实现
(1)挖坑法
import java.util.Arrays;
public class TestDemo {
public static void quickSort(int[] arr,int left,int right){
if(left >= right){
return;
}
int pivot = partition(arr,left,right);//找到了基准
quickSort(arr,left,pivot-1);
quickSort(arr,pivot+1,right);
}
private static int partition(int[] arr,int start,int end){
int tmp = arr[start];
while(start < end){
while(start < end && arr[end] >= tmp){//防止一直--下去,会有数组越界的问题,注意:此处不能不加等号,否则会出现死循环,如下图所示
end--;
}
arr[start] = arr[end];
while(start < end && arr[start] <= tmp){//防止一直++下去,会有数组越界的问题
start++;
}
arr[end] = arr[start];
}
arr[start] = tmp;
return start;
}
/*public static void test(int capacity){
int[] arr = new int[capacity];
for(int i = 0; i < arr.length; i++){
arr[i] = i;
}
long start = System.currentTimeMillis();
quickSort(arr,0, arr.length - 1);
long end = System.currentTimeMillis();
System.out.println(end - start);
}*/
public static void main(String[] args) {
int[] arr = {5, 1, 2, 4, 3, 6, 9, 7, 10, 8};
quickSort(arr,0,arr.length - 1);
//test(100_0000);
System.out.println(Arrays.toString(arr));
}
}
编译并运行该代码,输出如下:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

此代码有一个问题,当我要排一百万个数据的时候,发生了栈溢出的错误(递归时开辟的栈太多了) 【我们可以将上面的代码的注释去掉运行一下】
但是这也不妨碍我们计算待代码实现的快排的时间复杂度和空间复杂度
- 时间复杂度:我们发现这个实现的过程和二叉树十分相似O(n*logn)【最好情况:每次都可以均匀的分割待排序序列】
O(n^2)【最坏情况:有序的时候】
- 空间复杂度:O(logn)【最好情况】
O(n)【最坏情况-->单分支的一棵树】
- 稳定性:不稳定
(2)Hoare 法:
private static int partition(int[] array, int left, int right) {
int i = left;
int j = right;
int pivot = array[left];
while (i < j) {
while (i < j && array[j] >= pivot) {
j--;
}
while (i < j && array[i] <= pivot) {
i++;
}
swap(array, i, j);
}
swap(array, i, left);
return i;
}
(3)前后遍历法:
private static int partition(int[] array, int left, int right) {
int d = left + 1;
int pivot = array[left];
for (int i = left + 1; i <= right; i++) {
if (array[i] < pivot) {
swap(array, i, d);
d++;
}
}
swap(array, d, left);
return d;
}
4.针对栈溢出的代码优化:
import java.util.Arrays;
public class TestDemo {
public static void quickSort(int[] arr,int left,int right){
if(left >= right){
return;
}
//优化如下:找到中间大小的值
int midValIndex = findMidValIndex(arr,left,right);
int tmp = arr[midValIndex];
arr[midValIndex] = arr[left];
arr[left] = tmp;
//
int pivot = partition(arr,left,right);//找到了基准
quickSort(arr,left,pivot-1);
quickSort(arr,pivot+1,right);
}
//优化部分:
private static int findMidValIndex(int[] arr,int start, int end){
int mid = start + ((end - start) >>> 1);
if(arr[start] < arr[end]){
if(arr[mid] < arr[start]){
return start;
}else if(arr[mid] > arr[end]){
return end;
}else{
return mid;
}
}else{
if(arr[mid] < arr[start]){
return end;
}else if(arr[mid] > arr[end]){
return start;
}else{
return mid;
}
}
}
//
private static int partition(int[] arr,int start,int end){
int tmp = arr[start];
while(start < end){
while(start < end && arr[end] >= tmp){//防止一直--下去,会有数组越界的问题
end--;
}
arr[start] = arr[end];
while(start < end && arr[start] <= tmp){//防止一直++下去,会有数组越界的问题
start++;
}
arr[end] = arr[start];
}
arr[start] = tmp;
return start;
}
public static void test(int capacity){
int[] arr = new int[capacity];
for(int i = 0; i < arr.length; i++){
arr[i] = i;
}
long start = System.currentTimeMillis();
quickSort(arr,0, arr.length - 1);
long end = System.currentTimeMillis();
System.out.println(end - start);
}
public static void main(String[] args) {
int[] arr = {5, 1, 2, 4, 3, 6, 9, 7, 10, 8};
quickSort(arr,0,arr.length - 1);
test(100_0000);
System.out.println(Arrays.toString(arr));
}
}
编译并运行该代码,输出如下:
我们发现问题得以解决。
法二优化:我们可以在partition 过程中把和基准值相等的数也选择出来,移到跟前来,这样左右两边的递归次数就减少了
法三优化:利用直接插入排序越有序越快的规则来优化
import java.util.Arrays;
public class TestDemo {
public static void insertSort(int[] arr,int start, int end){
for(int i = 1; i <= end; i++){
int tmp = arr[i];
int j = i - 1;
for(;j >= start; j--){
if(arr[j] > tmp){
arr[j + 1] = arr[j];
}else{
break;
}
}
arr[j+1] = tmp;
}
}
public static void quickSort(int[] arr,int left,int right){
if(left >= right){
return;
}
//如果区间内的某个数据在排序的过程中小于某个范围了,可以使用直接插入排序
if(right - left + 1 <= 40000){
//使用直接插入排序法
insertSort(arr,left,right);
return;
}
//优化如下:找到中间大小的值
int midValIndex = findMidValIndex(arr,left,right);
int tmp = arr[midValIndex];
arr[midValIndex] = arr[left];
arr[left] = tmp;
//
int pivot = partition(arr,left,right);//找到了基准
quickSort(arr,left,pivot-1);
quickSort(arr,pivot+1,right);
}
//优化部分:
private static int findMidValIndex(int[] arr,int start, int end){
int mid = start + ((end - start) >>> 1);
if(arr[start] < arr[end]){
if(arr[mid] < arr[start]){
return start;
}else if(arr[mid] > arr[end]){
return end;
}else{
return mid;
}
}else{
if(arr[mid] < arr[start]){
return end;
}else if(arr[mid] > arr[end]){
return start;
}else{
return mid;
}
}
}
//
private static int partition(int[] arr,int start,int end){
int tmp = arr[start];
while(start < end){
while(start < end && arr[end] >= tmp){//防止一直--下去,会有数组越界的问题
end--;
}
arr[start] = arr[end];
while(start < end && arr[start] <= tmp){//防止一直++下去,会有数组越界的问题
start++;
}
arr[end] = arr[start];
}
arr[start] = tmp;
return start;
}
public static void test(int capacity){
int[] arr = new int[capacity];
for(int i = 0; i < arr.length; i++){
arr[i] = i;
}
long start = System.currentTimeMillis();
quickSort(arr,0, arr.length - 1);
long end = System.currentTimeMillis();
System.out.println(end - start);
}
public static void main(String[] args) {
int[] arr = {5, 1, 2, 4, 3, 6, 9, 7, 10, 8};
quickSort(arr,0,arr.length - 1);
test(100_0000);
System.out.println(Arrays.toString(arr));
}
}
编译并运行该代码,输出如下:
5.非递归实现快排
利用栈。划分之后,把左右的数对都放到栈当中。前提:pivot左边有两个元素(pivot>left+1);pivot的右边有两个元素(pivot < right-1)
如图所示:
import java.util.Arrays;
import java.util.Stack;
private static int partition(int[] arr,int start,int end){
int tmp = arr[start];
while(start < end){
while(start < end && arr[end] >= tmp){//防止一直--下去,会有数组越界的问题
end--;
}
arr[start] = arr[end];
while(start < end && arr[start] <= tmp){//防止一直++下去,会有数组越界的问题
start++;
}
arr[end] = arr[start];
}
arr[start] = tmp;
return start;
}
public static void quickSort2(int[] arr){
Stack<Integer> stack = new Stack<>();
int left = 0;
int right = arr.length - 1;
int pivot = partition(arr,left,right);
if(pivot > left + 1){//左边有两个元素
stack.push(left);
stack.push(pivot-1);
}
if(pivot < right -1){
stack.push(pivot + 1);
stack.push(right);
}
while(!stack.isEmpty()){
right = stack.pop();
left = stack.pop();
pivot = partition(arr,left,right);
if(pivot > left + 1){//左边有两个元素
stack.push(left);
stack.push(pivot-1);
}
if(pivot < right -1){
stack.push(pivot + 1);
stack.push(right);
}
}
}
public static void test(int capacity){
int[] arr = new int[capacity];
for(int i = 0; i < arr.length; i++){
arr[i] = i;
}
long start = System.currentTimeMillis();
quickSort2(arr);
long end = System.currentTimeMillis();
System.out.println(end - start);
}
public static void main(String[] args) {
int[] arr = {5, 1, 2, 4, 3, 6, 9, 7, 10, 8};
quickSort2(arr);
test(10_0000);
System.out.println(Arrays.toString(arr));
}
}
编译并运行该代码,输出如下:
八、归并排序
1.原理:归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并
2.实现归并排序之前,我们先写一下合并两个有序数组的代码
import java.util.Arrays;
public class TestDemo {
public static int[] arrayMerge(int[] arr1, int[] arr2){
int[] arr = new int[arr1.length + arr2.length];
int s1 = 0;
int e1 = arr1.length-1;
int s2 = 0;
int e2 = arr2.length-1;
int i = 0;
while(s1 <= e1 && s2 <= e2){
if(arr1[s1] <= arr2[s2]){
arr[i++] = arr1[s1++];
}else{
arr[i++] = arr2[s2++];
}
}
while(s1 <= e1){
arr[i++] = arr1[s1++];
}
while(s2 <= e2){
arr[i++] = arr2[s2++];
}
return arr;
}
public static void main(String[] args) {
int[] arr1 = {0, 2, 4, 6};
int[] arr2 = {1, 3, 5, 7};
int[] ret = arrayMerge(arr1, arr2);
System.out.println(Arrays.toString(ret));
}
}
3.归并排序的过程分析
4.代码实现
import java.util.Arrays;
public class TestDemo {
public static void mergeSort(int[] arr, int low, int high){
if(low >= high){
return;
}
int mid = low +((high - low) >>> 1);
mergeSort(arr,low,mid);
mergeSort(arr,mid+1,high);
merge(arr,low,mid,high);
}
public static void merge(int[] arr, int low, int mid, int high){
int i = 0;
int[] mergeArr = new int[high - low + 1];
int s1 = low;
int e1 = mid;
int s2 = mid + 1;
int e2 = high;
while(s1 <= e1 && s2 <= e2){
if(arr[s1] <= arr[s2]){//第21行代码
mergeArr[i++] = arr[s1++];
}else{
mergeArr[i++] = arr[s2++];
}
}
while(s1 <= e1){//第27行代码
mergeArr[i++] = arr[s1++];
}
while(s2 <= e2){//第30行代码
mergeArr[i++] = arr[s2++];
}
for(int j = 0; j < i; j++){
arr[j + low] = mergeArr[j];
}
}
public static void main(String[] args) {
int[] arr = {10, 6, 7, 1, 3, 9, 4, 2};
mergeSort(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
}
编译并运行该代码,输出如下:
[1, 2, 3, 4, 6, 7, 9, 10]
5.时间复杂度:O(n * log(n))
空间复杂度:O(n)
稳定性:稳定 (关键看第21、27、30行代码有没有取等号,若没有取等号则不稳定)
6.非递归实现归并排序
思想和递归相类似,只不过是用循环来实现
public class TestDemo {
public static void merge(int[] arr, int low, int mid, int high){
int i = 0;
int[] mergeArr = new int[high - low + 1];
int s1 = low;
int e1 = mid;
int s2 = mid + 1;
int e2 = high;
while(s1 <= e1 && s2 <= e2){
if(arr[s1] <= arr[s2]){
mergeArr[i++] = arr[s1++];
}else{
mergeArr[i++] = arr[s2++];
}
}
while(s1 <= e1){
mergeArr[i++] = arr[s1++];
}
while(s2 <= e2){
mergeArr[i++] = arr[s2++];
}
for(int j = 0; j < i; j++){
arr[j + low] = mergeArr[j];
}
}
public static void mergeSort2(int[] arr){
int gap = 1;//每组的数据个数
while(gap < arr.length){
//数组每次都要遍历,确定归并的区间
for (int i = 0; i < arr.length; i += gap*2) {
int left= i;
int mid = left + gap - 1;
if(mid >= arr.length){//防止越界
mid = arr.length - 1;
}
int right = mid + gap;
if(right >= arr.length){
right = arr.length - 1;
}
//下标确定后进行合并
merge(arr,left,mid,right);
}
gap *= 2;
}
}
public static void main(String[] args) {
int[] arr = {10, 6, 7, 1, 3, 9, 4, 2};
mergeSort2(arr);
System.out.println(Arrays.toString(arr));
}
}
编译并运行该代码,输出如下:
[1, 2, 3, 4, 6, 7, 9, 10]
7.海量数据排序的问题
九、其他非基于比较的排序
1.计数排序
代码实现:
import java.util.Arrays;
public class TestDemo {
public static void countingSort(int[] arr){
int maxVal = arr[0];
int minVal = arr[0];
for (int i = 0; i < arr.length; i++) {
if(arr[i] < minVal){
minVal = arr[i];
}
if(arr[i] > maxVal){
maxVal = arr[i];
}
}
int[] count = new int[maxVal - minVal + 1];
for (int i = 0; i < arr.length; i++) {
int index = arr[i];
count[index-minVal]++;//为了空间的合理使用
}
int indexArr = 0;
for (int i = 0; i < count.length; i++) {
while(count[i] > 0){
arr[indexArr] = i + minVal;
count[i]--;//拷贝一个,个数也就少一个
indexArr++;
}
}
}
public static void main(String[] args) {
int[] arr = {2,5,3,0,2,3,0,3};
countingSort(arr);
System.out.println(Arrays.toString(arr));
}
}
编译并运行,输出如下:
[0, 0, 2, 2, 3, 3, 3, 5]
时间复杂度:O(n)
空间复杂度:O(m) m:当前数据的范围,如:900~919 范围为20
稳定性:不稳定 ,但是本质是稳定的(我们可以再引入一个数组进行操作)
2.基数排序
原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数,如图所示:
我们可以把一个个的框框看成链表或者队列
3.桶排序
过程分析如图所示: