算法基础一

认识时间复杂度:

时间复杂度为,在一个算法的流程中,常数操作数量的指标,这个指标叫做O(字母O而非数字零),big O。具体为 ,如果常数操作数量的表达式中,只要高阶项,不要低阶项,也不要高阶项系数,剩下的部分记为f(N),那么该算法的时间复杂度为O(f(N))。

比如:我们求一个数组中的最大值

求数组中的最大值,一般思路是把数组遍历一遍,设置一个中间变量max来记录最大值,中间用来取数组中的数的操作,赋值操作,这些操作成常数操作(所谓常数操作,指的是完成这个操作的时间和数据量无关,它是一个固定的值)。在数组中的寻址代价是big O(1)的,即常数操作,拿到的这个数和max进行比较,比较的这个操作也是常数操作。从0位置遍历到n-1位置,我们花费的总代价为:n*常数操作。即认为完成这些操作的代价为C*n(其中C为常数),我们就说它是big O(n)的算法。

一 冒泡排序(Bubble Sort)

冒泡排序是一种典型的交换排序算法,通过交换数据元素的位置进行排序。

时间复杂度O(N^2),额外的空间复杂度O(1),实现可以做到稳定性。

  • 基本思想

冒泡排序的基本思想是:从无序序列头部开始,进行两两比较,根据大小交换位置,直到最后将最大(小)的数据元素交换到了序列的尾部,从而成为有序序列的一部分;下一次继续这个过程,直到所有数据元素都排好序。

算法的核心在于每次通过两两比较交换位置,选出剩余无序序列里最大(小)的数据元素放到尾部。

下面我们来看一下冒泡排序的简单例子(将序列按从小到大排):

假设有一个无序序列  { 5, 1, 7, 2, 9 }

第一趟排序:通过两两比较,找到第一大的数值 9 ,将其放在序列的末尾。{ 1, 5, 2, 7, 9 }

第二趟排序:通过两两比较,找到第二大的数值 7 ,将其放在序列的倒数第二位。{ 1, 2, 5, 7, 9 }

第三趟排序:通过两两比较,找到第三大的数值 5 ,将其放在序列的倒数第三位。{ 1, 2, 5, 7, 9 }

...

所有元素已经有序,排序结束。 

 

其实,流程大致是:

          在0-(n-1)进行操作

          在0-(n-2)进行操作

          在0-(n-3)进行操作

          ...

直到遍历结束,序列最终有序。

  • 代码转换

假设要对一个大小为 n 的无序序列进行升序排序(即从小到大排列)。 

(1)因为遍历第一次需要找到 0-(n-1)上最大的值,第二次遍历需要找到 0 - (n-2)上最大的值,因此,需要设置一个外循环,用来进行遍历;

(2)在遍历过程中,需要进行两两比较(第一次需要从0-(n-2)进行两两比较,第二次需要从0-(n-3)进行两两比较),因此,需要设置一个内循环来进行两两之间的比较;

(3)单独考虑特殊情况,数组长度为0或者小于2(即1)时不作处理。

以下是java代码的实现:

 1 public class BubbleSort {
 2 
 3     @Test
 4     public void bubbleSort(int arr[]) {
 5         //数组为空或者只有一个元素,直接返回
 6         if(arr==null || arr.length<2) {
 7             return;
 8         }
 9         else {
10             //end代表每次将0-end元素中最大的数放到end上,直到第一个元素
11             for(int end=arr.length-1; end>=0; end--) {
12                 //从0-(end-1)开始两两比较
13                 for(int i=0; i<end; i++) {
14                     if(arr[i]>arr[i+1])
15                         swap(arr,i,i+1);
16                 }
17             }
18         }
19     }
20     //交换两个数的方法
21     public void swap(int[] arr, int i, int j) {
22         int temp = arr[i];
23         arr[i] = arr[j];
24         arr[j] = temp;
25     }
26 }
View Code

 

 

 以上就是冒泡排序的简单介绍。

