极客时间-数据结构与算法之美

时间复杂度表达的是代码执行时间随数据规模增长的变化趋势。

 

时间复杂度分析:

  1. 只关注循环执行次数最多的一段代码
  2. 总的时间复杂度等于量级最大的那段代码的时间复杂
  3. 乘法法则:

O(1)表示代码执行时间不会随着n的变化而变化,无论n多大;只要代码中不存在循环语句和递归语句;即为O(1);

O(logn)分析:

         i=1;

         while(i<n){

         i=i*2;

}

算代码的执行次数,即2的x次方为n的时候停止计算,那么x=log2N,故复杂度为O(log2N);不用管底数是什么,所有的复杂度都是O(logn);为什么呢?因为对数都是可以互相转换的,

log3N=log32*log2N,所以O(log3N)=O(C*log2N),其中C为常数,可以忽略系数;按照乘法法则可以计算出O(nlogn);

O(m+n):有两个输入的数据时,加法法则不再适用,但是乘法依然可以;

空间复杂度表示算法的存储空间与数据规模之间的增长关系;

常见的空间复杂度是O(1) O(n) O(n2)

课程5:

question:为什么数组下标从0开始计算?

answer下标的定义是相对于首地址的偏移量offset,否则在计算的过程中CPU会多一次减法的运算;

寻址方式:a[k]_address = base_address + k * type_size

 

Topic:GCC中的编译器堆栈保护技术

栈区的增长方向是从高地址到低地址-栈顶地址<栈底地址;

 

课程6 and 7: 链表

question循环链表解决约瑟夫问题?

answerhttps://blog.csdn.net/wenhai_zh/article/details/9620847(c语言)

 

question数组与链表的选择?

answer以空间换时间或者以时间换空间。             

《极客时间-数据结构与算法之美》

数组为连续存储空间,可以借助CPU缓存机制预读数据,访问效率高;

(解释:CPU在从内存读取数据的时候,会先把读取到的数据加载到CPU的缓存中。而CPU每次从内存读取数据并不是只读取那个特定要访问的地址,而是读取一个数据块,并保存到CPU缓存中,然后下次访问内存数据的时候就会先从CPU缓存开始查找,如果找到就不需要再从内存中取。这样就实现了比内存访问速度更快的机制,也就是CPU缓存存在的意义:为了弥补内存访问速度过慢与CPU执行速度快之间的差异而引入。)

 

链表在内存中不是连续存储,对链表进行频繁额插入删除操作,会导致频繁的内存申请和释放,容易产出内存碎片,java中容易GC。

 

Java中ArrayList虽然支持动态扩容,但是数据需要重新拷贝消耗时间;

question:反转单链表问题?

answer:             

《极客时间-数据结构与算法之美》

https://blog.csdn.net/fx677588/article/details/72357389

  1. 非递归方式:从头逐个倒序
  2. 递归方式:从末尾开始逐个倒序

question:回文链表问题?

answer1.利用快慢指针找到中点

	//判断是否为回文链表
	public static boolean isPalindrome(Node head) {
		Node slow = head;
		Node fast = head;
		while(fast != null && fast.next != null) {
			slow = slow.next;
			fast = fast.next.next;
		}
		fast = head;
		slow = reverseList(slow);
		
		while(slow != null) {
			if(fast.data != slow.data) {
				return false;
			}
			slow = slow.next;
			fast = fast.next;
		}
		return true;
	}

question:快慢指针问题

answer

  1. 是否存在环和寻找环的入口

https://www.cnblogs.com/songdechiu/p/6686520.html

https://blog.csdn.net/l294265421/article/details/50478818

http://www.cnblogs.com/xudong-bupt/p/3667729.html

思想:先找到相遇点;

question:写链表代码的技巧

answer:

             1.理解指针和引用,实际的意义就是存储所指对象的内存地址;

                 p->next=q  p节点中的next指针存储了q节点的内存地址;

             2.警惕指针丢失和内存泄露

                 插入节点的时候需要注意操作的顺序,警惕next的指向地址发生变化;

             3.利用哨兵简化实现难度(引入哨兵节点)

                 当插入头结点和删除尾节点的时候代码需要注意;哨兵节点不存储数据,head时钟指向哨兵节点

              4.重点留意边界位置

           

课程8:栈

栈的应用-函数调用栈

操作系统给每个线程分配了一块独立的内存空间,这块内存被组织成栈结构,用来存储函数调用时的临时变量。没进入一个函数,就会将临时变量作为一个栈帧入栈,当被调用函数执行完成,返回后,这个函数对应的栈帧出栈。

 

栈的应用-表达式求值的应用

栈的应用-括号匹配中的应用

 

 

课程9:队列

       question:向固定大小线程池中请求一个线程时,如果线程没有空闲资源,如何处理请求,排队还是拒绝?

answer:策略一:非阻塞处理方式,直接拒绝任务请求;

               策略二:请求排队,先进者先服务

 

question:对比基于链表和基于数组的实现方式

answer:基于链表支持无限排队的无界队列,但是可能导致过多的请求排队等待,请求处理时间长;基于数组的队列代销有限,当超过数组大小时请求会被拒绝。

       队列:操作受限的线性表数据结构

      

 

课程10:递归Recursion

去的过程叫做递,回来的过程叫做归。

满足递归的三个条件:

  1. 一个问题可以分解成子问题;
  2. 子问题的求解思路完全一致;
  3. 存在递归终止条件;

 

attention:递归要避免堆栈溢出;

通过限制递归调用最大深度的方式(在最大深度较小的情况下可以使用);

 

attention:递归代码要避免重复计算;

可以通过数据结构表(例如散列表)来保存求解过的值

    

    原文作者:约瑟夫环问题
    原文地址: https://blog.csdn.net/runkui8905/article/details/88595884
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