《重学数据结构算法》相关笔记(部分)

前言

这是拉勾教育的重学数据结构算法的课程,此处记录相关笔记,供复习和参考。

1.代码效率

复杂度
复杂度是衡量程序运行效率的方式。

复杂度是一个关于输入数据量 n 的函数。假设你的代码复杂度是 f(n),那么就用个大写字母 O 和括号,把 f(n) 括起来就可以了,即 O(f(n))。例如,O(n) 表示的是,复杂度与计算实例的个数 n 线性相关;O(logn) 表示的是,复杂度与计算实例的个数 n 对数相关。

通常,复杂度的计算方法遵循以下几个原则:

  • 首先,复杂度与具体的常系数无关,例如 O(n) 和 O(2n) 表示的是同样的复杂度。我们详细分析下,O(2n) 等于O(n+n),也等于 O(n) + O(n)。也就是说,一段 O(n) 复杂度的代码只是先后执行两遍 O(n),其复杂度是一致的。

  • 其次,多项式级的复杂度相加的时候,选择高者作为结果,例如 O(n²)+O(n) 和 O(n²) 表示的是同样的复杂度。具体分析一下就是,O(n²)+O(n) = O(n²+n)。随着 n 越来越大,二阶多项式的变化率是要比一阶多项式更大的。因此,只需要通过更大变化率的二阶多项式来表征复杂度就可以了。

值得一提的是,O(1) 也是表示一个特殊复杂度,含义为某个任务通过有限可数的资源即可完成。此处有限可数的具体意义是,与输入数据量 n 无关。

用空间复杂度换时间复杂度
在程序优化中,需要让时间和空间的消耗都降低,但是这通常是不容易做到的。我们可以认识到时间的成本是要比空间的成本高的,所以我们常采用空间换时间的方式。

降低时间复杂度的方式有:递归、二分法、排序算法、动态规划等。
降低空间复杂度的核心思路是:能用低复杂度的数据结构能解决问题,就千万不要用高复杂度的数据结构。

降低复杂度的方法一般为下面三个步骤:

  • 第一步,暴力解法。在没有任何时间、空间约束下,完成代码任务的开发。
  • 第二步,无效操作处理。将代码中的无效计算、无效存储剔除,降低时间或空间复杂度。
  • 第三步,时空转换。设计合理数据结构,完成时间复杂度向空间复杂度的转移。

2.数据结构基础

增删查 是实际应用中最基础也是最常用数据结构操作方式。

线性表

线性表是 n 个数据元素的有限序列,最常用的是链式表达,通常也叫作线性链表或者链表。在链表中存储的数据元素也叫作结点,一个结点存储的就是一条数据记录。每个结点的结构包括两个部分:

  • 第一是具体的数据值;
  • 第二是指向下一个结点的指针。

线性表有:单向链表,双向链表,单向循环链表,双向循环链表等等。

例 快慢指针
判断链表是否有环。如下图所示,这就是一个有环的链表

有环链表示意图
链表的快慢指针方法,在很多链表操作的场景下都非常适用,对于这个问题也是一样。

假设链表有环,这个环里面就像是一个跑步赛道的操场一样。经过多次循环之后,快指针和慢指针都会进入到这个赛道中,就好像两个跑步选手在比赛。#加动图#快指针每次走两格,而慢指针每次走一格,相对而言,快指针每次循环会多走一步。这就意味着:

  • 如果链表存在环,快指针和慢指针一定会在环内相遇,即 fast == slow 的情况一定会发生。
  • 反之,则最终会完成循环,二者从未相遇。

根据这个性质我们就能对链表是否有环进行准确地判断了。如下图所示:
快慢指针示意图

栈是一种特殊的线性表。栈与线性表的不同,体现在增和删的操作。具体而言,栈的数据结点必须后进先出。后进的意思是,栈的数据新增操作只能在末端进行,不允许在栈的中间某个结点后新增数据。先出的意思是,栈的数据删除操作也只能在末端进行,不允许在栈的中间某个结点后删除数据。

宏观上来看,与数组或链表相比,栈的操作更为受限,那为什么我们要用这种受限的栈呢?其实,单纯从功能上讲,数组或者链表可以替代栈。然而问题是,数组或者链表的操作过于灵活,这意味着,它们过多暴露了可操作的接口。这些没有意义的接口过多,当数据量很大的时候就会出现一些隐藏的风险。一旦发生代码 bug 或者受到攻击,就会给系统带来不可预知的风险。虽然栈限定降低了操作的灵活性,但这也使得栈在处理只涉及一端新增和删除数据的问题时效率更高。

浏览器的页面存储问题,就是一个很好应用栈的例子。

栈有分为顺序栈和链表栈。

例 括号匹配
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。有效字符串需满足:左括号必须与相同类型的右括号匹配,左括号必须以正确的顺序匹配。例如,{ [ ( ) ( ) ] } 是合法的,而 { ( [ ) ] } 是非法的。

