最近很迷茫,不知道应该学什么好了,似乎什么都需要去学,是的,作为一个跨专业狗,仅有用的一点知识就是一些对计算机的基本理解和一些编程入门知识。缺乏系统性的计算机知识的学习,代码能力渣渣,算法和数据结构也和没学过一样。更不要提计网操作系统编译原理了,本科不学,自己也没自学,不知道我怎样能混过毕业这一关。多说无益,最近对数据结构和算法很感兴趣,那就学习吧,行动起来总是有希望的。
一、 时间复杂度和空间复杂度
代码执行消耗计算时间和计算空间,所以要衡量代码的复杂程度;
通常关注时间或控件消耗量与输入数据量的关系,复杂度是一个关于输入数据量n的函数,即O(f(n)); O(1)为与输入数据量n无关;
时间复杂度与代码的结构有着非常紧密的关系,空间复杂度与数据结构的设计有关
这个👆🏻总结很重要
- 一个顺序结构的代码,时间复杂度是 O(1)。
- 二分查找,或者更通用地说是采用分而治之的二分策略,时间复杂度都是 O(logn)。
- 一个简单的 for 循环,时间复杂度是 O(n)。
- 两个顺序执行的 for 循环,时间复杂度是 O(n)+O(n)=O(2n),其实也是 O(n)。
- 两个嵌套的 for 循环,时间复杂度是 O(n²)。
二、优化代码的方法论
-
第一步,暴力解法。在没有任何时间、空间约束下,完成代码任务的开发。
-
第二步,无效操作处理。将代码中的无效计算、无效存储剔除,降低时间或空间复杂度。
-
第三步,时空转换。设计合理数据结构,完成时间复杂度向空间复杂度的转移。
第一步的暴力解法没有太多的套路,只要围绕你面临的问题出发,大胆发挥想象去尝试解决即可。第二步的无效操作处理中,你需要学会并掌握递归、二分法、排序算法、动态规划等常用的算法思维。第三步的时空转换,你需要对数据的操作进行细分,全面掌握常见数据结构的基础知识。再围绕问题,有针对性的设计数据结构、采用合理的算法思维,去不断完成时空转移,降低时间复杂度。
三、数据结构基础
增删查,以不变应万变
设计合理的数据结构,又要从问题本身出发,我们可以采用这样的思考顺序:首先我们分析这段代码到底对数据先后进行了哪些操作。然后再根据分析出来的数据操作,找到合理的数据结构。
其实,代码对数据处理的操作类型非常少。代码对数据的处理就是代码对输入数据进行计算,得到结果并输出的过程。数据处理的操作就是找到需要处理的数据,计算结果,再把结果保存下来。这个过程总结为以下操作:
-
找到要处理的数据。这就是按照某些条件进行查找。
-
把结果存到一个新的内存空间中。这就是在现有数据上进行新增。
-
把结果存到一个已使用的内存空间中。这需要先删除内存空间中的已有数据,再新增新的数据。
经过对代码的拆解,你会发现即便是很复杂的代码,它对数据的处理也只有这 3 个基本操作,增、删、查。只要你围绕这 3 个数据处理的操作进行分析,就能得出解决问题的最优方案。常用的分析方法可以参考下面的 3 个步骤:
首先,这段代码对数据进行了哪些操作?
其次,这些操作中,哪个操作最影响效率,对时间复杂度的损耗最大?
最后,哪种数据结构最能帮助你提高数据操作的使用效率?
这 3 个步骤构成了设计合理数据结构的方法论。而第三个方面就需要你拥有足够扎实的数据结构基础知识了
package sample;
import java.util.HashMap;
import java.util.Map;
public class hi {
public static void main(String[] args) {
int a[] = { 1, 2, 3, 4, 5, 6, 6, 6, 5, 6 };
Map<Integer, Integer> d = new HashMap<>();
for (int i = 0; i < a.length; i++) {
if (d.containsKey(a[i])) {
d.put(a[i], d.get(a[i]) + 1);
} else {
d.put(a[i], 1);
}
}
for (Integer key : d.keySet())
{
System.out.println("Key = " + key);
}
for (Integer value : d.values())
{
System.out.println("Value = " + value);
}
int val_max = -1;
int time_max = 0;
for (Integer key : d.keySet()) {
if (d.get(key) > time_max) {
time_max = d.get(key);
val_max = key;
}
}
System.out.println(val_max);
System.out.println(time_max);
}
}
四、如何完成线性表的增删查
实现链表的反转 有两种方法:递归和遍历
- 遍历
package sample;
import sample.link.Node;
public class link {
public static class Node{
private int val;
private Node next;
public Node() {
// TODO Auto-generated constructor stub
next=null;
}
public Node(int val) {
// TODO Auto-generated constructor stub
this.val=val;
next=null;
}
public int getVal() {
return val;
}
public void setNext(Node next) {
this.next=next;
}
public Node getNext() {
return next;
}
}
private static final Node Node = null;
static Node reverse(Node head) {
Node pre = null;
Node temp = null;
while(head != null){
temp = head.next;
head.next = pre;
pre = head;
head = temp;
}
return pre;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] data={1,2,3,4,6};
Node p,s;
Node head=new Node();
p=head;
for(int i=0;i<data.length;i++){
p.setNext(new Node(data[i]));
p=p.getNext();
}
p=head.getNext();
s=reverse(p);
for(int i=0;i<data.length;i++){
System.out.println(s.getVal());
s=s.getNext();
}
}
}
- 递归实现反转
快慢指针
- 给定一个奇数个元素的链表,查找出这个链表中间位置的结点的数值。
这个问题也是利用了链表的长度无法直接获取的不足做文章,有一个巧妙的办法,就是利用快慢指针进行处理。其中快指针每次循环向后跳转两次,而慢指针每次向后跳转一次。
while(fast && fast.next && fast.next.next){
fast = fast.next.next;
slow = slow.next;
}
- 判断链表是否有环。如下图所示,这就是一个有环的链表。
五、栈:后进先出的线性表,如何实现增删查?
栈:后进先出,是经过限制的线性表。线性表的表头是栈底Bottom, 线性表的表尾是栈顶top.
线性表有顺序存储结构和链式存储结构。
顺序存储结构是地址连续的存储单元,链式存储结构是任意的存储单元。
所以栈可以分为顺序栈和链式栈两种。
- 计划任务
- 完成任务