二 对数器

下面我们来简单介绍一个对数器的概念及其使用。

对数器是用来测试代码正确性的,我们在找不到合适的oj系统(online judge)测试自己的代码时,可以自己写一个对数器对代码进行测试。

设计对数器的一般步骤为:

  1. 一个你要测试正确性的方法a; 
  2. 实现一个绝对正确即使复杂度不好的方法b; 
  3. 实现一个随机样本产生器; 
  4. 实现比对的方法; 
  5. 把方法a和方法b比对很多次来验证方法a是否正确 
  6. 如果有一个样本使得比对出错,打印样本分析是哪个方法出错 
  7. 当样本数量很多时比对测试依然正确,可以确定方法a已经正确

在设计对数器的时候,一定要保证程序的绝对正确,可能时间复杂度很坏,但是只要正确即可。在验证的时候可以挑一些你认为极端的情况进行正确性的验证,这样能够快速进行比对。

我们通过一个例子来简单了解一下:

1. 我们要验证的方法a

public void bubbleSort(int arr[]) {
        //数组为空或者只有一个元素,直接返回
        if(arr==null || arr.length<2) {
            return;
        }
        else {
            //end代表每次将0-end元素中最大的数放到end上,直到第一个元素
            for(int end=arr.length-1; end>=0; end--) {
                //从0-(end-1)开始两两比较
                for(int i=0; i<end; i++) {
                    if(arr[i]>arr[i+1])
                        swap(arr,i,i+1);
                }
            }
        }
    }
    //交换两个数的方法
    public void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
View Code

2. 实现一个绝对正确即使复杂度不好的方法b

1     // 一个绝对正确的方法,调用java自带的排序方法
2     public void rightMethod(int[] arr) {
3         Arrays.sort(arr);
4     }
View Code

 

3. 实现一个样本产生器

1 //产生测试数据  
2 //Math.random()  ->  double[0,1)
3 2 public static int[] testData(int len,int value){  
4 3     int arr[] = new int[len];  
5 4     for (int i = 0; i < arr.length; i++) {  
6 5     arr[i] = (int) ((value+1)*Math.random()-    (value+1)*Math.random());  
7 6         }  
8 7     return arr;  
9 8 } 
View Code

 

4. 实现比对的方法

 1 //判断两个数组是否相等
 2 public static boolean isEqual(int[] arr1, int[] arr2) {
 3     if((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null))
 4         return false;
 5     if(arr1 == null && arr2 == null)
 6         return true;
 7     if(arr1.length != arr2.length)
 8         return false;
 9     for(int i = 0;i < arr1.length;i++) {
10         if(arr1[i] != arr2[i])
11             return false;
12     }
13     return true;
14 }
View Code

5.把方法a和方法b比对很多次来验证方法a是否正确 

6.如果有一个样本使得比对出错,打印样本分析是哪个方法出错 
7.当样本数量很多时比对测试依然正确,可以确定方法a已经正确

 1 public static void main(String[] args) {  
 2     int len = 10;//测试数组长度  
 3     int val = 100;//测试数据范围  
 4     int times = 500000;//测试数据量  
 5     boolean isOK = true;  
 6     for (int i = 0; i < times; i++) {  
 7         int arr[] = testData(len, val);  
 8         int arr1[] = Arrays.copyOf(arr, len);  
 9         int arr2[] = Arrays.copyOf(arr, len);  
10         bubbleSort(arr1);  
11         systemSort(arr2);  
12         if( ! isEqual(arr1, arr2)){  
13             printArr(arr1);  
14             printArr(arr2);  
15             isOK = false;  
16             break;  
17         }  
18     }  
19     System.out.println(isOK);  
20 } 
View Code

 至此,一个对数器就实现了,正确的使用对数去对于学习和工作来说是有很大帮助的。

三 选择排序