队列

与栈相似,队列也是一种特殊的线性表,与线性表的不同之处也是体现在对数据的增和删的操作上。

队列的特点是先进先出:

  • 先进,表示队列的数据新增操作只能在末端进行,不允许在队列的中间某个结点后新增数据;

  • 先出,队列的数据删除操作只能在始端进行,不允许在队列的中间某个结点后删除数据。也就是说队列的增和删的操作只能分别在这个队列的队尾和队头进行,如下图所示:
    队列示意图
    与线性表、栈一样,队列也存在这两种存储方式,即顺序队列和链式队列:

  • 顺序队列,依赖数组来实现,其中的数据在内存中也是顺序存储。

  • 链式队列,则依赖链表来实现,其中的数据依赖每个结点的指针互联,在内存中并不是 顺序存储。链式队列,实际上就是只能尾进头出的线性表的单链表

数组

字符串

面试中的高频考察点(字符串匹配算法)。
字符串中通常使用顺序结构。
总结

树和二叉树

树:是由结点和边组成的,不存在环的数据结构。
二叉树:每个结点最多只有两个分支。
二叉树的种类

  • 树的存储方式:
    顺序存储:空间利用率不高;
    链式存储:也就是像链表一样,每个结点有三个字段,一个存储数据,另外两个分别存放指向左右子结点的指针。

  • 二叉查找树的特性
    特性

  • 树的案例
    输入一个字符串,判断它在已有的字符串集合中是否出现过?(假设集合中没有某个字符串与另一个字符串拥有共同前缀且完全包含的特殊情况,例如 deep 和 dee。)如,已有字符串集合包含 6 个字符串分别为,cat, car, city, dog,door, deep。输入 cat,输出 true;输入 home,输出 false。

哈希表

哈希表的核心思想:设计采用了函数映射的思想,将记录的存储位置与记录的关键字关联起来。这样的设计方式,能够快速定位到想要查找的记录,而且不需要与表中存在的记录的关键字比较后再来进行查找。这是一种**可以实现“地址 = f (关键字)”的映射关系,那么就可以快速完成基于数据的数值的查找了。**这就是哈希表的核心思想。

哈希表的在设计的过程中需要避免,两个不同的关键词对应到同一部分的内容,这是在设计哈希表的过程中需要避免的,但是当数据量大的时候这个情况是完全有可能发生的。

如何设计哈希函数
一些常用的设计哈希函数的方法:

  • 第一,直接定制法
    哈希函数为关键字到地址的线性函数。如,H (key) = a*key + b。 这里,a 和 b 是设置好的常数。
  • 第二,数字分析法
    假设关键字集合中的每个关键字 key 都是由 s 位数字组成(k1,k2,…,Ks),并从中提取分布均匀的若干位组成哈希地址。上面张一、张二、张三、张四的手机号信息存储,就是使用的这种方法。
  • 第三,平方取中法
    如果关键字的每一位都有某些数字重复出现,并且频率很高,我们就可以先求关键字的平方值,通过平方扩大差异,然后取中间几位作为最终存储地址。
  • 第四,折叠法
    如果关键字的位数很多,可以将关键字分割为几个等长的部分,取它们的叠加和的值(舍去进位)作为哈希地址。
  • 第五,除留余数法
    预先设置一个数 p,然后对关键字进行取余运算。即地址为 key mod p。

如何解决哈希冲突
上面这些常用方法都有可能会出现哈希冲突。那么一旦发生冲突,我们该如何解决呢?
常用的方法,有以下两种:

  • 第一,开放定址法

即当一个关键字和另一个关键字发生冲突时,使用某种探测技术在哈希表中形成一个探测序列,然后沿着这个探测序列依次查找下去。当碰到一个空的单元时,则插入其中。

常用的探测方法是线性探测法。 比如有一组关键字 {12,13,25,23},采用的哈希函数为 key mod 11。当插入 12,13,25 时可以直接插入,地址分别为 1、2、3。而当插入 23 时,哈希地址为 23 mod 11 = 1。然而,地址 1 已经被占用,因此沿着地址 1 依次往下探测,直到探测到地址 4,发现为空,则将 23 插入其中。如下图所示:
在这里插入图片描述

  • 第二,链地址法

将哈希地址相同的记录存储在一张线性链表中。
例如,有一组关键字 {12,13,25,23,38,84,6,91,34},采用的哈希函数为 key mod 11。如下图所示:
在这里插入图片描述

哈希表优点:可以提供非常快速的插入-删除-查找操作,无论多少数据,插入和删除值需要接近常量的时间。
哈希表不足:在数据处理顺序敏感的问题时,选择哈希表并不是个好的处理方法。同时,哈希表中的 key 是不允许重复的,在重复性非常高的数据中,哈希表也不是个好的选择。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值