时间复杂度

【数据结构与算法之美】排序优化:如何实现一个通用的、高性能的排序函数?

我与影子孤独终老i 提交于 2020-03-05 22:04:21
目录 一、如何选择合适的排序算法? 1.排序算法一览表 2.为什选择快速排序? 二、如何优化快速排序? 三、通用排序函数实现技巧 四、课后思考 一、如何选择合适的排序算法? 1.排序算法一览表 时间复杂度 是稳定排序? 是原地排序? 冒泡排序 O(n^2) 是 是 插入排序 O(n^2) 是 是 选择排序 O(n^2) 否 是 快速排序 O(nlogn) 否 是 归并排序 O(nlogn) 是 否 桶排序 O(n) 是 否 计数排序 O(n+k),k是数据范围 是 否 基数排序 O(dn),d是纬度 是 否 2.为什选择快速排序? 1)线性排序时间复杂度很低但使用场景特殊,如果要写一个通用排序函数,不能选择线性排序。 2)为了兼顾任意规模数据的排序,一般会首选时间复杂度为O(nlogn)的排序算法来实现排序函数。 3)同为O(nlogn)的快排和归并排序相比,归并排序不是原地排序算法,所以最优的选择是快排。 二、如何优化快速排序? 导致快排时间复杂度降为O(n)的原因是分区点选择不合理,最理想的分区点是:被分区点分开的两个分区中,数据的数量差不多。如何优化分区点的选择?有2种常用方法,如下: 1.三数取中法 ①从区间的首、中、尾分别取一个数,然后比较大小,取中间值作为分区点。 ②如果要排序的数组比较大,那“三数取中”可能就不够用了,可能要“5数取中”或者“10数取中”。 2.随机法

软件构造课程心得(2)

≯℡__Kan透↙ 提交于 2020-03-05 21:36:59
这次想做一下关于实验一中convex hull的另一种解答方法 在原本的实验中我是利用了点到点之间的角度来画出convex hull的,每次取角度最小的点加入convex集合,这样就可以画出包括整个点的集合。所用的时间复杂度是square n,虽然解决了问题,时间复杂度不让人满意,下面我将描述一个时间复杂度为n的方法。 我们可以利用分治算法,将一个整体的大问题,拆分成两个小问题,也就是n个点变成两个n/2个点的问题于是就有 T(n)=2T(n/2)+? 为了合并两个子问题,我们需要将两个已经画出convex的n/2个点的图合并成一个n个点的画出convex的图 为此我们需要取出一些原有的连线,并且增加一些连线,来画出n个点的convex。其实经过简单的分析我们就能知道,我们要加的线其实只有两条,一条在a、b上端封顶,一条在a、b下端封底,然后再删去此时被包围的线就行,难点就在于如何找到我们要加入的两条线。 而这个“?”就是我们对两个子问题进行合并所需要的时间,我们要尽可能的减小他。 我们现在设两个小问题分别是a 、b(a中的点与b中的点不位于同一侧) 普通的想法自然是我将a与b中的每两个点进行连线,如果图中所有的点都在这个线的一边,那我们就可以判定,这个线就是convex的一员。但这样我们的时间复杂度还是square n, 没有进步。 算法:因为a与b中的点不在同一侧

python dict查找操作时间复杂度

人走茶凉 提交于 2020-03-05 21:10:46
python: 在dict中查找某个key是否存在操作的时间复杂度为o(1);查找某个value是否存在操作的时间复杂度为o(n) 一、查找key是否在dict中: 该操作使用 object in dict 函数,首先对object进行hash变换,然后在dict中查找对应的地址, (图为dict中一个键值对的源码实现) 也即是找到对应的键值对,然后对key进行比较(若有冲突链则继续比较冲突链) 因此查找key是否在dict中的过程时间复杂为o(1) 二、查找value是否在dict中: 需要使用逐个比较的方式判断,因此时间复杂度为o(n) 比较实验: import time num = 10000000 dic = {} for i in range(num): dic[i] = i start = time.time() for i in range(10000): if 1 in dic.keys(): pass end = time.time() print (end - start) start = time.time() for i in range(10000): if 1 in dic.values(): pass end = time.time() print (end - start) 本质:dict作为一个hash链表,是对其key进行hash操作的

239. 滑动窗口最大值Leetcode

