插入排序的基本思想: 主要分为两个区间, 无序区间和有序区间, 每次选择无序区间的第一个元素, 在有序区间内选择合适的位置进行插入操作.
插入过程如下图所示:

代码:
public class InsertSort {
public static void insertSort(int[] array) {
for (int i = 1; i < array.length; i++) {
int tmp = array[i];
int j = i-1;
for (; j >= 0; j--) {
if (array[j] > tmp) {
array[j+1] = array[j];
} else {
break;
}
}
array[j+1] = tmp;
}
}
public static void main(String[] args) {
int[] arr = {3,2,5,7,1,6,8,9,4};
System.out.println(Arrays.toString(arr));
insertSort(arr);
System.out.println(Arrays.toString(arr));
}
}
运行结果:

性能分析:

折半插入的详细过程如下:

代码:
public class InsertSort {
public static void bsInsertSort(int[] array) {
for (int i = 1; i < array.length; i++) {
int tmp = array[i];
int left = 0;
int right = i;
while (left < right) {
int aver = (left + right) / 2;
if (tmp >= array[aver]) {
left = aver + 1;
} else {
right = aver;
}
}
for (int j = i; j > left; j--) {
array[j] = array[j-1];
}
array[left] = tmp;
}
}
public static void main(String[] args) {
int[] arr = {3,2,5,7,1,6,8,9,4};
System.out.println(Arrays.toString(arr));
bsInsertSort(arr);
System.out.println(Arrays.toString(arr));
}
}
运行结果:

性能分析:

希尔排序的基本思想: 把待排序列中的数据分成若干个组, 并对每组的数据进行排序, 重复上述工作, 直到等于 1 的时候, 所有记录即将排序完毕.
详细过程如下:

代码如下:
public class InsertSort {
public static void shell(int[] arr, int gap) {
for (int i = gap; i < arr.length; i++) {
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[] array) {
int gap = array.length;
while (gap > 1) {
gap = gap / 3 + 1;
shell(array, gap);
}
}
public static void main(String[] args) {
int[] arr = {3,2,5,7,1,6,8,9,4};
System.out.println(Arrays.toString(arr));
shellSort(arr);
System.out.println(Arrays.toString(arr));
}
}
运行结果:

性能分析:

直接选择排序比较简单, 每一次排序只能确定一个数值, 因此这里不再进行描述其详细过程.
代码如下:
public class SelectSort {
public static void selectSort(int[] array) {
for (int i = 0; i < array.length; i++) {
for (int j = i+1; j < array.length; j++) {
if (array[j] < array[i]) {
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
}
}
}
public static void main(String[] args) {
int[] arr = {3,2,5,7,1,6,8,9,4};
System.out.println(Arrays.toString(arr));
selectSort(arr);
System.out.println(Arrays.toString(arr));
}
}
运行结果:

性能分析:


向下调整的前提是左右子树必须已经是一个堆才能调整.我们以小堆为例, 如下所示.
其中: array 代表存储堆的数组; size 代表数组中被视为堆数据的个数; index 代表要调整位置的下标; left 代表 index 左孩子下标; right 代表 index 右孩子下标; min 代表 index 的最小值孩子的下标.

关于堆的建立基本思想就是从倒数第一个非叶子节点的子树开始调整, 一直调整到根节点的树, 即为堆.这里我们以大根堆为例.

堆排序的基本思想也是选择排序, 只是不再使用遍历的方式查找无序区间的最大值, 而是通过堆来选择无序区间的最大的数; 还需要注意: 升序排序要建大堆; 降序排序要建小堆.
详细过程如下:

代码如下:
public class SelectSort {
public static void createHeap(int[] arr) {
//从小到大排序 --> 大根堆
for(int i = (arr.length - 1 - 1)/2;i>=0;i--) {
siftDown(arr,i, arr.length);
}
}
public static void siftDown(int[] arr,int root,int len) {
int parent = root;
int child = 2*parent+1;
while (child < len) {
if(child+1 < len && arr[child] < arr[child+1]) {
child++;
}
//child的下标就是左右孩子的最大值下标
if(arr[child] > arr[parent]) {
int tmp = arr[child];
arr[child] = arr[parent];
arr[parent] = tmp;
parent = child;
child = 2*parent+1;
} else {
break;
}
}
}
public static void heapSort(int[] arr) {
createHeap(arr); //O(n)
int end = arr.length - 1;
while (end > 0) { //O(N*LogN)
int tmp = arr[end];
arr[end] = arr[0];
arr[0] = tmp;
siftDown(arr,0,end);
end--;
}
}
public static void main(String[] args) {
int[] arr = {3,2,1,5,8,7,6,4,9};
System.out.println(Arrays.toString(arr));
heapSort(arr);
System.out.println(Arrays.toString(arr));
}
}
运行结果:

性能分析:

冒泡排序的基本思想就是两两进行比较, 也就是相邻的两个元素进行比较, 每次一个外循环都可以确定一个最大值, 并将这个最大值放在无序序列的最后面, 持续这个过程, 直到整个序列有序.
详细执行过程如下:

代码如下:
public static void bubbleSort(int[] arr) {
for (int i = 0; i < arr.length-1; i++) {
boolean flag = false;
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;
flag = true;
}
}
if (flag == false) {
break;
}
}
}
public static void main(String[] args) {
int[] arr = {3,2,5,7,1,6,8,9,4};
System.out.println(Arrays.toString(arr));
bubbleSort(arr);
System.out.println(Arrays.toString(arr));
}
}
运行结果:

