> 文档中心 > 复杂度和简单排序算法_笔记01

复杂度和简单排序算法_笔记01

目录

          • 01_选择排序
          • 02_冒泡排序
            • swap() (用^运算符)
            • ^的应用
          • 03_插入排序
            • 二分
          • 对数器
            • 为什么要有对数器?
            • 对数器的实现
          • 递归行为和递归行为时间复杂度的估算
            • 递归时间复杂度的估算

本文知识点记录自左程云 算法与数据结构基础到高级教程 图片截取自视频中

常数操作

和数据量无关的操作

+ - * / 位运算 都是常数操作;int a= arr[i]//常数操作int b= list.get(i)    //非常数操作,list是一个链表
时间复杂度

常数操作的数量写成表达式,不要低阶项,除去最高项的系数剩下的。例如a*N²+b*N+c,写为O(N²)

对任何算法,估算其时间复杂度时,按照最差情况来估计,即耗费的时间最多是多少,取最大阶作为时间复杂度

当时间复杂度指标相同,需要比较不同算法流程常数项时间的时候,不需要再分析了,用具体的数据样本测试。因为虽然可以估算出常数操作数量,但是不同常数操作花费的时间不同,比较常数项时间就不能仅仅靠常数项的值的大小

加减法和位运算进行一次花费的时间是不同的

01_选择排序

时间复杂度:O(N^2)

//selection sortstatic void selectionSort(int[] arr){    for (int i = 0; i < arr.length-1; i++) { int min = arr[i]; for (int j = i+1; j < arr.length; j++) {     if(arr[j] < min){  swap(arr[i],arr[j]);     } }    }}
02_冒泡排序

时间复杂度:O(N^2)

//bubble sortstatic void bubbleSort(int[] arr){    for (int i = arr.length-1; i > 1; i--) { for (int j = 0; j < i; j++) {     if(arr[j] > arr[j+1])  swap(arr[j], arr[j+1]); }    }}
swap() (用^运算符)

异或运算^,按位 相异为1,相同为0,可以理解为无进位相加,满足交换律、结合律,即一堆数进行^操作,与其顺序无关

为什么^满足 结果与顺序无关的性质?

根据^的无进位相加来理解:

一批数,最终结果的每一位上是0还是1,与该位上有几个1相加有关。比如,若有N个数相^,某一位上,1出现了奇数次,结果值对应的该位上,值就为1;1出现偶数次,结果值对应的该位上,值为0。显然,结果与顺序无关。

若两个数存储在两个不同的内存区域中,则可以这样交换

