最近打算重新过一遍算法,所以在此汇总一下常用的算法。
这次会更加注重算法的原理,剔除杂七杂八的东西,用最简单的代码体现算法的原理,希望大家可以和我一样有所收获。
冒泡排序
冒泡排序是我们接触到的第一个排序算法,原理如下:
- 比较相邻的元素,如果第一个比第二个大,就交换位置。
- 从开始一对到末尾一对做相邻的动作,做完后最后一个位置应是最大数
- 每次比较忽略上一轮的最大数,重复N-1次完成排序
所以时间复杂度为O(N2),额外空间复杂度O(1)
冒泡可实现稳定性(排序后相同元素相对次序不变)
代码如下:
java语言,swap交换算法自己写下即可
public static void bubbleSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int e = arr.length - 1; e > 0; e--) {
for (int i = 0; i < e; i++) {
if (arr[i] > arr[i + 1]) {
swap(arr, i, i + 1);
}
}
}
}
插入排序
例如排序一个长度为N的数组
- 把数组看成两个部分,第1个有序,2-N的元素无序
- 依次把无序中的元素往有序里插入,插入一次后变成,第1-2的元素有序,3-N的元素无序。
- 重复到无序中没有元素即完成排序。
所以最坏时间复杂度为O(N2),最好时间复杂度为O(N)
额外空间复杂度为O(1)
插入排序也可以实现成稳定性
代码如下:
public static void insertionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 1; i < arr.length; i++) {
for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
swap(arr, j, j + 1);
}
}
}
注意往有序部分插入时是从后往前比较,你们细品。
选择排序
类似于冒泡,冒泡时相邻元素比较交换,这里是找出无序部分的最值。
- 在第1-N个元素中找到最小的那个与第一个元素(0号位)交换
- 在2-N找最小的元素与第二个元素(1号位)交换
- 一共重复N-1次
因此时间复杂度为O(N2) 额外空间复杂度为O(1)
是不稳定算法。(同值元素的相对次序会被破坏)
例如:2 2 2 1 3
代码如下:
public static void selectionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
minIndex = arr[j] < arr[minIndex] ? j : minIndex;
}
swap(arr, i, minIndex);
}
}
归并排序
归并,就是讲两个有序数组合并成一个数组,过程可以形容为:
- A,B两个数组各设置两个标记位m,n,起始都指向0位置
- 两个标记为比较,如果A[m]<=B[n],则A[m]存入新数组,m++否则B[n]存入新数组,n++
- A,B数组中的一个比较完毕,则另外一个数组剩下元素直接按序存入新数组。
因为是两个有序数组所以可以这么做。而归并排序就是利用这点来递归,一个无序数组,不断二分二分,最后一定会有两个数组都只有一个元素,一个元素的数组可视为有序,再一步步追溯来排序。
归并排序的时间复杂度O(N*logN) 额外空间复杂度O(N)
归并排序可以实现稳定性
public static void mergeSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
sortProcess(arr, 0, arr.length - 1);
}
public static void sortProcess(int[] arr, int l, int r) {
if (l == r) {
return;
}
int mid = l + ((r - l) >> 1);
sortProcess(arr, l, mid);
sortProcess(arr, mid + 1, r);
merge(arr, l, mid, r);
}
public static void merge(int[] arr, int l, int m, int r) {
int[] help = new int[r - l + 1];
int i = 0;
int p1 = l;
int p2 = m + 1;
while (p1 <= m && p2 <= r) {
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= m) {
help[i++] = arr[p1++];
}
while (p2 <= r) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[l + i] = help[i];
}
}
快速排序
基于分治的思想,是冒泡排序的改进型。首先在数组中选择一个基准点(该基准点的选取可能影响快速排序的效率,后面讲解选取的方法),然后分别从数组的两端扫描数组,设两个指示标志(low指向起始位置,high指向末尾),首先从后半部分开始,如果发现有元素比该基准点的值小,就交换low和high位置的值,然后从前半部分开始扫秒,发现有元素大于基准点的值,就交换low和high位置的值,如此往复循环,直到low>=high,然后把基准点的值放到high这个位置。一次排序就完成了。以后采用递归的方式分别对前半部分和后半部分排序,当前半部分和后半部分均有序时该数组就自然有序了。
因为时间问题,写好久啦,快排原理概括就引用:包子的百草园的概括了,只附上我写的代码。
方法一单指针
public static void quickSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
quickSort(arr, 0, arr.length - 1);
}
public static void quickSort(int[] arr, int l, int r) {
if (l < r) {
swap(arr, l + (int) (Math.random() * (r - l + 1)), r);
int p = split(arr, l, r);
quickSort(arr, l, p-1);
quickSort(arr, p+1, r);
}
}
public static int split(int[] arr,int l,int r) {
int index=l;//期望位置
int x=arr[l];
for(int j=l+1;j<=r;j++) {
if(arr[j]<x) {
index++;
if(index!=j) {
swap(arr, index, j);
}
}
}
swap(arr, l, index);
return index;
}
方法二:双指针
public static void quickSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
quickSort(arr, 0, arr.length - 1);
}
public static void quickSort(int[] arr, int l, int r) {
if (l < r) {
swap(arr, l + (int) (Math.random() * (r - l + 1)), r);
int p = partition(arr, l, r);
quickSort(arr, l, p-1);
quickSort(arr, p+1, r);
}
}
public static int partition(int a[],int l,int r) {
int x=a[l];
int i=l;
int j=r;
while(i<j) {
while(i<j && a[j]>=x)
j--;
while(i<j && a[i]<=x)
i++;
//if(i<j)
swap(a, i, j);
}
swap(a, i, l);
return i;
}
大神炫技法:不提倡就少用个参数,但是划分更清晰
这里是每次排序分成三个部分 小于基准值,等于基准值,大于基准值。
public static void quickSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
quickSort(arr, 0, arr.length - 1);
}
public static void quickSort(int[] arr, int l, int r) {
if (l < r) {
swap(arr, l + (int) (Math.random() * (r - l + 1)), r);
int[] p = partition(arr, l, r);
quickSort(arr, l, p[0] - 1);
quickSort(arr, p[1] + 1, r);
}
}
public static int[] partition(int[] arr, int l, int r) {
int less = l - 1;
int more = r;
while (l < more) {
if (arr[l] < arr[r]) {
swap(arr, ++less, l++);
} else if (arr[l] > arr[r]) {
swap(arr, --more, l);
} else {
l++;
}
}
swap(arr, more, r);
return new int[] { less + 1, more };
}
堆排序
swap交换函数自己补下噢,原理以后填坑,先贴代码了。
public static void heapSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 0; i < arr.length; i++) {
heapInsert(arr, i);
}
int size = arr.length;
swap(arr, 0, --size);
while (size > 0) {
heapify(arr, 0, size);
swap(arr, 0, --size);
}
}
public static void heapInsert(int[] arr, int index) {
while (arr[index] > arr[(index - 1) / 2]) {
swap(arr, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
public static void heapify(int[] arr, int index, int size) {
int left = index * 2 + 1;
while (left < size) {
int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
largest = arr[largest] > arr[index] ? largest : index;
if (largest == index) {
break;
}
swap(arr, largest, index);
index = largest;
left = index * 2 + 1;
}
}
非比较排序
桶排序。计数排序。有兴趣可以自行了解。
额外部分 工程上使用的综合排序
快排,堆排都不具有稳定性,
不追求稳定性一般用快排,
追求稳定性使用归并排序,
比较排序,还需要学会使用比较器。