选择排序(Selection sort)同样也是最经典最简单的排序算法之一,它的特点就是简单直观。

排序的原理:在0-(n-1)(元素总数目为n)里面选择最小(大)的一个元素,放在0号位置;

      在1-(n-1)里面选择最小(大)的一个元素,放在1号位置;

      ...

选依次循环,直到排序完成。

和上面介绍的冒泡排序不同的是,选择排序只需要交换一次。

下面我们来看代码:

 1 public class SelectionSort{
 2     
 3     public static void selectionSort(int[] arr) {
 4         if(arr==null || arr.length<2) {
 5             return ;
 6         }
 7         //此处我们选择最小的进行选择排序
 8         for(int i=0; i<arr.length; i++) {
 9             int minIndex = i;
10             for(int j=i+1; j<arr.length-1; j++) {
11                 minIndex = arr[j]<arr[minIndex] ? j : minIndex;
12             }
13             swap(arr, i, minIndex);
14         }
15     }
16     
17     public static void swap(int[] arr, int i, int j) {
18         int temp = arr[i];
19         arr[i] = arr[j];
20         arr[j] = temp;
21     }
22     
23 }
View Code

 四 插入排序

插入排序原理:它是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。我们假设第一个元素排好,之后的元素对排好的部分从后向前比较并逐一移动。

下面我们来看代码:

View Code

五 时间复杂度,空间复杂度的简单比较以及master公式

插入排序,选择排序,冒泡排序的时间复杂度都是big (N2) ,空间复杂度big O(1)(因为是有限的几个变量,big O(1)就是常数的意思);

而归并排序,快速排序,堆排序的时间复杂度是big O(N*logN),空间复杂度:

                                  归并排序:big O(N)

                                  快速排序:big O(logN)

                                  堆排序:big O(1)

 

master公式(也称主方法)是用来利用分治思想来计算时间复杂度,分治思想中使用递归来求解问题分为三步走,分别为分解、解决和合并,主方法的表现形式:

T [n] = aT[n/b] + f (n)(直接记为T [n] = aT[n/b] + T (N^d))

其中 a >= 1 and b > 1 是常量,其表示的意义是:n表示问题的规模,a表示递归的次数也就是生成的子问题数,b表示每次递归是原来的1/b之一个规模,f(n)表示分解和合并所要花费的时间之和。

解法

  1. 当d<logba时,时间复杂度为O(n^(logb a))
  2. 当d=logba时,时间复杂度为O((n^d)*logn)
  3. 当d>logba时,时间复杂度为O(n^d)

六 归并排序

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

归并排序介绍:

上图是归并排序的一个大致的过程。归并排序的思想是分治思想,具体做法是:

把一个数组分为两部分,左半部分和右半部分,然后分别将这两个部分再分解,直到各个部分长度为1,然后进行合并,合并的过程中始终保持左边小右边大(即左半部分和右半部分分别从小到大排好序),然后再将左半部分和右半部分进行合并(在合并的过程中创建一个辅助数组用来存放排好序的数组,最后将排好序的数组赋值给原数组),归并排序结束。