久未见 提交于 2020-03-05 01:38:01
239. 滑动窗口最大值 给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回滑动窗口中的最大值。 提示: 你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。 进阶: 你能在线性时间复杂度内解决此题吗? 分析 记录时机:遍历vector ,i >= k - 1时开始记录窗口中的最大值 获取窗口中的最大值方法: 维护一个严格递减的数据结构 循环[i , i + k) ,比较得出最大值 O(n) 采用方法二嵌套循环时间复杂度O(n 2 ) 因为我们只需要窗口中的最大值,严格递减的数据结构使用栈的特性,比当前遍历元素小的栈元素 出栈 ,然后将当前遍历元素 入栈 ,栈底为我们需要的最大值;在有序的数据结构中,随着窗口的移动,利用到队列的特性,当队首不在窗口中时,要把队首删除,维护有效的严格递减数据结构。双边队列deque满足上述栈和队列的所有能力,选用这个数据结构使问题简化,并降低时间复杂度为O(n)。 本题中,双边队列中可以存储数组下标,也可以存储数据元素。 C++实现 class Solution { public: vector<int> maxSlidingWindow(vector<int>& nums, int k) { vector

算法时间复杂度和空间复杂度

陌路散爱 提交于 2020-03-04 23:47:07
算法 即解决问题的方法。同一个问题,使用不同的算法,虽然得到的结果相同,但是耗费的时间和资源是不同的。 就比如要拧一个螺母,使用扳手还是钳子是有区别的,虽然使用钳子也能拧螺母,但是没有扳手好用。 “条条大路通罗马”,解决问题的算法有多种,这就需要判断哪个算法 “更好” 。 算法VS程序 很多人误以为程序就是算法,其实不然:算法是解决某个问题的想法、思路;而程序是在心中有算法的前提下编写出来的可以运行的代码。 例如,要解决依次输出一维数组中的数据元素的值的问题,首先想到的是使用循环结构( for 或者 while ),在有这个算法的基础上,开始编写程序。 所以, 算法相当于是程序的雏形 。当解决问题时,首先心中要有解决问题的算法,围绕算法编写出程序代码。 有算法一定能解决问题吗? 对于一个问题,想出解决的算法,不一定就能解决这个问题。 例如拧螺母,扳手相对于钳子来说更好使(选择算法的过程),但是在拧的过程(编写程序的过程)中发现螺母生锈拧不动,这时就需要另想办法。 为了避免这种情况的发生,要充分全面地思考问题,尽可能地考虑到所有地可能情况,慎重选择算法(需要在实践中不断地积累经验)。 “好”算法的标准 对于一个问题的算法来说,之所以称之为算法,首先它必须能够解决这个问题(称为准确性)。其次,通过这个算法编写的程序要求在任何情况下不能崩溃(称为健壮性)。 如果准确性和健壮性都满足

数据结构和算法-知识点总结

回眸只為那壹抹淺笑 提交于 2020-03-04 10:01:20
时间复杂度 1、假设计算机执行每个基本操作的时间是固定的时间单位,那么有多少基本操作步骤就代表有多少时间单位。虽然不同机器的时间单位不同,但是执行基本操作是相同的,因此可以使用步骤来客观描述代码的时间复杂度。 2、时间复杂度用大(O)记法表示。 3、T(n) = n^2 x 2, 关注趋势图的话可省略 x 2, 所以下面这个函数的时间复杂度可用g(n) = n^2表示 g(n) 和T(n) 相差 x2 的操作,g(n)是T(n)的渐近函数,记为f(n) = O(g(n)),即趋向无穷极限时,函数的增长速度受到g(n)的约束。 def a ( n ) : # 操作步骤=n for i in range ( n ) : # 操作步骤=n for j in range ( n ) : # 操作步骤=1步(i**2)+1步(j**3)+1步(i**2+j**3)+ 1步(c = i**2+j**3), #基本操作对趋势图影响不大,所以可以简单的看成是1步 c = i ** 2 + j ** 3 # 操作步骤=1步 print ( c ) 4、大(O)记法表示法用g(n)表示 算法 算法是一种思想,一种解决问题的思路,关注的是解决问题的步骤和思想,不关注处理的数据类型。 数据结构 1、数据的保存方式叫作数据结构 2、数据结构用于描述数据元素之间的关系,数据的组织方法会影响算法的时间复杂度

阶段总结(三)——为什么有了散列表我们还需要二叉树

南笙酒味 提交于 2020-03-04 05:17:40
二叉查找树最大的特点就是,支持动态数据集合的快速插入、删除、查找操作。 散列表也是支持这些操作的,而且散列表的这些操作比二叉查找树更高效,时间复杂度是 O(1)。 既然散列表如此高效,那么散列表是不是可以完全替代二叉树呢。 或者说有没有什么地方用散列表是做不了的,必须用二叉树呢? 散列表的插入、删除、查找操作的时间复杂度可以做到常量级的 O(1),非常高效。 而二叉查找树在比较平衡的情况下,插入、删除、查找操作时间复杂度才是 O(logn)。 但是为什么我们不能完全用散列表去替代二叉树呢? 一 散列表中的数据是无序存储的,如果要输出有序的数据,需要先进行排序。 而对于二叉查找树来说,我们只需要中序遍历,就可以在 O(n) 的时间复杂度内,输出有序的数据序列。 二 散列表扩容耗时很多,而且当遇到散列冲突时,性能不稳定。 尽管二叉查找树的性能也不稳定,但是在工程中,最常用的平衡二叉查找树的性能非常稳定,时间复杂度稳定在 O(logn)。 三 尽管散列表的查找等操作的时间复杂度是常量级的,但因为哈希冲突的存在,这个常量不一定比 logn 小,所以实际的查找速度可能不一定比 O(logn) 快。加上哈希函数的耗时,也不一定就比平衡二叉查找树的效率高。 四 散列表的构造比二叉查找树要复杂,需要考虑的东西很多。比如散列函数的设计、冲突解决办法、扩容、缩容等。

数据结构-桶排序 计数排序 基数排序

眉间皱痕 提交于 2020-03-03 15:38:23
文章目录 桶排序(Bucket sort) 简介 时间复杂度 使用场景 计数排序(Counting sort) 简介 例子 代码实现 总结 基数排序(Radix sort) 简介 使用场景 代码实现 总结 注:所有的代码在我的 Github 中有均具体C++代码实现。 这里主要讲的是三大 线性排序 :桶排序(Bucket sort)、计数排序(Counting sort)和基数排序(Radix sort)。 所谓线性排序,也就是说时间复杂度为 O(n),而之所以能够做到线性排序,是因为这三个算法是 非基于比较 的排序算法,都不涉及元素之间的比较操作。 桶排序(Bucket sort) 简介 顾名思义,会用到“桶”,核心的思想就是将要排序的数据分到几个有序的桶里面,然后每个桶再进行单独的排序。桶内的数据排序过后,再把每个桶里面的数据依次取出,这样组成的序列就是有序的了。 时间复杂度 如果要排序的数据有 n 个,我们把它们均匀地划分到 m 个桶内,每个桶里就有 k=n/m 个元素。每个桶内部使用快速排序,时间复杂度为 O(k * logk)。m 个桶排序的时间复杂度就是 O(m * k * logk),因为 k=n/m,所以整个桶排序的时间复杂度就是 O(n*log(n/m))。当桶的个数 m 接近数据个数 n 时,log(n/m) 就是一个非常小的常量,这个时候桶排序的时间复杂度接近

1428:面试题 10.01. 合并排序的数组

梦想的初衷 提交于 2020-03-03 15:22:51
问题描述 给定两个排序后的数组 A 和 B,其中 A 的末端有足够的缓冲空间容纳 B。 编写一个方法,将 B 合并入 A 并排序。 初始化 A 和 B 的元素数量分别为 m 和 n。 示例 输入 : A = [ 1 , 2 , 3 , 0 , 0 , 0 ] , m = 3 B = [ 2 , 5 , 6 ] , n = 3 输出 : [ 1 , 2 , 2 , 3 , 5 , 6 ] 思路 我们可以遍历B,对于每个B中的元素都插入到A中合适的位置。这样的时间复杂度是O(m*n). (方法一) 我们可以把数组B接在数组A的后面,然后对整个数组A进行排序。这样的时间复杂度是O((n+m)log(n+m)).(方法二) 我们可以用一个临时的数组,设置两个指针分别指向A和B的开头元素,谁小就把谁复制到新的数组中(即比较出来的是最终的位置)。最后把临时数组的值全部复制到数组A中。这样的时间复杂度是O(n+m), 空间复杂度也是O(n+m)。(方法三) 我们方法三中用临时数组的原因是,我们设置两个指针分别指向A和B的开头元素,没有办法谁小就把谁放在最终的位置,因为这可能会使我们遗失一些值。由于我们选定的某个元素的最终位置上是有元素的,直接覆盖的话我们会丢失这个值(因为指针并未更新,如果强行更新,指针也不知道该指向哪里)。But who care? 我们可以转变思维

C++序列式容器(STL序列式容器)介绍

◇◆丶佛笑我妖孽 提交于 2020-03-03 08:36:23
所谓序列容器,即以线性排列(类似普通数组的存储方式)来存储某一指定类型(例如 int、double 等)的数据,需要特殊说明的是,该类容器并不会自动对存储的元素按照值的大小进行排序。 1.array<T,N> (数组容器) :是一个长度固定的序列,有 N 个 T 类型的对象,不能增加或删除元素。 2.vector (向量容器) :是一个长度可变的序列,用来存放T类型的对象。是一个长度可变的序列容器,即在存储空间不足时,会自动申请更多的内存。使用此容器,在尾部增加或删除元素的效率最高(时间复杂度为 O(1) 常数阶),在其它位置插入或删除元素效率较差(时间复杂度为 O(n) 线性阶,其中 n 为容器中元素的个数); 3.deque (双向队列容器) :和 vector 非常相似,区别在于使用该容器不仅尾部插入和删除元素高效,在头部插入或删除元素也同样高效,时间复杂度都是 O(1) 常数阶,但是在容器中某一位置处插入或删除元素,时间复杂度为 O(n) 线性阶; 4.list (链表容器) 是一个长度可变的、由 T 类型对象组成的序列,它以双向链表的形式组织元素,在这个序列的任何地方都可以高效地增加或删除元素。访问容器中任意元素的速度要比前三种容器慢,这是因为 list 必须从第一个元素或最后一个元素开始访问,需要沿着链表移动,直到到达想要的元素。 5.forward list