递归的含义:一种非常简洁、高效的编码技巧,方法/函数调用自身的方式称之为递归,调用为“递”,返回为“归”。

  所有的递归问题都可以用递推公式来表达。

  优点:代码表达能力强,编码简洁。

  缺点:(1)空间复杂度高,存在栈溢出风险(策略:可以设置递归次数强行终止条件);(2)存在重复计算,针对这一点可以(策略:可以额外增加哈希表来快速查找结果而减少重复计算);(3)过多函数调用耗时较长。

  针对上述缺陷,任何一个递归问题(笼统来讲)都可以转化为非递归实现方式。方法:抽象出递推公式、初始值、边界条件,然后用循环来实现。

11 排序(上):为什么插入排序比冒泡排序更受欢迎?

  有序度:在一个数列中,符合顺序排列的数据对个数。(满有序度:全部数据均有序的数据对个数)

  逆序度:在一个数列中,不符合顺序排列的数据对个数。

  有序度 + 逆序度 = 满有序度。  

  冒泡排序和插入排序都是稳定算法(选择排序是不稳定算法),不论怎么优化,两者的元素移动的次数都等于数列的逆序度。但是,冒泡排序的赋值操作是3个,而插入排序是1个。所以理论上来讲,冒泡排序时间复杂度是3K,而插入排序是1K,如果希望性能更好肯定要首选插入而不是冒泡。基础的插入排序还有很大优化空间,进一步的改进可参考:希尔排序。

数据结构与算法之美专栏学习体会

  这三种基础排序算法实际的用途不大,但插入排序的思想在某些编程语言的实现原理中还是有可能用到。

12 排序(下):如何用快排思想在O(n)内查找第K大元素?

  归并排序:将数列分成前后两部分分别进行排序,然后将两个有序数列合并起来。(优点:时间复杂度低且稳定排序;缺陷:由于合并函数无法原地执行,所以是非原地排序算法,空间复杂度较高O(n)不如快排)

数据结构与算法之美专栏学习体会

   快速排序:与归并排序看起来有点类似,都是借助“分治”思想。(优点:时间复杂度低,空间复杂度仅O(n);缺点:不稳定排序)

数据结构与算法之美专栏学习体会

  快速排序的时间复杂度依赖于Partition选择的合理性,在极端情况下分区不合理会导致时间复杂度退化为O(n*n)。

  选择第K大的思路,每一次分区后,看看分区较小的大小是否符合K-1?如果是那么Guard元素就是了,如果不是,那么继续在分区内查找即可。

13 线性排序:如何根据年龄给100万用户数据排序?

  (1)桶排序

  将n个数均匀分到m个桶内,每个桶内有k=n/m个元素,桶内用快速排序O(k*logk),整个桶排序的时间复杂度就是O(m*k*logk)=O(n*logk),当m接近n时,该时间复杂度接近O(n)。

  看似很棒,实际有很多限制约束条件:<1>需要数列可划分且桶之间天然有序;<2>各桶内数据分布均匀;<3>数据范围不能太大,否则桶太多了。

  适用场景:外部排序(内存较小,将数据划分到外部大空间“桶”)。

  (2)计数排序

  可以理解为桶排序的一种特殊情况:按照n个数据的数据范围[0, k] 划分k 个桶,省去桶内排序时间。其他步骤跟桶排序一样。

  最巧妙的关键点:对数列A进行统计得到C,C中每一个位置表示该数值的不大于(<=)的元素个数,通过还原扫描C中每一个位置的数据(每一次计数减一)来得到排序结果R。

  约束条件与桶排序一致,数据量不能太大。

数据结构与算法之美专栏学习体会

  (3)基数排序

  对于数据位数一致(例如手机号码)的排序,可能数据量非常大到不能用桶排序和计数排序。这里有一个很巧妙的排序可以接近达到O(n):对每一位从低到高依次进行稳定O(n)排序。例如:

数据结构与算法之美专栏学习体会

  如果数据位数不等,可以通过补齐空位的方式来对齐排序。每一位的排序可以用上面的桶排序或者计数排序来逼近O(n),然后每一位顺序操作下来就是k*O(n)。总之,当位数k不太大时,时间复杂度就是O(k*n),接近O(n)。

  综上,基数排序约束条件为:<1>数据要能够划分出独立的“位”;<2>位之间有递进关系,例如高位对比之后低位可以不用比较了;<3>每一位数据范围不能太大,否则不能使用O(n)的桶排序/计数排序。总时间复杂度无法达到O(n)。

14 排序优化:如何实现一个通用的、高性能的排序函数?

数据结构与算法之美专栏学习体会

  优化要点:

  (1)让快速排序尽可能接近O(n*logn) 的时间复杂度而不是O(n*n):分区点的选择最重要,可以用“三数取中法”、“随机法”来优化;

  (2)对于数据量不大的情况,可以选择O(n)算法来排序,消耗O(n)的空间来换取时间是个很好的优化点;

  (3)一般数据量采用快速排序O(n);

  (4)对于快速排序中区间小于4个数的情况,退化采用插入排序,虽然是O(n*n)复杂度但实际操作更快。从理论上来解释就是O(n*logn)实际上剪去了一些常量系数,如果加上常量系数后则实际比O(n*n)更慢;

  (5)利用哨兵技巧减少一次对比判断,实际效果很好;

15 二分查找(上):如何用最省内存的方式实现快速查找功能?

  二分查找时间复杂度O(logn),但有约束条件:(1)连续内存空间支持随机访问;(2)数据有序;(3)不适合处理小规模数据,顺利遍历就OK了,也不适合太大规模数据因为连续内存申请不到;(4)不适合处理动态变化的数据,因为频繁插入删除影响效率;

  二分查找算法实现的注意点:(1)(high + low) / 2 有可能因为序号太大而加法溢出,应改成 low + (high - low) >> 1,这里 >> 操作效率比 / 更高。(2)下标移动的时候注意 low = mid + 1,high = mid - 1;

16 二分查找(下):如何快速定位IP对应的省份地址?

   二分查找算法的难点在于如何编写正确又能处理变种情况的代码,例如:

数据结构与算法之美专栏学习体会

  (1)查找第一个值等于给定值的元素

  一个精简的“漂亮”的写法:

 1 int bsearch(int a[], int n, int val) {
 2     int low = 0;
 3     int high = n - 1;
 4     while (low <= high) {
 5         int mid = low + ((high - low) >> 1);
 6         if (a[mid] >= val) {
 7             high = mid - 1;
 8         }
 9         else {
10             low = mid + 1;
11         }
12     }
13     if (a[low] == val) {
14         return low;
15     }
16     else {
17         return -1;
18     }
19 }
View Code

相关文章:

  • 2021-05-28
  • 2022-01-12
  • 2021-05-31
  • 2022-01-23
  • 2021-11-30
猜你喜欢
  • 2021-08-16
  • 2021-10-01
  • 2021-08-05
  • 2022-12-23
  • 2021-10-26
  • 2021-10-27
  • 2021-09-08
相关资源
相似解决方案