下面我们来看Java代码的实现:

 

 1 public class MergeSort {
 2 
 3     
 4     public static void mergeSort(int[] arr) {
 5         if(arr==null || arr.length<2)
 6             return;
 7         mergeprocess(arr, 0, arr.length-1);//递归调用
 8     }
 9     
10     public static void mergeprocess(int[] arr, int L, int R) {
11         if(L==R)
12             return;
13         int mid = L + (R-L)/2;
14         mergeprocess(arr, L, mid);
15         mergeprocess(arr, mid+1, R);
16         merge(arr, L, R);
17     }
18     
19     public static void merge(int[] arr, int L, int R) {
20         int[] help = new int[R-L+1];
21         int mid = L + (R-L)/2;
22         int p = L;
23         int q = mid+1;
24         int i = 0;
25         while(p<=mid && q<=R) {//循环比较加入到辅助数组
26             if(arr[p]<=arr[q])
27                 help[i++] = arr[p++];
28             if(arr[p]>arr[q])
29                 help[i++] = arr[q++];
30         }
31         
32         while(p<=mid) {//必有一个没循环结束
33             help[i++] = arr[p++];
34         }
35         while(q<=R) {
36             help[i++] = arr[q++];
37         }
38         
39         //辅助数组复制给原数组
40         for(i=0; i<help.length; i++) {
41             arr[L+i] = help[i];
42         }
43         
44     }
45     
46     //swap method
47     public static void swap(int i, int j, int[] arr) {
48         int temp = arr[i];
49         arr[i] = arr[j];
50         arr[j] = temp;
51     }
52 
53     //for test
54     public static void printArray(int[] arr) {
55         if(arr==null)
56             return;
57         for(int i=0; i<arr.length; i++) 
58             System.out.print(arr[i]);
59         System.out.println();
60     }
61     public static void main(String[] args) {
62         int[] arr = {6,2,9,3,8,1,2};
63         mergeSort(arr);
64         printArray(arr);
65     }
66         
67 }
View Code

 

归并排序的使用——小和问题

利用归并排序的分治思想来求解小和问题,在左半部分(以左半部分为例)合并的过程中,如果排序时不交换(即左边<右边),则产生一个小和;在左半部分和右半部分合并的过程中,左半部分中的数如果小于右半部分的数,则利用右半部分的下标(因为右半部分已经排好序,故右半部分中的数(大于左半部分的数),则小和的个数是——右半部分的数的末尾数字下标-比对的右半部分的数对应的下标)即可得到左右部分合并过程中的小和数。

例子:
  [1,3,4,2,5]
  1左边比1小的数,没有;
  3左边比3小的数,1;
  4左边比4小的数,1、3;
  2左边比2小的数,1;
  5左边比5小的数,1、3、4、2;
所以小和为1+1+3+1+1+3+4+2=16

下面我们来看Java代码实现:

 1 package Sort;
 2 
 3 public class SmallSum {
 4 
 5     public static int smallSum(int[] arr) {
 6         if(arr==null || arr.length<2)
 7             return 0;
 8         return mergeSort(arr, 0, arr.length-1);
 9     }
10     
11     public static int mergeSort(int[] arr, int L, int R) {
12         if(L==R)
13             return 0;
14         int mid = L + (R-L)/2;
15         return mergeSort(arr, L, mid) +
16               mergeSort(arr, mid+1, R)+
17               merge(arr, L, mid, R);
18     }
19     
20     public static int merge(int[] arr, int L, int mid, int R) {
21         
22         int[] help = new int[R-L+1];
23         int res = 0;
24         int p = L;
25         int q = mid+1;
26         int i = 0;
27         while(p<=mid && q<=R) {
28             if(arr[p]<arr[q]) {
29                 res += arr[p]*(R-q+1);
30                 help[i++] = arr[p++];
31             }
32             if(arr[p]>=arr[q]) {
33                 res += 0;
34                 help[i++] = arr[q++];
35             }
36         }
37         while(p<=mid) {
38             help[i++] = arr[p++];
39         }
40         while(q<=R) {
41             help[i++] = arr[q++];
42         }
43         
44         for(i=0; i<help.length; i++) {
45             arr[L+i] = help[i];
46         }
47         return res;
48     }
49     
50     //for test
51     public static void printArray(int[] arr) {
52         if(arr==null)
53             return;
54         for(int i=0; i<arr.length; i++) 
55             System.out.print(arr[i]);
56         System.out.println();
57     }
58     
59     public static void main(String[] args) {
60         int[] arr = {1,3,2,5,2};
61         int res = smallSum(arr);
62         System.out.println(res);
63     }
64 
65 }
View Code

 

posted @ 2018-05-30 18:26  CiaoBello  阅读(682)  评论(0编辑  收藏  举报