概述
由于面试中,排序基本上必问知识,故实现常见排序算法代码(一点都不了解排序算法的跳过,毫无价值)
特别说明
- 如果面试过程中让手写一个排序算法,推荐快速排序,又简单又装逼
- 大多数性能都能保证的归并排序
- 这些算法都是我随笔写的,只保证上述main方法正确,不保证算法一定正确,且不做边界校验
插入排序
直接插入排序
原理
- 国外直接插入排序演示,点击按钮
Insertion Sort - 国内直接插入排序演示`,推荐
代码实现
public static void main(String[] args) {
int[] x = new int[] {5, 1, 3, 7, 11, 4};
System.out.println(Arrays.toString(x));
insertionSort(x);
System.out.println(Arrays.toString(x));
}
public static void insertionSort(int[] nums) {
int len = nums.length;
int j;
for (int i = 1; i < len; i++) {
if (nums[i - 1] < nums[i]) {
continue;
}
int num = nums[i];
for (j = i - 1; j >= 0 && nums[j] > num; j--) {
nums[j + 1] = nums[j];
}
nums[j + 1] = num;
}
}
优化直接插入排序,采用折半插入排序,如1/3/8已经是有序序列,减少比较次数
代码实现
public static void main(String[] args) {
int[] x = new int[] {5, 1, 3, 7, 11, 4, 6, 2, 0, 13, -3};
System.out.println(Arrays.toString(x));
insertionSort(x);
System.out.println(Arrays.toString(x));
}
public static void insertionSort(int[] nums) {
int len = nums.length;
int j;
for (int i = 1; i < len; i++) {
if (nums[i - 1] < nums[i]) {
continue;
}
int num = nums[i];
int low = 1;
int high = i;
while (low <= high) {
int middle = (low + high) / 2;
if (num < nums[middle - 1]) {
high = middle - 1;
} else {
low = middle + 1;
}
}
// position high
for (j = i; j > high; j--) {
nums[j] = nums[j - 1];
}
nums[high] = num;
}
}
2路插入排序
不常见,但是比直接插入排序、折半插入排序更快。参考:data.biancheng.net/view/67.htm…
希尔排序
原理参考:www.cnblogs.com/chengxiao/p…
下面我说一下我自己的理解,排序无非就是比较和交换,要优化效率,要么减少比较的次数,要么减少元素交换的次数,希尔排序是后者,减少元素交换的次数。
如何做得的呢,如图,普通的插入排序两个元素的间隔距离都为1,那就要想办法把间隔距离拉大,让原始一次性尽可能的移动更多的距离,这就是希尔排序。重要说明如下
- 希尔排序最后一次的元素间隔一定是1,即直接插入排序
- 希尔排序间隔都是从大到小,比如,5、2、1,如下图所示
代码实现
public static void main(String[] args) {
int[] x = new int[] {5, 1, 3, 7, 11, 4, 6, 2, 0, 13, -3};
System.out.println(Arrays.toString(x));
shellSort(x, 5);
shellSort(x, 3);
shellSort(x, 1);
System.out.println(Arrays.toString(x));
}
/**
* var dk is incremental
*/
public static void shellSort(int[] nums, int dk) {
int len = nums.length;
int j;
for (int i = dk; i < len; i++) {
if (nums[i - dk] < nums[i]) {
continue;
}
int num = nums[i];
for (j = i - dk; j >= 0 && nums[j] > num; j -= dk) {
nums[j + dk] = nums[j];
}
nums[j + dk] = num;
}
}
与直接插入排序代码差异比较,其实就是多了一个增量dk,由1变成任意大于1的整数
交换排序
冒泡排序
原理
代码实现
public static void main(String[] args) {
int[] x = new int[] {5, 1, 3, 7, 11, 4, 6, 2, 0, 13, -3};
System.out.println(Arrays.toString(x));
bubbleSort(x);
System.out.println(Arrays.toString(x));
}
public static void bubbleSort(int[] nums) {
int len = nums.length;
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - 1 - i; j++) {
if (nums[j] > nums[j+1]) {
int temp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = temp;
}
}
}
}
快速排序
原理
- 国外直接插入排序演示,点击按钮
Quick Sort
个人理解,快速排序属于二路比较,如果是一路扫描,需要引入与数据等大的存储空间,如果是二路扫描,只需要额外的一个存储空间(个人理解,不喜勿喷)
代码实现
public static void main(String[] args) {
int[] x = new int[] {5, 1, 3, 7, 11, 4, 6, 2, 0, 13, -3};
System.out.println(Arrays.toString(x));
quickSort(x);
System.out.println(Arrays.toString(x));
}
public static void quickSort(int[] nums) {
quickSort(nums, 0, nums.length - 1);
}
public static void quickSort(int[] nums, int low, int high) {
if (low < high) {
int location = partition(nums, low, high);
// 分治
quickSort(nums, low, location - 1);
quickSort(nums, location + 1, high);
}
}
//
private static int partition(int[] nums, int low, int high) {
int flagNum = nums[low];
while (low < high) {
while (low < high && nums[high] >= flagNum) {
high--;
}
nums[low] = nums[high];
while (low < high && nums[low] <= flagNum) {
low++;
}
nums[high] = nums[low];
}
nums[low] = flagNum;
return low;
}
选择排序
简单选择排序
原理
- 国外简单选择排序演示,点击按钮
Selection Sort - 国内简单选择排序演示`,推荐
代码实现
public static void main(String[] args) {
int[] x = new int[] {5, 1, 3, 7, 11, 4, 6, 2, 0, 13, -3};
System.out.println(Arrays.toString(x));
selectionSort(x);
System.out.println(Arrays.toString(x));
}
public static void selectionSort(int[] nums) {
for (int i = 0; i < nums.length - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < nums.length; j++) {
if (nums[minIndex] > nums[j]) {
minIndex = j;
}
}
if (minIndex != i) {
int temp = nums[minIndex];
nums[minIndex] = nums[i];
nums[i] = temp;
}
}
}
堆排序
原理,理论参考完全二叉树概念
最小堆构建过程
代码实现
public static void main(String[] args) {
int[] x = new int[] {5, 1, 3, 7, 11, 4, 6, 2, 0, 13, -3};
System.out.println(Arrays.toString(x));
heapSort(x);
System.out.println(Arrays.toString(x));
}
public static void heapSort(int[] nums) {
// 构建堆
for (int i = nums.length / 2; i >= 1 ; i--) {
heapAdjust(nums, i, nums.length);
}
for (int i = nums.length; i > 1 ; i--) {
// 堆中最大元素与第一个元素交换
int temp = nums[0];
nums[0] = nums[i - 1];
nums[i - 1] = temp;
// 第一个元素构造最大堆,剔除最后一个元素
heapAdjust(nums, 1, i - 1);
}
}
// 大顶堆调整,注:数组下标0开始,所有取值都-1
public static void heapAdjust(int[] nums, int start, int length) {
int top = nums[start - 1];
for (int i = start * 2; i <= length; i = i * 2) {
// 1. i < m必有两个孩子,2start、2start+1
// 2. 选择孩子中较大的孩子
if (i < length && nums[i - 1] < nums[i]) {
i++;
}
// 3. 较大的孩子与当前的父节点比较,如果父节点已经是最大的,则完成
if (top > nums[i - 1]) {
break;
}
// 4. 交换值后,递归继续调整
nums[start - 1] = nums[i - 1];
start = i;
}
// 5.
nums[start - 1] = top;
}
归并排序
原理
- 国外直接插入排序演示,点击按钮
Merge Sort,推荐 - 国内直接插入排序演示`
2-路归并排序实现
public static void main(String[] args) {
int[] x = new int[] {5, 1, 3, 7, 11, 4, 6, 2, 0, 13, -3};
System.out.println(Arrays.toString(x));
mergeSort(x);
System.out.println(Arrays.toString(x));
}
public static void mergeSort(int[] nums) {
// 避免生成大量的临时对象,声明等大数组存储空间
int[] tmp = new int[nums.length];
doSort(nums, tmp, 1, nums.length);
}
// 注:数组下标0开始,所有取值都-1
public static void doSort(int[] nums, int[] tmp, int begin, int end) {
if (begin == end) {
tmp[begin - 1] = nums[begin - 1];
} else {
int middle = (begin + end) / 2;
doSort(nums, tmp, begin, middle);
doSort(nums, tmp, middle + 1, end);
doMerge(tmp, nums, begin, middle, end);
System.arraycopy(nums, begin - 1, tmp, begin - 1, (end - begin) + 1);
}
}
// 注:数组下标0开始,所有取值都-1
public static void doMerge(int[] from, int[] to, int begin, int middle, int end) {
int index = begin;
int position = middle + 1;
while (begin <= middle && position <= end) {
if (from[begin - 1] < from[position - 1]) {
to[index - 1] = from[begin - 1];
begin++;
} else {
to[index - 1] = from[position - 1];
position++;
}
index++;
}
while (begin <= middle) {
to[index - 1] = from[begin - 1];
begin++;
index++;
}
while (position <= end) {
to[index - 1] = from[position - 1];
position++;
index++;
}
}