这学期我们导师带我们的算法课。没有办法,必须好好听讲。所以,趁着这波好好听讲,我打算总结一下这半个学期来上的算法课的知识点吧。讲算法必然从排序讲起,排序+算法时间复杂度分析是可以将二者巩固的最扎实的。
1.递归式
递归思想常用来解决重复性较高的问题,何为递归呢?递归是指在函数的定义中使用函数自身的方法。简单来说就是自己调用自己。可以理解为整个问题的处理过程中,处理方法基本一致。但是这里主要不是介绍递归思想,而是算法复杂度分析中的递归式。举个栗子,如下图:
在上图中,每一个问题可以根据递归式一步一步分解,最终,我们可以得出,递归树的高度是lg4_n.所以,根据换底公式外加数学方面的推导,可以得出,整个问题的时间复杂度是O(n^2).
其实这面有一个叫MASTER公式的神器,只不过我记不住而已。
2.简单排序
当然,这不是主要的。主要的是下面的排序算法。
首先比较简单地有冒泡排序,插入排序,和选择排序。
**
* Created by ql on 2018/5/6.
*/
public class Sort {
public static void select_sort(int [] arr){
//选择排序,每次选择最小的值放在相应的位置。
if (arr==null||arr.length==0)return ;
for (int i=0;i<arr.length;i++){
int minIndex=i;
for (int j=i+1;j<arr.length;j++){
minIndex=arr[j]<arr[minIndex]?j:minIndex;
swap(arr,minIndex,i);
}
}
}
public static void insert_sort(int []arr){
//插入排序
if (arr==null||arr.length==0)return ;
for (int i=1;i<arr.length;i++){
for (int j=i;j>=0&&arr[j-1]>arr[j];j--){
swap(arr,j,j-1);
}
}
}
public static void bubble_sort(int []arr){
//冒泡排序
if (arr==null||arr.length==0)return ;
for (int i=1;i<arr.length;i++){
for (int j=0;j<i;j++){
if(arr[j]>arr[j+1]){
swap(arr,j,j+1);
}
}
}
}
public static void swap(int[] arr, int i, int j) {
if (i != j) {
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
}
上述的算法,时间复杂度都是O(n^2)
3.复杂排序
下面是基于分治思想的归并排序。可以说归并排序是一种递归的体现。其主要思想就是先将数组按照一定的规则划分,直到不能划分为止,然后进行归并,在归并的过程中使用插入排序算法。直至归并后数组的大小为原数组大小为止。
整个过程的就是先划分再归并。
递归式表示为:
所以,整个问题的时间复杂度为O(nlg_n)
附上归并排序的代码:
public static void merge_sort(int []arr){
if (arr==null||arr.length==0)return ;
merge_sort(arr,0,arr.length-1);
}
public static void merge_sort(int []arr,int l,int r){
if(l>=r||l<0||r>arr.length) return ;
int mid=1+((r-l)>>1);
merge_sort(arr,l,mid);
merge_sort(arr,mid+1,r);
merge(arr,l,mid,r);
}
public static void merge(int arr[],int l,int mid,int r){
int help[]=new int [r-l+1];
int i=0;int p1=l;int p2=mid+1;
while(p1<=mid&&p2<=r){
help[i++]=arr[p1]<arr[p2]?arr[p1++]:arr[p2++];
}
while(p1<=mid){
help[i++]=arr[p1++];
}
while(p2<=r){
help[i++]=arr[p2++];
}
for(int j=0;j<arr.length;j++){
arr[j]=help[j];
}
}
常考的问题:求数组中逆序对的个数
快速排序:与归并排序一样,快排也是基于分治的思想。与前者不同的是,快排有一个基准值,且划分的要求不同。快排一般选择最后一个值作为基准,然后递归调用快速排序的方法使得该基准值最终的位置的左边都是小于它的,右边的值都是大于它的。注意每次都是确定一个值得位置。可能你会觉得这个过程很复杂,为何称为快速排序呢。没错,它的过程确实很复杂,而且每次只能确定一个值得位置,所以,当数组中数字多的时候,每次仅仅能确定一个值得位置,就显得很慢。当然这只是显得很慢,实质上经过众多实验,快排在数组相对无序的情况下是很快地。因为其划分过程中,能够确定一个值左边的都比他小,右边的都比它大。这样递归下去,快排的效率就很高了。
public class QuickSort {
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 };
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
堆排序:
堆,与堆排序都是很高效的。对应一种数据结构我们称之为优先队列。在堆排序算法中,我们使用的是大顶堆,小顶堆通常在构造优先队列中使用。堆可以被看成是一棵树,结点在堆中的高度定义为本结点到叶子的最长简单下降路径上边的数目。堆排序常用来解决中位数问题。代码以前写过的堆排序过程
上述排序过程都是基于比较的排序算法,时间复杂度对应为O(nlg_n)。其中堆排序和归并排序都是渐进最有的比较排序算法。
4.线性时间排序算法
线性时间排序算法有计数排序和基数排序,有的人可能会认为这俩是一种排序算法(我当时就是这样,以为有的博客有错别字)
计数排序:计数排序的基本思想就是对每一个输入的元素x,确定出小于x的元素个数,有了这一信息,就可以把x直接放到它在最终输出数组中的位置上。例如,有是17个元素小于x,则x就属于第18个输出位置。当有几个元素相同时,这个方案要略做修改,因为不能把他们放在同一个输出位置上了。
对应着伪代码的思想。我们可以有一下java代码实现:
public static void countSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int max = Integer.MIN_VALUE;
for (int i = 0; i < arr.length; i++) {
max = Math.max(max, arr[i]);
}
int[] count = new int[max + 1];
for (int i = 0; i < arr.length; i++) {
count[arr[i]]++;
}
int i = 0;
for (int j = 0; j < count.length; j++) {
while (count[j]-- > 0) {
arr[i++] = j;
}
}
}
基数排序:首先你要理解什么是基数。其次,基数排序的核心就是按位排序。因为任何一个数都有高位和低位,所以我们有按高位排序和按低位排序的两种方法。实践证明,按低位排序相对于按高位排序更高效一些。
附上代码:(很魔性)
public static void radixSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
radixSort(arr, 0, arr.length - 1, maxbits(arr));
}
public static int maxbits(int[] arr) {
int max = Integer.MIN_VALUE;
for (int i = 0; i < arr.length; i++) {
max = Math.max(max, arr[i]);
}
int res = 0;
while (max != 0) {
res++;
max /= 10;
}
return res;
}
public static void radixSort(int[] arr, int begin, int end, int digit) {
final int radix = 10;
int i = 0, j = 0;
int[] count = new int[radix];
int[] bucket = new int[end - begin + 1];
for (int d = 1; d <= digit; d++) {
for (i = 0; i < radix; i++) {
count[i] = 0;
}
for (i = begin; i <= end; i++) {
j = getDigit(arr[i], d);
count[j]++;
}
for (i = 1; i < radix; i++) {
count[i] = count[i] + count[i - 1];
}
for (i = end; i >= begin; i--) {
j = getDigit(arr[i], d);
bucket[count[j] - 1] = arr[i];
count[j]--;
}
for (i = begin, j = 0; i <= end; i++, j++) {
arr[i] = bucket[j];
}
}
}
public static int getDigit(int x, int d) {
return ((x / ((int) Math.pow(10, d - 1))) % 10);
}