笔者写到现在大约20多篇blog,开始写写算法系列的blog了。算法这东西如果不是专职做以下工作或比赛的,包括:ACM比赛、AI算法工程师、算法岗职业和做计算机技术研究的等等,那么主要就是了解主要的思想、懂大概流程、能够自主实现经典的实例和过得了笔试就差不多了(仅代表笔者个人的观点)。
言归正传,今天笔者的blog主要是冒择路兮快归堆中的冒泡排序(bubble sort)、选择排序(select sort)、插入排序(insert sort)和归并排序(merge sort)4种。
1.Bubble Sort
冒泡在工程上基本不会有太多用武之地,而作为教学典例会被经常提及,因此笔者在这就不过多赘述了。
上图省略了一些步骤。
Bubble.java
class Bubble{
public static void bubbleSort(int[] arr){
if(null == arr || arr.length < 2) return;
for(int end = arr.length - 1; end >= 0; end--){
for(int start = 0; start < end; start++){
if(arr[start] > arr[start + 1])
swap(arr, start, start + 1);
System.out.println(Arrays.toString(arr));
}
}
}
public static void swap(int[] arr, int i, int j){
arr[i] = arr[i] ^ arr[j];//这里用的是二进制中异或运算的特性:1 = 1 ^ 0 ^ 0
arr[j] = arr[i] ^ arr[j];//一个数连续异或另一个数两次其值仍为此数本身
arr[i] = arr[i] ^ arr[j];//这种方式不能乱用
}
public static void main(String[] args) {
int[] arr = {4, 3, 2, 1};
bubbleSort(arr);
System.out.println(Arrays.toString(arr));
}
}
Bubble Sort Big O
bubble sort的时间复杂度是属于T(N)=a(N^2)+bN+c的O(N^2)。
2.Select Sort
select sort和bubble sort是有一点点的相似的,它们在时间复杂度上都是O(N^2),下面给出笔者的一点点分析。
下面是对应的代码。
Select.java
class Select{
public static void selectSort(int[] arr){
if(null == arr || arr.length < 2) return;
for(int end = arr.length - 1; end >= 0; end--){
int maxIndex = end;
for(int start = end - 1; start >= 0; start--){
maxIndex = arr[maxIndex] < arr[start] ? start : maxIndex;
}
swap(arr, maxIndex, end);
System.out.println(Arrays.toString(arr));
}
}
public static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String[] args) {
int[] arr = {4, 3, 2, 1};
selectSort(arr);
System.out.println(Arrays.toString(arr));
}
}
上面的这个select sort的实现只是为了作证下笔者对于select sort和bubble sort的相似的想法。
Select Sort Big O
下面给出比较经典的写法。
Select.java version1.1
class Select{
public static void selectSort(int[] arr){
if(null == arr || arr.length < 2) return;
for(int start = 0; start < arr.length; start++){
int minIndex = start;
for(int next = start + 1; next < arr.length; next++){
minIndex = arr[minIndex] > arr[next] ? next : minIndex;
}
swap(arr, start, minIndex);
}
}
public static void swap(int[] arr, int i, int j){
int temp = arr[i];//这里不要使用上面的异或来进行交换
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String[] args) {
int[] arr = {4, 3, 2, 1};
selectSort(arr);
System.out.println(Arrays.toString(arr));
}
}
select sort和bubble sort一样现在最多也只是成为教学典例而已,因为其性能太差了,任然摆脱不了无用的比较。
3.Insert Sort
insert sort较之前面两种排序方式其实也并没有过多的性能上的提升,仍然是O(N^2)的复杂度,也仍然和数据的规模和数据的好坏情况有莫大关联。
这里也不多赘言,我们的重点是第4和第5点。
这里右边已排序元素只会有一个,而且当当前此次排序完成时它会立即成为参照元素,代替原本的参照元素,当有新的元素插入时,参照元素的右边是不会存在已排序的元素的。
Insert.java
class Insert{
public static void insertSort(int[] arr){
if(null == arr || arr.length < 2) return;
for(int start = 1; start < arr.length; start++){
for(int tar = start; tar > 0 && arr[tar] < arr[tar - 1]; tar--)
swap(arr, tar, tar - 1);
}
}
public static void swap(int[] arr, int i, int j){
int temp = arr[i];//这里不要使用上面的异或来进行交换
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String[] args) {
int[] arr = {4, 3, 2, 1};
insertSort(arr);
System.out.println(Arrays.toString(arr));
}
}
Insert Sort Big O
接下来才是我们重点!
4.Merge Sort
merge sort的实现和上面的三种颇为不同,它是通过递归的方式和分治策略(Master)实现的。说道递归或许留给各位最深印象就是“递归不就是自己调用自己吗?”,在具体讨论merge sort前,笔者先说说递归的实现。
递归
递归是通过stack(栈)实现的,在上一层递归方法(父方法)调用下一层同一递归方法(子方法)时,系统会先把上一层递归方法的所有信息保存在栈中,包括当前运行后PC指向的行号、参数列表以及方法体内的局部变量等,当子方法执行到递归结束条件返回退出时,父方法就能获取其子方法返回的值,当此父方法也处理完毕也返回了,那么它也将把值传递给它父方法,就这样层层调用和返回直到结束。
现在笔者来一个简单的例子:使用递归的方式来寻找数组中的最大值。
Test.java
package com.unicorn.base_01;
/**
* @author Unicorn
* @descrition
* @created 2018-05-05-8:55
*/
public class Test {
public static Integer findMax(int[] arr, int l, int r){
if(null == arr) return null;
if(l == r) return arr[l];
int mid = l + ((r - l) >> 1);
int leftMax = findMax(arr, l, mid);
int rightMax = findMax(arr, mid + 1, r);
return leftMax > rightMax ? leftMax : rightMax;
}
public static void main(String[] args) {
int[] arr = new int[]{1001, 4, 55, 8, 12, 99, 101, 22, 688};
System.out.println(findMax(arr, 0, arr.length - 1));
}
}
Analysis
接下来给出merge sort的实现
Merge.java
class Merge{
public static void mergeSort(int[] arr){
if(null == arr || arr.length < 2)
return;
mergeSort(arr, 0, arr.length - 1);
}
private static void mergeSort(int[] arr, int l, int r) {
if(l == r)
return;
int mid = l + ((r - l) >> 1);
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, r);
merge(arr, l, mid, r);
}
private static void merge(int[] arr, int l, int mid, int r) {
int[] help = new int[(r - l) + 1];
int pl = l, pr = mid + 1;
int index = 0;
while(pl <= mid && pr <= r){
help[index++] = arr[pl] > arr[pr] ? arr[pr++] : arr[pl++];
}
while(pl <= mid){
help[index++] = arr[pl++];
}
while (pr <= r){
help[index++] = arr[pr++];
}
for (int i = 0; i < index; i++) {
arr[l + i] = help[i];
}
}
public static void main(String[] args) {
int[] arr = {4, 3, 2, 1};
mergeSort(arr);
System.out.println(Arrays.toString(arr));
}
}
Analysis
5.Test our sorted methods
到现在笔者完成了4种排序,以上的排序都经过50万次长度为100的随机数组的排序测试,现在笔者就来讲讲如何编写这个随机测试。
编写随机测试时我们要满足以下的几点要求:
- 要有一个正确的排序方法,作为排序结果的参照,这个正确的排序方法不需要管其性能的好坏,只要保证正确就可以了(其实不正确的也没关系,迭代代码直到正确为止就好)
- 需要一个产生随机数组的方法
- 需要一个比较自行编写的排序方法和正确的排序方法所排序的结果
测试不正确的时候几时停止并打印结果,便于修改
Test.java
package com.unicorn.base_01;
import com.sun.xml.internal.bind.v2.model.core.ID;
import java.util.ArrayList;
import java.util.Arrays;
/**
* @author Unicorn
* @descrition
* @created 2018-05-05-8:55
*/
public class Test {
//for test
public static void comparator(int[] arr){
Arrays.sort(arr);
}
public static int[] generateRandomArray(int maxSize, int maxValue){
int[] arr = new int[(int)((maxSize + 1) * Math.random())];
for(int i = 0; i < arr.length; i++){
arr[i] = (int)((maxValue + 1) * Math.random()) - (int)((maxValue + 1) * Math.random());
}
return arr;
}
public static int[] copyArray(int[] arr){
if(null == arr) return null;
int[] res = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
res[i] = arr[i];
}
return res;
}
public static boolean isEqual(int[] arr1, int[] arr2){
if((null == arr1 && null != arr2) || (null != arr1 && null == arr2))
return false;
if(null == arr1 && null == arr2)
return true;
if(arr1.length != arr2.length)
return false;
for (int i = 0; i < arr1.length; i++) {
if(arr1[i] != arr2[i]) return false;
}
return true;
}
public static void printArray(int[] arr){
if(null == arr) return;
System.out.println(Arrays.toString(arr));
}
public static void main(String[] args) {
int testTimes = 500000;
int maxSizes = 100;
int maxValue = 100;
boolean succeed = true;
for (int i = 0; i < testTimes; i++) {
int[] arr1 = generateRandomArray(maxSizes, maxValue);
int[] arr2 = copyArray(arr1);
Merge.mergeSort(arr1);
comparator(arr2);
if(!isEqual(arr1, arr2)){
succeed = false;
printArray(arr1);
printArray(arr2);
break;
}
}
System.out.println(succeed ? "pass" : "shit!");
}
}
各位可以自行测试上面的排序结果,如果出错了恳请各位及时联系笔者,笔者会即使进行改正.
感谢阅读到末尾的各位的支持!