一、概述

一、絮絮叨叨

计划写一系列数据结构与算法的博客:

  1. 一是给自己立个flag——坚持做完,
  2. 二是记录自己的学习过程,总结和分享知识

1、Why?

  1. 面试 =》考查基础 =》数据结构与算法
  2. 工作 =》有助于理解、使用框架;优化程序,提升效率、性能
  3. 锻炼逻辑思维能力 =》提升个人竞争力

2、What?

数据结构: Array(数组)、Stack/Queue(堆栈/队列)、PriorityQueue(优先队列)、LinkedList(链表)、Tree/Binary Search Tree(树/二分查找树)、HashTable(哈希表、散列表)、Disjoint Set(并查集)、Heap(堆)、跳表(SkipList)、Trie、BloomFilter、LRU Cache

算法: Greedy(贪婪算法)、Recursion/Backtrace(递归/回溯)、Traversal(遍历)、Breadth-first/Depth-first search(广度优先/深度优先搜索)、Divide and Conquer(分治法)、Dynamic Programming(动态规划)、Binary Search(二分查找)、Graph(图)、sort(排序)、字符串匹配算法、哈希算法

3、How?

(1)Chunk it up(切碎知识点)==》从书籍、课程中获得
(2)Deliberate practicing(刻意练习)
+ 刻意练习:练习缺陷、弱点的地方
+ 心理:感觉不舒服、不爽、枯燥 =》坚持住
(3)Feedback(反馈)
+ ① 即时反馈
+ ② 主动型反馈(自己去找)
+ 高手代码(Github、LeetCode…)
+ 论坛交流
+ ③ 被动式反馈(高手指点)
+ code review
+ 高手看自己打并给予反馈

4、关键点

  • 来历、自身的特点、适用解决的问题、实际应用场景

5、解题步骤

(1) 理解题目
(2)想出 possible solutions

  • 关注时间/空间复杂度
  • 选择最优解法

(3) Coding!!!
(4)测试多种情况下,检查codes是否考虑周全

6、相关书籍

在这里插入图片描述

一、数据结构

1、概念

数据结构:是相互之间存在一种或多种特定关系的数据元素的集合 =》存储结构
算法:是为求解一个问题需要遵循的、被清楚地指定的简单指令的结合。

两者关系: 数据结构为算法服务,算法作用于数据结构之上。

二、复杂度分析

  • 时间/空间复杂度 ——是效率和资源消耗的度量衡(不依赖于环境)

1、大O时间复杂度表示法

  • 所有代码的执行时间 T(n) 与每行代码执行的次数 n 成正比 ==》可用执行的次数衡量算法的优劣
    T(n) = O(f(n))=》 大O记法
    其中,T(n)表示代码执行的时间,n表示数据规模的大小,f(n)表示每行代码执行次数的总和。
    大O时间复杂度实际上并不具体代表代码真正的执行时间,而是代表代码执行时间随数据规模增长的变化趋势,所以,也叫做渐进时间复杂度

2、时间复杂度分析

  • 只关注循环执行次数最多的一段代码
  • 加法法则:总复杂度等于量级最大的那段代码的复杂度
若 T1(n) = O(f(n)), T2(n) = O(g(n)),
则 T1(n) = T1(n) + T2(n) =  max(O(f(n)), O(g(n))) = O(max(f(n), g(n)))
  • 乘法法则嵌套代码的复杂度等于嵌套内外代码复杂度的乘积

3、几种常见时间复杂度

(1)多项式量级

  • O(1)——常数复杂度(Constant Complexity)
 int i = 10;
 int j = 6;
 int sum = i + j;
  • O(logn) ——对数复杂度
for(int i = 1; i < n; i = i * 2){
	cout<<"Hey~I'm busy looking at:"<<i<<endl;
}
  • O(n)——线性复杂度
for(int i = 1; i <= n; i++){
	cout<<"Hey~I'm busy looking at:"<<i<<endl;
	}
  • O(nlogn)——线性对数阶
    • O(nlogn) 也是一种非常常见的算法时间复杂度。比如,归并排序、快速排序的时间复杂度都是 O(nlogn)。
for(int i = 1; i <= n; i++){
	for(int j = 1; j <= m; j = j * 2){
		cout<<"Hey~I'm busy looking at:"<<i<<"and"<<j<<endl;
 	}
 }

  • O(n2)、O(n3) … O(nk)——平方阶、立方阶…k次方阶
// 平方阶复杂度
for(int i = 1; i <= n; i++){
	for(int j = 1; j <= m; j++){
		 cout<<"Hey~I'm busy looking at:"<<i<<"and"<<j<<endl;
	}
 }
 //k次方阶复杂度
 for(int i = 1; i <= Math.pow(2,n);i++){
	cout<<"Hey~I'm busy looking at:"<<i<<endl;
}
  • O(m+n)、O(m*n)——m和n表示两个数据规模的大小,且无法事先评估m和n的量级大小

(2)非多项式量级

时间复杂度为非多项式量级的算法问题 =》NP(Non-Deterministic Polynomial)问题

  • O(2n)——指数阶
  • O(n!)——阶乘阶