性能分析: 这里需要注意一下: 如果序列是优化过的, 也就是有序的, 其时间复杂度可能是 O(n).

快速排序的基本思想如下:
关于基准值的选择主要有三种方式:
1) 选择边上的数字, 例如我们经常会选择序列的首位数字作为基准值;
2) 随机选择;
3) 三位数中, 也就是arr[start], arr[mid], arr[end] 这三个数中选择中间大的数字, 下面的代码我们就是用的这种方式.

关于快速排序这里主要介绍两种方法, 一种是递归遍历的方式, 另一种是非递归遍历的方式.
详细过程如下:

代码如下:
public class SwapSort {
public static int partition(int[] arr, int low, int high) {
int tmp = arr[low];
while (low < high) {
while (low < high && arr[high] >= tmp) {
high--;
}
arr[low] = arr[high];
while (low < high && arr[low] <= tmp) {
low++;
}
arr[high] = arr[low];
}
arr[high] = tmp;
return low;
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
public static void selectPivotMedianOfThree(int[] arr, int start, int end, int mid) {
while (true) {
if (arr[mid] > arr[start]) {
swap(arr, start, mid);
continue;
}
if (arr[start] > arr[end]) {
swap(arr, start, end);
continue;
}
if (arr[mid] > arr[end]) {
swap(arr, start, end);
continue;
}
break;
}
}
public static void quick(int[] arr, int start, int end) {
if (start >= end) {
return;
}
int mid = (start+end)/2;
selectPivotMedianOfThree(arr, start, end, mid);
int pivot = partition(arr, start, end);
quick(arr, start, pivot-1);
quick(arr, pivot+1, end);
}
public static void quickSort(int[] arr) {
quick(arr, 0, arr.length-1);
}
public static void main(String[] args) {
int[] arr = {3,2,5,7,1,6,8,9,4};
System.out.println(Arrays.toString(arr));
quickSort(arr);
System.out.println(Arrays.toString(arr));
}
}
运行结果:

性能分析:

非递归的实现需要用到栈, 详细过程如下:

代码如下:
public class SwapSort {
public static int partition(int[] arr, int low, int high) {
int tmp = arr[low];
while (low < high) {
while (low < high && arr[high] >= tmp) {
high--;
}
arr[low] = arr[high];
while (low < high && arr[low] <= tmp) {
low++;
}
arr[high] = arr[low];
}
arr[high] = tmp;
return low;
}
public static void quickSort2(int[] arr) {
Stack<Integer> stack = new Stack<>();
int start = 0;
int end = arr.length - 1;
int pivot = partition(arr, start, end);
// 左边有两个元素及以上
if (pivot > start + 1) {
stack.push(0);
stack.push(pivot - 1);
}
if (pivot < end - 1) {
stack.push(pivot + 1);
stack.push(end);
}
while (!stack.empty()) {
end = stack.pop();
start = stack.pop();
pivot = partition(arr, start, end);
// 左边有两个元素及以上
if(pivot > start+1) {
stack.push(0);
stack.push(pivot-1);
}
if(pivot < end-1) {
stack.push(pivot+1);
stack.push(end);
}
}
}
public static void main(String[] args) {
int[] arr = {3,2,5,7,1,6,8,9,4};
System.out.println(Arrays.toString(arr));
quickSort2(arr);
System.out.println(Arrays.toString(arr));
}
}
运行结果:

关于归并排序也是有两种实现方式, 递归实现和非递归实现, 其思路如下.
**递归实现归并排序的基本思想: 就是一直对半分, 直到分到剩下一个或者是两个元素的时候为止, 然后对分开到最后的小序列进行排序比较, 比较完后回到上一层的序列中再次进行比较, 直到序列整体有序. **
详细过程如下:

代码如下:
public class MergeSort {
/**
* 递归实现
*/
public static void merge(int[] arr, int low, int mid, int high) {
int s1 = low;
int e1 = mid;
int s2 = mid + 1;
int e2 = high;
int[] tmp = new int[high - low + 1];
int k = 0; // 代表 tmp 数组的下标
while (s1 <= e1 && s2 <= e2) {
if (arr[s1] <= arr[s2]) {
tmp[k++] = arr[s1++];
} else {
tmp[k++] = arr[s2++];
}
}
// 有两种情况
while (s1 <= e1) {
// 说明第二个归并段没有了数据, 把第一个归并段剩下的数据全部拷过来
tmp[k++] = arr[s1++];
}
while (s2 <= e2) {
// 说明第一个归并段没有了, 把第二个归并段剩下的数据全部拷贝过来
tmp[k++] = arr[s2++];
}
// tmp 数组当中, 存储的就是当前归并好的数据
for (int i = 0; i < tmp.length; i++) {
arr[i + low] = tmp[i];
}
}
public static void mergeSortInternal(int[] arr, int low, int high) {
if (low >= high) {
return;
}
int mid = (low + high) / 2;
mergeSortInternal(arr, low, mid);
mergeSortInternal(arr, mid + 1, high);
// 合并的过程
merge(arr, low, mid, high);
}
public static void mergeSort1(int[] arr) {
mergeSortInternal(arr, 0, arr.length - 1);
}
public static void main(String[] args) {
int[] arr = {3,2,5,7,1,6,8,9,4};
System.out.println(Arrays.toString(arr));
mergeSort1(arr);
System.out.println(Arrays.toString(arr));
}
}
运行结果:

性能分析:

对于非递归来实现的基本思想就是先从 i = 1开始, 对 i*2 的序列进行排序, 如下图所示, 首先对 3,2 进行排序, 然后再对 2,3,5,7 进行排序, 然后再对 1,2,3,5,6,7,8,9 八个元素进行排序, 最后再对所有元素进行排序.
详细思路如下:

代码如下:
public class MergeSort {
public static void merge2(int[] arr, int gap) {
int[] tmp = new int[arr.length];
int k = 0;
int s1 = 0;
int e1 = s1 + gap - 1;
int s2 = e1 + 1;
int e2 = s2 + gap - 1 < arr.length ? s2 + gap - 1 : arr.length - 1;
// 需要保证有两个归并段
while (s2 < arr.length) {
while (s1 <= e1 && s2 <= e2) {
if (arr[s1] <= arr[s2]) {
tmp[k++] = arr[s1++];
} else {
tmp[k++] = arr[s2++];
}
}
while (s1 <= e1) {
tmp[k++] = arr[s1++];
}
while (s2 <= e2) {
tmp[k++] = arr[s2++];
}
// 一组完成后, 确定新的区间的开始和结束
s1 = e2 + 1;
e1 = s1 + gap - 1;
s2 = e1 + 1;
e2 = s2 + gap - 1 < arr.length ? s2 + gap - 1 : arr.length - 1;
}
while (s1 <= arr.length - 1) {
tmp[k++] = arr[s1++];
}
for (int i = 0; i < tmp.length; i++) {
arr[i] = tmp[i];
}
}
public static void mergeSort2(int[] arr) {
for (int i = 1 ; i < arr.length; i*=2) {
merge2(arr, i);
}
}
public static void main(String[] args) {
int[] arr = {3,2,5,7,1,6,8,9,4};
System.out.println(Arrays.toString(arr));
mergeSort2(arr);
System.out.println(Arrays.toString(arr));
}
}
运行结果:

非比较排序只需要了解即可, 知道是怎么排的, 这里我们不再演示代码.
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中, 计数排序要求输入的数据必须是有确定范围的整数.具体算法如下:
详细过程如下:

基数排序的基本思想就是"多关键字排序", 其实现方式有两种:
详细过程如下:

桶排序可以说是计数排序的升级版本, 利用函数的映射关系, 高效与否的关键就在于这个映射函数的确定, 需要做到以下两点:
也可以这么理解, 当输入的数据可以均匀的分配到 K 个桶中的时候效率比较高, 当输入的数据被分配到同一个桶中的时候, 效率比较低.
详细过程如下:

