剑指offer读书笔记

96
国士无双A
2017.04.06 08:16* 字数 2007

数据结构

数组

数组可以说是最简单的一种数据结构,它占据一块连续的内存并按照顺序存储数据,由于数组中的内存是连续的,于是可以在O(1)时间读/写任何元素,因此它的时间效率是很高的。 数据结构中的哈希表(Hash table),也可以在O(1)时间读/写任何元素,它是根据键(Key)而直接访问在内存存储位置的数据结构。 哈希表应用的还是比较多的,比如Java中的HashMap,Objective-C中的NSDictionary与NSSet等。但是在使用哈希表进行查找的时候,要考虑冲突的次数,冲突次数越多,查找效率就越低。

字符串

字符串是由若干字符组成的序列,字符串使用起来比较简单,每种语言都对字符串做了许多的优化。在使用字符串时需要注意两点:

  • 字符串转成整形的时候,一定要注意数值的范围,因为字符串中代表的数字很容易就超出了整形表示的范围。
  • 在进行数值计算的时候,当牵涉到大数的时候,可以考虑用字符串来进行处理。

链表

链表的种类有很多,比如单向链表、双向链表、循环链表。链表是一种动态数据结构,是因为在创建链表时,无须知道链表的长度,当插入一个结点时,我们只需要为新节点分配内存,然后调整指针的指向来确保被链接到链表当中。内存分配不是在创建链表时一次性完成,而是每添加一个结点分配一次内存。由于没有闲置的内存,链表的空间效率非常高。由于链表中的内存不是一次性分配的,因而我们无法保证链表的内存和数组一样是连续的。 链表用于构建许多其他数据结构,如堆栈、队列和他们的派生。在Java中LinkedList已经实现了链表。

树是一种在实际编程中经常遇到的数据结构,二叉树是树的一种特殊结构,在二叉树中每个结点最多只能有两个子节点。在二叉树中最重要的操作莫过于遍历,即按照某一顺序访问树中的所有结点。最常用的集中遍历方式:

  • 前序遍历:先访问根结点,再访问左子结点,最后访问右子结点。
  • 中序遍历:先访问左子结点,再访问根结点,最后访问右子结点。
  • 后续遍历:先访问左子结点,再访问右子结点,最后访问根结点。

二叉树有很多特例,二叉搜索树就是其中之一,在二叉搜索树中,左子结点总是小于根结点,而右子结点总是大于或等于根结点。根据二叉搜索树的这个特点,我们可以平均在O(logn)的时间内根据数值在二叉搜索树中找到一个结点。

二叉树的另外两个特例是堆和红黑树。堆分为最大堆和最小堆,在最大堆中根结点的值最大,在最小堆中根结点的值最小。有很多需要快速找到最大值或者最小值的问题都可以用堆来解决。 红黑树是把树中的结点定义为红、黑两种颜色,并通过规则确保从根结点到叶结点的最长路径的长度不超过最短路径的两倍。

栈和队列

栈是一个非常常见的数据结构,比如操作系统会给每个线程创建一个栈用来存储函数调用时各个函数的参数、返回地址及临时变量等。 栈的特点是先进后出。 通常栈是一个不考虑排序的数据结构,我们需要O(n)时间才能找到栈中最大或者最小的元素。队列是另外一个重要的数据结构,队列的特点是先进先出。 栈和队列有很强的相互联系,比如用两个栈来模拟一个队列,用两个队列来模拟一个栈。栈的本质就是递归函数。


算法和数据操作

排序和查找是面试时考察算法的重点,排序中应该重点掌握二分查找、归并排序和快速排序。 有很多算法都可以用递归和循坏两种不同的方式实现,通常基于递归的实现方法会比较简洁,但性能不如基于循环的实现方法。

常用的排序有:冒泡排序、选择排序、插入排序、堆排序、归并排序、快速排序、希尔排序、计数排序、桶排序、基数排序等。

七大查找算法:顺序查找、二分查找、插值查找、斐波那契查找、树表查找、分块查找、哈希查找。

递归由于是函数调用自身,而函数调用是有时间和空间的消耗的:每一次函数调用,都需要在内存栈中分配空间以保存参数、返回地址及临时变量,而且往栈里压入数据和弹出数据都需要时间。另外,递归中有可能很多计算都是重复的,从而对性能带来很大的负面影响。除了效率之外,还有可能使调用栈溢出,前面分析中提到需要为每一次函数调用在内存栈中分配空间,而每个进程的栈的容量是有限的。当递归调用的层级太多时,就会超出栈的容量,从而导致调用栈溢出。

在使用算法的时候,时时刻刻需要考虑其时间复杂度与空间复杂度,特别是在软件层面开发时,往往对时间复杂度的要求更高。


高质量的代码

高质量的代码从三个方面来进行保证:代码的规范性、代码的完整性、代码的鲁棒性。

  • 代码的规范性:清晰的书写、清晰的布局、合理的命名。在命名的时候,用完整的英文单词组合命名变量和函数。
  • 代码的完整性:从功能测试、边界测试、负面测试3个方面设计测试用例,以保证代码的完整性。功能测试就是我们要实现的功能是什么,从这方面来设计测试用例。边界测试,主要就是找边界值进行测试。负面测试,主要是指比如输入的参数是非法的。
  • 代码的鲁棒性:鲁棒性主要体现在采取防御式编程处理无效的输入。比如对数组取值的时候,判断数组的长度是否大于小标值。比如对一个对象进行操作的时候,先判断一下其是否为空。

要想写出高质量的代码,最后的方式就是进行单元测试,把各种情况都进行测试一下。

欢迎关注国士梅花

国士梅花
闲话算法