mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-25 04:36:41 +08:00
689 lines
26 KiB
HTML
689 lines
26 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
|
||
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
|
||
<head>
|
||
|
||
<head>
|
||
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||
|
||
<link rel="icon" href="/static/favicon.png">
|
||
|
||
<title>01 复杂度:如何衡量程序运行的效率?.md.html</title>
|
||
|
||
<!-- Spectre.css framework -->
|
||
|
||
<link rel="stylesheet" href="/static/index.css">
|
||
|
||
<!-- theme css & js -->
|
||
|
||
<meta name="generator" content="Hexo 4.2.0">
|
||
|
||
</head>
|
||
<body>
|
||
<div class="book-container">
|
||
|
||
<div class="book-sidebar">
|
||
|
||
<div class="book-brand">
|
||
|
||
<a href="/">
|
||
|
||
<img src="/static/favicon.png">
|
||
|
||
<span>技术文章摘抄</span>
|
||
|
||
</a>
|
||
|
||
</div>
|
||
|
||
<div class="book-menu uncollapsible">
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="/" class="current-tab">首页</a></li>
|
||
|
||
</ul>
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="../">上一级</a></li>
|
||
|
||
</ul>
|
||
<ul class="uncollapsible">
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/00 数据结构与算法,应该这样学!.md.html">00 数据结构与算法,应该这样学!.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
<a class="current-tab" href="/专栏/重学数据结构与算法-完/01 复杂度:如何衡量程序运行的效率?.md.html">01 复杂度:如何衡量程序运行的效率?.md.html</a>
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/02 数据结构:将“昂贵”的时间复杂度转换成“廉价”的空间复杂度.md.html">02 数据结构:将“昂贵”的时间复杂度转换成“廉价”的空间复杂度.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/03 增删查:掌握数据处理的基本操作,以不变应万变.md.html">03 增删查:掌握数据处理的基本操作,以不变应万变.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/04 如何完成线性表结构下的增删查?.md.html">04 如何完成线性表结构下的增删查?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/05 栈:后进先出的线性表,如何实现增删查?.md.html">05 栈:后进先出的线性表,如何实现增删查?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/06 队列:先进先出的线性表,如何实现增删查?.md.html">06 队列:先进先出的线性表,如何实现增删查?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/07 数组:如何实现基于索引的查找?.md.html">07 数组:如何实现基于索引的查找?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/08 字符串:如何正确回答面试中高频考察的字符串匹配算法?.md.html">08 字符串:如何正确回答面试中高频考察的字符串匹配算法?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/09 树和二叉树:分支关系与层次结构下,如何有效实现增删查?.md.html">09 树和二叉树:分支关系与层次结构下,如何有效实现增删查?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/10 哈希表:如何利用好高效率查找的“利器”?.md.html">10 哈希表:如何利用好高效率查找的“利器”?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/11 递归:如何利用递归求解汉诺塔问题?.md.html">11 递归:如何利用递归求解汉诺塔问题?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/12 分治:如何利用分治法完成数据查找?.md.html">12 分治:如何利用分治法完成数据查找?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/13 排序:经典排序算法原理解析与优劣对比.md.html">13 排序:经典排序算法原理解析与优劣对比.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/14 动态规划:如何通过最优子结构,完成复杂问题求解?.md.html">14 动态规划:如何通过最优子结构,完成复杂问题求解?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/15 定位问题才能更好地解决问题:开发前的复杂度分析与技术选型.md.html">15 定位问题才能更好地解决问题:开发前的复杂度分析与技术选型.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/16 真题案例(一):算法思维训练.md.html">16 真题案例(一):算法思维训练.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/17 真题案例(二):数据结构训练.md.html">17 真题案例(二):数据结构训练.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/18 真题案例(三):力扣真题训练.md.html">18 真题案例(三):力扣真题训练.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/19 真题案例(四):大厂真题实战演练.md.html">19 真题案例(四):大厂真题实战演练.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/20 代码之外,技术面试中你应该具备哪些软素质?.md.html">20 代码之外,技术面试中你应该具备哪些软素质?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/21 面试中如何建立全局观,快速完成优质的手写代码?.md.html">21 面试中如何建立全局观,快速完成优质的手写代码?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/加餐 课后练习题详解.md.html">加餐 课后练习题详解.md.html</a>
|
||
</li>
|
||
|
||
</ul>
|
||
</div>
|
||
|
||
</div>
|
||
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
|
||
|
||
<div class="sidebar-toggle-inner"></div>
|
||
|
||
</div>
|
||
<script>
|
||
|
||
function add_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.add('show')
|
||
|
||
}
|
||
function remove_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.remove('show')
|
||
|
||
}
|
||
function sidebar_toggle() {
|
||
|
||
let sidebar_toggle = document.querySelector('.sidebar-toggle')
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let content = document.querySelector('.off-canvas-content')
|
||
|
||
if (sidebar_toggle.classList.contains('extend')) { // show
|
||
|
||
sidebar_toggle.classList.remove('extend')
|
||
|
||
sidebar.classList.remove('hide')
|
||
|
||
content.classList.remove('extend')
|
||
|
||
} else { // hide
|
||
|
||
sidebar_toggle.classList.add('extend')
|
||
|
||
sidebar.classList.add('hide')
|
||
|
||
content.classList.add('extend')
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
function open_sidebar() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.add('show')
|
||
|
||
overlay.classList.add('show')
|
||
|
||
}
|
||
|
||
function hide_canvas() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.remove('show')
|
||
|
||
overlay.classList.remove('show')
|
||
|
||
}
|
||
</script>
|
||
<div class="off-canvas-content">
|
||
|
||
<div class="columns">
|
||
|
||
<div class="column col-12 col-lg-12">
|
||
|
||
<div class="book-navbar">
|
||
|
||
<!-- For Responsive Layout -->
|
||
|
||
<header class="navbar">
|
||
|
||
<section class="navbar-section">
|
||
|
||
<a onclick="open_sidebar()">
|
||
|
||
<i class="icon icon-menu"></i>
|
||
|
||
</a>
|
||
|
||
</section>
|
||
|
||
</header>
|
||
|
||
</div>
|
||
|
||
<div class="book-content" style="max-width: 960px; margin: 0 auto;
|
||
|
||
overflow-x: auto;
|
||
|
||
overflow-y: hidden;">
|
||
|
||
<div class="book-post">
|
||
|
||
<p id="tip" align="center"></p>
|
||
|
||
<div><h1>01 复杂度:如何衡量程序运行的效率?</h1>
|
||
|
||
<p>前面我说到了,咱们这个专栏的目标是想教会你利用数据结构的知识,建立算法思维,并完成代码效率的优化。为了达到这个目标,在第一节课,我们先来讲一讲<strong>如何衡量程序运行的效率</strong>。</p>
|
||
|
||
<p>当你在大数据环境中开发代码时,你一定遇到过程序执行好几个小时、甚至好几天的情况,或者是执行过程中电脑几乎死机的情况:</p>
|
||
|
||
<ul>
|
||
|
||
<li>如果这个效率低下的系统是<strong>离线</strong>的,那么它会让我们的开发周期、测试周期变得很长。</li>
|
||
|
||
<li>如果这个效率低下的系统是<strong>在线</strong>的,那么它随时具有时间爆炸或者内存爆炸的可能性。</li>
|
||
|
||
</ul>
|
||
|
||
<p>因此,衡量代码的运行效率对于一个工程师而言,是一项非常重要的基本功。本课时我们就来学习程序运行效率相关的度量方法。</p>
|
||
|
||
<h4>复杂度是什么</h4>
|
||
|
||
<p><strong>复杂度是衡量代码运行效率的重要度量因素</strong>。在介绍复杂度之前,有必要先看一下复杂度和计算机实际任务处理效率的关系,从而了解降低复杂度的必要性。</p>
|
||
|
||
<p>计算机通过一个个程序去执行计算任务,也就是对输入数据进行加工处理,并最终得到结果的过程。每个程序都是由代码构成的。可见,编写代码的核心就是要完成计算。但对于同一个计算任务,不同计算方法得到结果的过程复杂程度是不一样的,这对你实际的任务处理效率就有了非常大的影响。</p>
|
||
|
||
<p>举个例子,你要在一个在线系统中实时处理数据。假设这个系统平均每分钟会新增 300M 的数据量。如果你的代码不能在 1 分钟内完成对这 300M 数据的处理,那么这个系统就会发生时间爆炸和空间爆炸。表现就是,电脑执行越来越慢,直到死机。因此,我们需要讲究合理的计算方法,去通过尽可能低复杂程度的代码完成计算任务。</p>
|
||
|
||
<p><img src="assets/CgqCHl7CRGiAe-NpAR0S70dSC2M990.gif" alt="1.gif" /></p>
|
||
|
||
<p>那提到降低复杂度,我们首先需要知道怎么衡量复杂度。而在实际衡量时,我们通常会围绕以下2 个维度进行。<strong>首先,这段代码消耗的资源是什么</strong>。一般而言,代码执行过程中会消耗计算时间和计算空间,那需要衡量的就是时间复杂度和空间复杂度。</p>
|
||
|
||
<p>我举一个实际生活中的例子。某个十字路口没有建立立交桥时,所有车辆通过红绿灯分批次行驶通过。当大量汽车同时过路口的时候,就会分别消耗大家的时间。但建了立交桥之后,所有车辆都可以同时通过了,因为立交桥的存在,等于是消耗了空间资源,来换取了时间资源。</p>
|
||
|
||
<p><img src="assets/CgqCHl7CRMaAO_oEAJfz6fjfMNQ403.gif" alt="2.gif" /></p>
|
||
|
||
<p><strong>其次,这段代码对于资源的消耗是多少</strong>。我们不会关注这段代码对于资源消耗的绝对量,因为不管是时间还是空间,它们的消耗程度都与输入的数据量高度相关,输入数据少时消耗自然就少。为了更客观地衡量消耗程度,我们通常会关注时间或者空间消耗量与输入数据量之间的关系。</p>
|
||
|
||
<p>好,现在我们已经了解了衡量复杂度的两个纬度,那应该如何去计算复杂度呢?</p>
|
||
|
||
<p><strong>复杂度是一个关于输入数据量 n 的函数</strong>。假设你的代码复杂度是 f(n),那么就用个大写字母 O 和括号,把 f(n) 括起来就可以了,即 O(f(n))。例如,O(n) 表示的是,复杂度与计算实例的个数 n 线性相关;O(logn) 表示的是,复杂度与计算实例的个数 n 对数相关。</p>
|
||
|
||
<p>通常,复杂度的计算方法遵循以下几个原则:</p>
|
||
|
||
<ul>
|
||
|
||
<li>首先,<strong>复杂度与具体的常系数无关</strong>,例如 O(n) 和 O(2n) 表示的是同样的复杂度。我们详细分析下,O(2n) 等于 O(n+n),也等于 O(n) + O(n)。也就是说,一段 O(n) 复杂度的代码只是先后执行两遍 O(n),其复杂度是一致的。</li>
|
||
|
||
<li>其次,<strong>多项式级的复杂度相加的时候,选择高者作为结果</strong>,例如 O(n²)+O(n) 和 O(n²) 表示的是同样的复杂度。具体分析一下就是,O(n²)+O(n) = O(n²+n)。随着 n 越来越大,二阶多项式的变化率是要比一阶多项式更大的。因此,只需要通过更大变化率的二阶多项式来表征复杂度就可以了。</li>
|
||
|
||
</ul>
|
||
|
||
<p>值得一提的是,<strong>O(1) 也是表示一个特殊复杂度</strong>,含义为某个任务通过有限可数的资源即可完成。此处有限可数的具体意义是,<strong>与输入数据量 n 无关</strong>。</p>
|
||
|
||
<p>例如,你的代码处理 10 条数据需要消耗 5 个单位的时间资源,3 个单位的空间资源。处理 1000 条数据,还是只需要消耗 5 个单位的时间资源,3 个单位的空间资源。那么就能发现资源消耗与输入数据量无关,就是 O(1) 的复杂度。</p>
|
||
|
||
<p>为了方便你理解不同计算方法对复杂度的影响,我们来看一个代码任务:对于输入的数组,输出与之逆序的数组。例如,输入 a=[1,2,3,4,5],输出 [5,4,3,2,1]。</p>
|
||
|
||
<p>先看<strong>方法一</strong>,建立并初始化数组 b,得到一个与输入数组等长的全零数组。通过一个 for 循环,从左到右将 a 数组的元素,从右到左地赋值到 b 数组中,最后输出数组 b 得到结果。</p>
|
||
|
||
<p><img src="assets/Ciqc1F7CRP6ARwDTAGHL-opG6Bk835.gif" alt="3.gif" /></p>
|
||
|
||
<p>代码如下:</p>
|
||
|
||
<pre><code>public void s1_1() {
|
||
|
||
int a[] = { 1, 2, 3, 4, 5 };
|
||
|
||
int b[] = new int[5];
|
||
|
||
for (int i = 0; i < a.length; i++) {
|
||
|
||
b[i] = a[i];
|
||
|
||
}
|
||
|
||
for (int i = 0; i < a.length; i++) {
|
||
|
||
b[a.length - i - 1] = a[i];
|
||
|
||
}
|
||
|
||
System.out.println(Arrays.toString(b));
|
||
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>这段代码的输入数据是 a,数据量就等于数组 a 的长度。代码中有两个 for 循环,作用分别是给b 数组初始化和赋值,其执行次数都与输入数据量相等。因此,代码的<strong>时间复杂度</strong>就是 O(n)+O(n),也就是 O(n)。</p>
|
||
|
||
<p>空间方面主要体现在计算过程中,对于存储资源的消耗情况。上面这段代码中,我们定义了一个新的数组 b,它与输入数组 a 的长度相等。因此,空间复杂度就是 O(n)。</p>
|
||
|
||
<p><strong>接着我们看一下第二种编码方法</strong>,它定义了缓存变量 tmp,接着通过一个 for 循环,从 0 遍历到a 数组长度的一半(即 len(a)/2)。每次遍历执行的是什么内容?就是交换首尾对应的元素。最后打印数组 a,得到结果。</p>
|
||
|
||
<p><img src="assets/Ciqc1F7CR22AIbSuABc0Rwl-t3w666.gif" alt="4.gif" /></p>
|
||
|
||
<p>代码如下:</p>
|
||
|
||
<pre><code>public void s1_2() {
|
||
|
||
int a[] = { 1, 2, 3, 4, 5 };
|
||
|
||
int tmp = 0;
|
||
|
||
for (int i = 0; i < (a.length / 2); i++) {
|
||
|
||
tmp = a[i];
|
||
|
||
a[i] = a[a.length - i - 1];
|
||
|
||
a[a.length - i - 1] = tmp;
|
||
|
||
}
|
||
|
||
System.out.println(Arrays.toString(a));
|
||
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>这段代码包含了一个 for 循环,执行的次数是数组长度的一半,时间复杂度变成了 O(n/2)。根据复杂度与具体的常系数无关的性质,这段代码的时间复杂度也就是 O(n)。</p>
|
||
|
||
<p>空间方面,我们定义了一个 tmp 变量,它与数组长度无关。也就是说,输入是 5 个元素的数组,需要一个 tmp 变量;输入是 50 个元素的数组,依然只需要一个 tmp 变量。因此,空间复杂度与输入数组长度无关,即 O(1)。</p>
|
||
|
||
<p>可见,<strong>对于同一个问题,采用不同的编码方法,对时间和空间的消耗是有可能不一样的</strong>。因此,工程师在写代码的时候,一方面要完成任务目标;另一方面,也需要考虑时间复杂度和空间复杂度,以求用尽可能少的时间损耗和尽可能少的空间损耗去完成任务。</p>
|
||
|
||
<h4>时间复杂度与代码结构的关系</h4>
|
||
|
||
<p>好了,通过前面的内容,相信你已经对时间复杂度和空间复杂度有了很好的理解。从本质来看,时间复杂度与代码的结构有着非常紧密的关系;而空间复杂度与数据结构的设计有关,关于这一点我们会在下一讲进行详细阐述。接下来我先来系统地讲一下时间复杂度和代码结构的关系。</p>
|
||
|
||
<p>代码的<strong>时间复杂度,与代码的结构有非常强的关系</strong>,我们一起来看一些具体的例子。</p>
|
||
|
||
<p>例 1,定义了一个数组 a = [1, 4, 3],查找数组 a 中的最大值,代码如下:</p>
|
||
|
||
<pre><code>public void s1_3() {
|
||
|
||
int a[] = { 1, 4, 3 };
|
||
|
||
int max_val = -1;
|
||
|
||
for (int i = 0; i < a.length; i++) {
|
||
|
||
if (a[i] > max_val) {
|
||
|
||
max_val = a[i];
|
||
|
||
}
|
||
|
||
}
|
||
|
||
System.out.println(max_val);
|
||
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>这个例子比较简单,实现方法就是,暂存当前最大值并把所有元素遍历一遍即可。因为代码的结构上需要使用一个 for 循环,对数组所有元素处理一遍,所以时间复杂度为 O(n)。</p>
|
||
|
||
<p>例2,下面的代码定义了一个数组 a = [1, 3, 4, 3, 4, 1, 3],并会在这个数组中查找出现次数最多的那个数字:</p>
|
||
|
||
<pre><code>public void s1_4() {
|
||
|
||
int a[] = { 1, 3, 4, 3, 4, 1, 3 };
|
||
|
||
int val_max = -1;
|
||
|
||
int time_max = 0;
|
||
|
||
int time_tmp = 0;
|
||
|
||
for (int i = 0; i < a.length; i++) {
|
||
|
||
time_tmp = 0;
|
||
|
||
for (int j = 0; j < a.length; j++) {
|
||
|
||
if (a[i] == a[j]) {
|
||
|
||
time_tmp += 1;
|
||
|
||
}
|
||
|
||
if (time_tmp > time_max) {
|
||
|
||
time_max = time_tmp;
|
||
|
||
val_max = a[i];
|
||
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
|
||
System.out.println(val_max);
|
||
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>这段代码中,我们采用了双层循环的方式计算:第一层循环,我们对数组中的每个元素进行遍历;第二层循环,对于每个元素计算出现的次数,并且通过当前元素次数 time_tmp 和全局最大次数变量 time_max 的大小关系,持续保存出现次数最多的那个元素及其出现次数。由于是双层循环,这段代码在时间方面的消耗就是 n*n 的复杂度,也就是 O(n²)。</p>
|
||
|
||
<p>在这里,我们给出一些经验性的结论:</p>
|
||
|
||
<ul>
|
||
|
||
<li>一个顺序结构的代码,时间复杂度是 O(1)。</li>
|
||
|
||
<li>二分查找,或者更通用地说是采用分而治之的二分策略,时间复杂度都是 O(logn)。这个我们会在后续课程讲到。</li>
|
||
|
||
<li>一个简单的 for 循环,时间复杂度是 O(n)。</li>
|
||
|
||
<li>两个顺序执行的 for 循环,时间复杂度是 O(n)+O(n)=O(2n),其实也是 O(n)。</li>
|
||
|
||
<li>两个嵌套的 for 循环,时间复杂度是 O(n²)。</li>
|
||
|
||
</ul>
|
||
|
||
<p>有了这些基本的结论,再去分析代码的时间复杂度将会轻而易举。</p>
|
||
|
||
<h4>降低时间复杂度的必要性</h4>
|
||
|
||
<p>很多新手的工程师,对降低时间复杂度并没有那么强的意识。这主要是在学校或者实验室中,参加的课程作业或者科研项目,普遍都不是实时的、在线的工程环境。</p>
|
||
|
||
<p>实际的在线环境中,用户的访问请求可以看作一个流式数据。假设这个数据流中,每个访问的平均时间间隔是 t。如果你的代码无法在 t 时间内处理完单次的访问请求,那么这个系统就会一波未平一波又起,最终被大量积压的任务给压垮。这就要求工程师必须通过优化代码、优化数据结构,来降低时间复杂度。</p>
|
||
|
||
<p>为了更好理解,我们来看一些数据。假设某个计算任务需要处理 10 万 条数据。你编写的代码:</p>
|
||
|
||
<ul>
|
||
|
||
<li>如果是 O(n²) 的时间复杂度,那么计算的次数就大概是 100 亿次左右。</li>
|
||
|
||
<li>如果是 O(n),那么计算的次数就是 10 万 次左右。</li>
|
||
|
||
<li>如果这个工程师再厉害一些,能在 O(log n) 的复杂度下完成任务,那么计算的次数就是 17 次左右(log 100000 = 16.61,计算机通常是二分法,这里的对数可以以 2 为底去估计)。</li>
|
||
|
||
</ul>
|
||
|
||
<p>数字是不是一下子变得很悬殊?通常在小数据集上,时间复杂度的降低在绝对处理时间上没有太多体现。但在当今的大数据环境下,时间复杂度的优化将会带来巨大的系统收益。而这是优秀工程师必须具备的工程开发基本意识。</p>
|
||
|
||
<h4>总结</h4>
|
||
|
||
<p>OK,今天的内容到这儿就结束了。相信你对复杂度的概念有了进一步的认识。</p>
|
||
|
||
<p>复杂度通常包括时间复杂度和空间复杂度。在具体计算复杂度时需要注意以下几点。</p>
|
||
|
||
<ol>
|
||
|
||
<li><strong>它与具体的常系数无关</strong>,O(n) 和 O(2n) 表示的是同样的复杂度。</li>
|
||
|
||
<li><strong>复杂度相加的时候,选择高者作为结果</strong>,也就是说 O(n²)+O(n) 和 O(n²) 表示的是同样的复杂度。</li>
|
||
|
||
<li><strong>O(1) 也是表示一个特殊复杂度</strong>,即任务与算例个数 n 无关。</li>
|
||
|
||
</ol>
|
||
|
||
<p>复杂度细分为时间复杂度和空间复杂度,其中时间复杂度与<strong>代码的结构设计</strong>高度相关;空间复杂度与代码中<strong>数据结构的选择</strong>高度相关。会计算一段代码的时间复杂度和空间复杂度,是工程师的基本功。这项技能你在实际工作中一定会用到,甚至在参加互联网公司面试的时候,也是面试中的必考内容。</p>
|
||
|
||
<h3>练习题</h3>
|
||
|
||
<p>下面的练习题,请你独立思考。评估一下,如下的代码片段,时间复杂度是多少?</p>
|
||
|
||
<pre><code>for (i = 0; i < n; i++) {
|
||
|
||
for (j = 0; j < n; j++) {
|
||
|
||
for (k = 0; k < n; k++) {
|
||
}
|
||
|
||
for (m = 0; m < n; m++) {
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>关于复杂度的评估,需要你深入理解本节课的知识点。最后,你工作中有遇到过关于计算复杂度的哪些实际问题吗?你又是如何解决的?欢迎你在留言区和我分享。</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div>
|
||
|
||
<div style="float: left">
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/00 数据结构与算法,应该这样学!.md.html">上一页</a>
|
||
|
||
</div>
|
||
|
||
<div style="float: right">
|
||
|
||
<a href="/专栏/重学数据结构与算法-完/02 数据结构:将“昂贵”的时间复杂度转换成“廉价”的空间复杂度.md.html">下一页</a>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
|
||
|
||
</div>
|
||
|
||
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997dbd9a5a3cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
|
||
|
||
</body>
|
||
|
||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||
|
||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
|
||
|
||
<script>
|
||
|
||
window.dataLayer = window.dataLayer || [];
|
||
function gtag() {
|
||
|
||
dataLayer.push(arguments);
|
||
|
||
}
|
||
gtag('js', new Date());
|
||
|
||
gtag('config', 'G-NPSEEVD756');
|
||
|
||
var path = window.location.pathname
|
||
|
||
var cookie = getCookie("lastPath");
|
||
|
||
console.log(path)
|
||
|
||
if (path.replace("/", "") === "") {
|
||
|
||
if (cookie.replace("/", "") !== "") {
|
||
|
||
console.log(cookie)
|
||
|
||
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
setCookie("lastPath", path)
|
||
|
||
}
|
||
function setCookie(cname, cvalue) {
|
||
|
||
var d = new Date();
|
||
|
||
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
|
||
|
||
var expires = "expires=" + d.toGMTString();
|
||
|
||
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
|
||
|
||
}
|
||
function getCookie(cname) {
|
||
|
||
var name = cname + "=";
|
||
|
||
var ca = document.cookie.split(';');
|
||
|
||
for (var i = 0; i < ca.length; i++) {
|
||
|
||
var c = ca[i].trim();
|
||
|
||
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
|
||
|
||
}
|
||
|
||
return "";
|
||
|
||
}
|
||
</script>
|
||
</html>
|
||
|