for(int i = 1; i <= factorial(n); i++){
 cout<<"Hey~I'm busy looking at:"<<i<<endl;
 }

4、空间复杂度分析

空间复杂度表示算法的存储空间与数据规模之间的增长关系,又称渐进空间复杂度
常见的空间复杂度:O(1)、O(n)、O(n2)

void print(int n){    
	int i = 0;
	int *a = new int[n];    
	for (i; i<n; ++i){        
		a[i] = i * i;    }     
	for (i = n-1; i>=0; --i){        
		cout<<a[i]<<endl;    }
	}

第二行代码中,我们申请了一个空间存储变量i,但是它是常量阶的,跟数据规模n没有关系,所以可以忽略。第三行中,我们申请了一个大小为n的int类型数据。所以整段代码的空间复杂度就是O(n)

5、复杂度比较

复杂度从低到高:
O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) <… < O(2n) < O(n!)
在这里插入图片描述

三、其他时间复杂度

1、最好/最坏情况时间复杂度(best/wrost case time complexity)

// 目的:在一个无序的数组中,查找变量x出现的位置
// n 表示数组 array 的长度
int fun1( int[] array, int n, int x){    
	int i = 0;    
	int pos = -1;    
	for(; i < n, ++i){ 
		//找到即结束       
		if( array[i] == x) {  
			pos = i;    
			break;       
		}
	}    
	return pos;
}

复杂度分析:变量 x 可能出现在数组中的任何位置。如果数组中第一个元素正好是要查找的变量 x ,那就不需要遍历剩下的 n-1 个数据了,这时时间复杂度为 O(1) ,也就是最好情况时间复杂度为 O(1)。 但如果数组中不存在变量 x ,那么就需要把整个数组都办理一遍,时间复杂度就是 O(n) ,也就是最坏情况时间复杂度为 O(n)。 所以不同情况下,这段代码的时间复杂度是不一样的。

==》引入概念:最好情况时间复杂度、最坏情况时间复杂度、平均情况时间复杂度。

2、平均情况时间复杂度

平均情况时间复杂度:假定各种输入实例的出现符合某种概率分布(如均匀独立随机分布)之后,进而估计出的加权平均时间复杂度

复杂度分析: 仍为上面的例子,要查找的变量x,要么在数组里,要么不在数组里。这两种情况对应的概率统计起来很麻烦,假设这两种情况的概率都为1/2。要查找的数据在0~n-1这n个位置的概率也是一样的,为1/n。所以根据概率乘法法则,要查找的数据出现在0~n-1中任意位置的概率为1/2n。
把每种情况发生的概率也考虑上:

1*1/2n + 2*1/2n + 3*1/2n + ... + n*1/2n + n*1/2 = (3n+1)/4

==》平均情况时间复杂度为O(n)

3、均摊时间复杂度

(1)举例

目的:数组元素求和(有限大小)——往数组中插入数据,当数组满后count = n,用for循环遍历数组求和,并清空数组。求和之后的sum值当道数组的第一个位置,然后再进行插入,如果数组一开始就有空闲空间,直接插入。

int arr[SIZE] = {0};
int n = sizeof(arr)/sizeof(arr[0]);
int count = 0;
int fun2(int val)
{
    if(count == n)
    {
        int sum = 0;
        for(int i=0; i<n; i++)
        {
            sum = sum + arr[i];
        }
        arr[0] = sum;
        count = 1;
    }
    arr[count] = val;
    ++count;
}

最好情况时间复杂度O(1)

最坏情况时间复杂度O(n)

平均时间复杂度: 假设数组的长度是n,根据数据插入位置的不同,我们可以分为n种情况,每种情况的时间复杂度为O(1)。除此外,还有一种情况,就是当数组没有空闲空间时,这时的时间复杂度为O(n)。而且,这n+1种情况发生的概率一样,都是1/(n+1)。所以有:
1*1/(n+1) + 1*1/(n+1) + ... + 1*1/(n+1) + n*1/(n+1) = O(1)

(2)对比 fun1( ) 与 func2( ):

  • fun1函数在极端情况下,复杂度才为O(1)。但是fun2在大部分情况下,时间复杂度都为O(1),只有个别情况下,复杂度才比较高。
  • 对于fun2来说,O(1)时间复杂度的插入和O(n)时间复杂度的插入的出现是有规律的,而且有一定的前后时序关系,一般是O(n)插入后,紧跟着n-1个O(1)的插入操作,循环往复。

均摊时间复杂度: 在代码执行的所有复杂度情况中绝大部分是低级别的复杂度,个别情况是高级别复杂度且发生具有时序关系时,可以将个别高级别复杂度均摊到低级别复杂度上。基本上均摊结果就等于低级别复杂度。

上例分析:对于每一次的O(n)操作,将时间均摊给n-1个O(1)的操作,这样均摊下来,总的时间复杂度为O(1)。==》func2()的均摊时间复杂度为O(1)

四、递归算法的时间复杂度

待补充。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值