复杂度是什么
复杂度是衡量代码运行效率的重要的度量因素。
计算机通过一个个程序去执行计算任务,也就是对输入数据进行加工处理,并最终得到结果的过程。每个程序都是由代码构成的。可见,编写代码的核心就是要完成计算。但对同一个计算任务,不同计算方法得到结果的过程复杂度是不一样的,这对你实际的任务处理效率就有了非常大的影响。
复杂度是一个关于输入数据量n的函数、计算方法遵循以下几个原则:
首先,复杂度与具体的常熟无关。如O(n)和O(2n)表示同样的复杂度。
其次,多项式级的复杂度相加的时候,选择高者作为结果。
其中,O(1)也是表示一个特殊复杂度,含义为某个任务通过有限可数的资源即可完成。此处有限可数的具体意义是,与输入数据量n无关。
时间复杂度与代码结构的关系
时间复杂度与代码有着非常紧密的关系;而空间复杂度与数据结构的设计有关。
降低复杂度的方法三步骤:
1.暴力解法。在没有任何时间、空间约束下,完成代码任务的开发。
2.无效操作处理。将代码中的无效计算、无效存储剔除,降低时间或空间复杂度。
3.时空转换。设计合理数据结构,完成时间复杂度向空间复杂度的转移。
首先,这段代码对数据进行了哪些操作?
其次,这些操作中,哪个操作最影响效率,对时间复杂度的损耗最大?
最后,哪种数据结构最能帮助你提高数据操作的使用效率?
数据处理的基本操作只有 3 个,分别是增、删、查。其中,增和删又可以细分为在数据结构中间的增和删,以及在数据结构最后的增和删。区别就在于原数据的位置是否发生改变。查找又可以细分为按照位置条件的查找和按照数据数值特征的查找。几乎所有的数据处理,都是这些基本操作的组合和叠加。
什么是线性表
线性表是n个数据元素的有限序列,最常用的是链式表达,通常也叫作线性链表或者链表。在链表中存储的数据元素也叫作结点,一个结点存储的就是一条数据记录。每个结点包含两部分:
第一是具体的数据值
第二是指向下一个结点的指针
线性表案例:
例1,链表的翻转。给定一个链表,输出翻转后的链表。例如,输入1 ->2 -> 3 -> 4 ->5,输出 5 -> 4 -> 3 -> 2 -> 1。
while(curr){ next = curr.next; curr.next = prev; prev = curr; curr = next; }
例 2,给定一个奇数个元素的链表,查找出这个链表中间位置的结点的数值。
1. 一个暴力的办法是,先通过一次遍历去计算链表的长度,这样我们就知道了链表中间位置是第几个。接着再通过一次遍历去查找这个位置的数值。
2. 除此之外,还有一个巧妙的办法,就是利用快慢指针进行处理。其中快指针每次循环向后跳转两次,而慢指针每次向后跳转一次。
链表在增、删方面比较容易实现,可以在 O(1) 的时间复杂度内完成。但对于查找,不管是按照位置的查找还是按照数值条件的查找,都需要对全部数据进行遍历。
线性表的价值在于,它对数据的存储方式是按照顺序的存储。当数据的元素个数不确定,且需要经常进行数据的新增和删除时,那么链表会比较合适。链表的翻转、快慢指针的方法,是你必须掌握的内容。
栈是什么
栈是一种特殊的线性表。栈与线性表的不同,体现在增和删的操作。栈是一种后进先出的线性表,栈的数据新增和删除操作只能在这个线性表的表尾进行,即在线性表的基础上加了限制。
栈的基本操作
初始时,栈内没有数据,即空栈。此时栈顶就是栈底。当存入数据时,最先放入的数据会进入栈底。接着加入的数据都会放入到栈顶的位置。如果要删除数据,也只能通过访问栈顶的数据并删除。对于栈的新增操作,通常也叫作 push 或压栈。对于栈的删除操作,通常也叫作 pop 或出栈。
顺序栈:
栈的顺序存储可以借助数组来实现。一般来说,会把数组的首元素存在栈底,最后一个元素放在栈顶。然后定义一个 top 指针来指示栈顶元素在数组中的位置。假设栈中只有一个数据元素,则 top = 0。一般以 top 是否为 -1 来判定是否为空栈。当定义了栈的最大容量为 StackSize 时,则栈顶 top 必须小于 StackSize。
删除数据元素,即出栈操作,只需要 top - 1 就可以了。
对于查找操作,栈没有额外的改变,跟线性表一样,它也需要遍历整个栈来完成基于某些条件的数值查找。
链栈:
关于链式栈,就是用链表的方式对栈的表示。通常,可以把栈顶放在单链表的头部,如下图所示。由于链栈的后进先出,原来的头指针就显得毫无作用了。因此,对于链栈来说,是不需要头指针的。相反,它需要增加指向栈顶的 top 指针,这是压栈和出栈操作的重要支持。
在链式栈中进行删除操作时,只能在栈顶进行操作。因此,将栈顶的 top 指针指向栈顶元素的 next 指针即可完成删除。对于链式栈来说,新增删除数据元素没有任何循环操作,其时间复杂度均为 O(1)。
对于查找操作,相对链表而言,链栈没有额外的改变,它也需要遍历整个栈来完成基于某些条件的数值查找。
队列是什么
与栈相似,队列也是一种特殊的线性表,与线性表的不同之处也是体现在对数据的增和删的操作上。
队列的特点是先进先出:
先进,表示队列的数据新增操作只能在末端进行,不允许在队列的中间某个结点后新增数据;
先出,队列的数据删除操作只能在始端进行,不允许在队列的中间某个结点后删除数据。也就是说队列的增和删的操作只能分别在这个队列的队尾和队头进行
与线性表、栈一样,队列也存在这两种存储方式,即顺序队列和链式队列:
顺序队列,依赖数组来实现,其中的数据在内存中也是顺序存储。
而链式队列,则依赖链表来实现,其中的数据依赖每个结点的指针互联,在内存中并不是顺序存储。链式队列,实际上就是只能尾进头出的线性表的单链表。
队列从队头(front)删除元素,从队尾(rear)插入元素。对于一个顺序队列的数组来说,会设置一个 front 指针来指向队头,并设置另一个 rear 指针指向队尾。当我们不断进行插入删除操作时,头尾两个指针都会不断向后移动。
通常情况下,在可以确定队列长度最大值时,建议使用循环队列。无法确定队列长度时,应考虑使用链式队列。队列具有先进先出的特点,很像现实中人们排队买票的场景。在面对数据处理顺序非常敏感的问题时,队列一定是个不错的技术选型。