int a=10,b=20;void swap(int a, int b){    //若传入参数是数组元素,则两数下标不能相同a= a^b;b= a^b;a= a^b;}
^的应用

剑指 Offer 56 - I. 数组中数字出现的次数

//在一个数组中,只有两个数出现了奇数次,其他数都出现了偶数次,返回这两个数class Solution {    //从n右边往左,找第一个值为1的位,在该处,a与b的值是不同的,根据这一位上的值可以把a和b从nums里面分到两个区域    int GetRightOne(int n){ return n^(n&(n-1));    }    public int[] singleNumbers(int[] nums) { int[] res = new int[2]; res[0] = 0; res[1] = 0; int tmp = 0; for(int i = 0; i < nums.length; ++i){     tmp ^= nums[i];    //temp=a^b(a b即是要返回的两数) } int rightone = GetRightOne(tmp); for(int i = 0; i < nums.length; ++i){     if((nums[i] & rightone) == rightone){  res[0] ^= nums[i];     }else{  res[1] ^= nums[i];     } } return res;    }}
03_插入排序

时间复杂度:O(N^2)

//insertion sort, method bstatic void insertionSort(int[] arr){    for (int i = 1; i < arr.length; i++) { for (int k = i; k > 0 && (arr[k] < arr[k-1]); --k) {     swap(arr[k], arr[k-1]); }    }}
二分

二分与数组有序无序无关,不是只有有序才能用二分

  • 在一个有序数组中,找某个数是否存在
O(logN) //时间复杂度,logN默认以2为底//每次对半分,找比较中间的数和要找的数的大小关系,最坏的情况:分到不能再分才找到,即进行了logN次比较
  • 在一个有序数组中,找 >=某个数最左侧的位置
  • 局部最小值

给一个无序且互不相等的数组,求其局部最小值。局部最小值定义为:若a[0]a[n-1],n-1位置局部最小;若a[i]<a[i-1] && a[i]<a[i+1] ,i位置为局部最小。

在这里插入图片描述

求L和R的中点(L和R是数组索引):

int mid = L + (R-L)>>1;

这样写不容易越界(当L和R非常大时,L+R可能溢出);

(R-L)>>1 比(R-L)/2 更快。

对数器
  • 有一个想测试的方法a(复杂度更好)
  • 实现复杂度不好,但是容易实现的方法b;
  • 实现一个随机样本产生器
  • 方法a和方法b跑相同的随机样本,看看结果是否一致
  • 若有一个样本使得比对结果不一致,打印样本进行人工干预,更改方法a或方法b
  • 当样本数量很多时,比对测试依然正确,可以确定方法a已经正确
为什么要有对数器?

各种OJ也是人工输入的测试用例,不排除你的算法有问题时,测试用例没有测出来(没包含进所有的情况),所以自己设计对数器,进行人工干预,这是最稳妥的测试方法,不会出现上面的错误。

对数器的实现
import java.util.Arrays;public class Test {    //交换两数    static void swap(int a, int b){ int tmp = a; a = b; b = tmp;    }    //若确定a和b不指向同一块内存,则swap可以这样实现//    static void swap(int a, int b){// a = a ^ b;// b = a ^ b;// a = a ^ b;//    }    //打印数组    static void print(int[] arr){ for(int a : arr)     System.out.print(a+" ");    }    //bubble sort    static void bubbleSort(int[] arr){ for (int i = arr.length-1; i > 1; i--) {     for (int j = 0; j < i; j++) {  if(arr[j] > arr[j+1])      swap(arr[j], arr[j+1]);     } }    }    //selection sort, method a    static void selectionSort(int[] arr){ for (int i = 0; i < arr.length-1; i++) {     int min = arr[i];     for (int j = i+1; j < arr.length; j++) {  if(arr[j] < min){      swap(arr[i],arr[j]);  }     } }    }    //insertion sort, method b    static void insertionSort(int[] arr){ for (int i = 1; i < arr.length; i++) {     for (int k = i; k > 0 && (arr[k] < arr[k-1]); --k) {  swap(arr[k], arr[k-1]);     } }    }    //generate random array    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 void main(String[] args) { //logarithmic detector int testTime = 500000; int maxSize = 100; int maxValue = 100; boolean sign = true; for (int i = 0; i < testTime; i++) {     int[] arr1 = generateRandomArray(maxSize, maxValue);     int[] arr2 = Arrays.copyOf(arr1,arr1.length);     selectionSort(arr1);     bubbleSort(arr2);     if(!Arrays.equals(arr1,arr2)){  sign = false;  break;     } } System.out.println(sign ? 666 : "gg"); if((sign ? 666 : "gg") == "gg"){     int[] arr1 = generateRandomArray(maxSize, maxValue);     int[] arr2 = Arrays.copyOf(arr1,arr1.length);     print(arr1);     selectionSort(arr1);     print(arr1);     bubbleSort(arr2);     print(arr2); }    }}
递归行为和递归行为时间复杂度的估算

递归行为可以理解为多叉树的遍历,每一个节点要计算值,必须依赖当前节点的子节点。由于这种依赖关系,在没执行到叶子节点前,就相当于不断把节点压入栈中(这实际上是一个递推的过程);到了叶子节点,不再依赖其他节点就可以返回当前值了,于是相当于弹栈(相当于回溯过程)。

在这里插入图片描述

递归时间复杂度的估算

master公式(适用于子问题等规模的递归时间复杂度的求解)

T(N) = a*T(N/b) + O(N^d)

lo g b ⁡a<d→O( N d )log_b⁡a<d→O(N^d ) logba<dO(Nd)

lo g b a>d→O( N x )......x=lo g b alog_ba>d→O(N^x)......x=log_ba logba>dO(Nx)......x=logba

lo g b a=d→O( N d ∗logN)log_ba=d→O(N^d*logN) logba=dO(NdlogN)

算法的复杂度与Master定理(必看!!!)

户口查询网