meface/数据结构与算法.md

5.4 KiB
Raw Blame History

1. 数学知识基础

1.1 基本概念

1.1.1 指数

X^AX^B = X^{A+B}

X^A/X^B = X^{A-B}

X^N+X^N = 2X^N

2^N + 2^N = 2^{N+1}

(X^A)^B = X^{AB}
1.1.2 对数

在计算机科学中默认所有对数都是以2为底的。


X^A = B <==>  log_xB = A

log_AB = log_cB/log_cA

logAB = logA + logB

log1=0,log2=1,log1024=10,log1048576=20
1.1.3 级数

\sum_{i=0}^{N}2^i=2^{N+1}-1

\sum_{i=0}^NA^i=(A^{N+1}-1)/(A-1)

1.2 关于递归

base case (基准情况):也就是递归头,结束循环的情况。

circular logic循环推理方法内部调用自身

递归的四个基本法则:

  • 基准情形base case必须有无需递归就能解出的结果
  • 不断推进making progress对于那些需要递归求解的情形每一次递归调用都必须要使状况朝向一种基准情况推进
  • 设计法则:假设所有的递归调用都能运行
  • 合成效益法则:在求解一个问题的同一实例时,切勿在不同的递归调用中做重复性的工作。

2. 算法分析

算法是为求解一个问题需要遵循的、被清楚指定的简单指令的集合。对于一个问题,一旦某种算法给定并且(以某种方式)被确定是正确的,那么重要的一步就是确定该算法将需要多少时间或空间等资源量的问题。从算法所占的时间和空间两个维度去衡量算法的优略。

2.1 时间复杂度

大O符号表示法T(n) = O(f(n))

其中f(n) 表示每行代码执行次数之和,而 O 表示正比例关系,这个公式的全称是:算法的渐进时间复杂度

大O符号表示法并不是用于来真实代表算法的执行时间的它是用来表示代码执行时间的增长变化趋势的。

常见的时间复杂度量级有:

  • 常数阶O(1)
  • 对数阶O(logN)
  • 线性阶O(n)
  • 线性对数阶O(nlogN)
  • 平方阶O(n^2)
  • 立方阶O(n^3)
  • K次方阶O(n^k)
  • 指数阶(2^n)
2.1.1 常数阶O(1)

代码当中没有循环等复杂结构那这代码的时间复杂度都是O(1)。它的时间消耗不随着某个变量增长而增长那么无论这类代码有多长都可以用O(1)来表示它的时间复杂度。

int i = 1;
int j = 2;
++i;
int m = i + j;
2.1.2 线性阶O(n)

简单的例子,

for(int i=0;i<n;++i){
    j = i;
}

for 循环里面的代码会执行n遍因此它消耗的时间是随着n的变化而变化的所以这类代码的时间复杂度可用O(n)来表示。

2.1.3 对数阶O(logN)
int i = 1;
while(i < n){
    i = i*2;
}

在while循环里面每次i都乘以2当循环来x次后i 大于 n 了,就退出了循环,即:


2^x = n <==> x=log_2n

也就是说当循环了logn 2为底次后这代码就结束了所以这个代码的时间复杂度为O(logn)。

2.1.4 线性对数阶O(nlogN)

线性对数阶O(nlogN) 其实非常容易理解将时间复杂度为O(logn)的代码循环N遍


n*O(logN) ==>O(nlogN)
for(int m=1;m<n;m++){
    i = 1;
    while(i<n){
        i = i * 2;
    }
}
2.1.5 平方阶O(n²)

平方阶O(n²) 就更容易理解了,如果把 O(n) 的代码再嵌套循环一遍,它的时间复杂度就是 O(n²) 了。

for(x=1; i<=n; x++)
{
   for(i=1; i<=n; i++)
    {
       j = i;
       j++;
    }
}

O(n*n)==>O(n^2)

这段代码嵌套了2层循环时间复杂度是O(n*n)即O(n²)如果其中一个循环的n改为m则时间复杂度变为O(n * m)。

2.2 空间复杂度

空间复杂度是对一个算法在运行过程中临时占用存储空间大小的一个量度同样是反映一个趋势用S(n)来定义。

空间复杂度比较常用的有O(1)、O(n)、O(n²)

2.2.1 空间复杂度O(1)

如果算法执行所需的临时空间不随着某个变量n的大小而变化即此算法空间复杂度为一个常量可用O(1)表示。

int i = 1;
int j = 2;
++i;
int m = i + j;

代码中的 i、j、m 所分配的空间都不随着处理数据量变化,因此它的空间复杂度 S(n) = O(1)。

2.2.2 空间复杂度为O(n)
int[] m = new int[n]
for(i=1; i<=n; ++i)
{
   j = i;
   j++;
}

这段代码中第一行new了一个数组出来这个数据占用的大小为n这段代码的2-6行虽然有循环但没有再分配新的空间因此这段代码的空间复杂度主要看第一行即可即 S(n) = O(n)。

3. 数组和链表

数据的逻辑分类:

  • 线性的:连成一条线的结构,如数组、链表、队列、栈
  • 非线性的:数据之间的关系是非线性的,如堆、树、图

3.1 数组

数组是一个有限的、类型相同的数据的集合,在内存中是一段连续的内存区域。

array

数组的下标是从0开始的上图数组中有6个元素对应着下标依次是0、1、2、3、4、5同时数组里面存的数据的类型必须是一致的比如上图中存的都是数字类型。数组中的全部元素是“连续”的存储在一块内存空间中的如上图右边部分元素与元素之间是不会有别的存储隔离的。另外也是因为数组需要连续的内存空间所以数组在定义的时候就需要提前指定固定大小不能改变。