算法:时间复杂度+二分查找法(Java/Go/Python)实现

导读

曾几何时学好数据结构与算法是我们从事计算机相关工作的基本前提,然而现在很多程序员从事的工作都是在用高级程序设计语言(如Java)开发业务代码,久而久之,对于数据结构和算法就变得有些陌生了,由于长年累月的码砖的缘故,导致我们都快没有这方面的意识了,虽然这种论断对于一些平时特别注重学习和思考的人来说不太适用,但的确是有这样的一个现象。

而在要出去面试找工作的时候,才发现这些基础都快忘光光了,所以可能就“杯具”了!实际上,对于数据结构和算法相关的知识点的学习,是程序员必须修炼的一门内功,而要掌握得比较牢靠除了需要在写代码的时候时刻保持这方面的意识外,也需要日常的训练,换一个目前比较流行的词叫刻意练习

这就像打乒乓球一样,虽然大家都会打,但是要打得好,打出水准就得经常训练。而学习算法的过程也是这样,因为大部分人的脑容量有限,对于学过的算法知识虽然之前理解过,但是因为时间的关系和算法本身就是比较抽象的一种知识,所以容易忘记。那么有没有什么好的练习工具呢?

在这里给大家推荐一个练习数据结构和算法编程的网站

https://leetcode.com(因为墙的原因,你可能需要搭个梯子,或者也可以访问中文版的网站)这是一个目前很多硅谷的公司或程序员在学习或者招聘时都在使用的在线练习网站。上面有很多数据结构和算法的题,可以选择不同的编程语言实现,还支持代码社交,你提交的代码可以被全世界的程序员看到并被评论,从而得到相应地反馈。

以本文将要讲述的二分查找算法为例,在给大家的代码示例中作者就在这个网站上使用Java/Go/Python三种语言进行了实现,如👇图所示:

算法复杂度

因为我是想做一个比较系统的总结,所以在给大家分享具体的算法内容之前,需要先和大家一起回顾下什么是算法复杂度

在编程的过程中,特别是写一些比较基础的逻辑代码时,我们经常会讨论说这段代码的时间复杂度是多少,空间复杂度是多少之类的术语。而这两项指标就是我们衡量我们写的代码(任何代码片段都可以视为算法)好坏最主要的两个标准了,时间复杂度是说我们写的这段代码的运行时间,而空间复杂度则是在说这段代码运行所占用的内存空间大小。

一般来说,我们在选择算法、编写相应的代码时都应该尽量选择运行时间最快,内存空间占用最少的方式。然而作为衡量算法好坏的标准,关于时间复杂度、空间复杂度如何衡量?目前是通过大O表示法来表示的,也就是我们经常说的O(xx),例如O(1)、O(n)之类。

以下是我们常见的一些大O运行时间的表示(从快到慢):

这是一种常数级的复杂度,这种复杂度的算法运行效率是最高的。例如,我们要计算“1+2+3+...+n”的和(假设n=100)?

如果我们采用如👇方式实现,那么100的累加和的计算,这段代码需要执行100次,1000累加和则需要执行1000次。

 public static void main(String args[]) {

       int y = 0;
       for (int i = 0; i <= 100; i++) {
           y = i + y;
       }
       System.out.println(y);
   }

而如果我们换种方式如👇


  public static void main(String args[]) {

        int y = 0;
        y = 100 * (100 + 1) / 2;
        System.out.println(y);
    }

那么无论多少的累加和,10000、100000、100000?上面这段程序都是需要执行一次,此时我们就可以说这段代码的时间复杂度是O(1)了。

这是一种对数级的复杂度。可能上学的时候是因为体育老师教数学的缘故小码农已经忘记什么是对数了,在这里和大家一起复习下,假如你忘记了对数的概念,但是幂的概念你一定还是记得的log8相当于在问“将多少个2相乘的结果为8”,正常来说log对数还有个下标,因为我们在讨论算法复杂度时通常默认对数的下标为2,如2x2x2=8,所以log8=3。

那么什么样的算法的时间复杂度是对数级的呢?后面我们即将讨论的第一种算法(二分查找法)的时间复杂度就是对数级的,关于这块的代码示例,大家可以直接参考后面的示例。

O(n)是一种线性的时间复杂度,如前面我们在说0(1)时,如果计算累加和的操作采用第一种方式,那么100的累加和需要执行100次,1000的累加和就需要执行1000次,以此类推,这样的时间复杂度就称之为O(n)。

O(nlogn)是O(logn)、O(n)两种复杂度的一种组合,在后续的文章中要给大家介绍的“快速排序算法”(一种速度较快的排序算法),其时间复杂度就是O(nlogn),这里大家可以暂时先放一下,等后面具体讲述此算法时,可以体会下相应的代码示例。

平方级的复杂度,在后续要介绍的算法中,“选择排序算法”(一种速度较慢的算法)的时间复杂度就是这个级别的。如👇

public static void main(String args[]) {

       int[] arr = new int[]{4, 1, 3, 2, 6, 7, 8};
       for (int i = 0; i < arr.length; i++) {
           int m = i;
           for (int j = i + 1; j < arr.length; j++) {
               //如果第j个元素比第m个元素小,将j赋值给m
               if (arr[j] < arr[m]) {
                   m = j;
               }
           }
           //交换m和i两个元素的位置
           if (i != m) {
               int t = arr[i];
               arr[i] = arr[m];
               arr[m] = t;
           }
       }

       for (int i = 0; i < arr.length; i++) {
           System.out.print(arr[i]);
           System.out.print(",");

       }

   }

阶乘级的复杂度表示,时间复杂度为O(n!)的算法是一个非常慢的算法,例如斐波那契数列问题。如👇

 public static int fib(int num) {
        if (num == 1 || num == 2) {
            return 1;
        } else {
            return fib(num - 2) + fib(num - 1);
        }
    }

    public static void main(String[] args) {

        for (int i = 1; i <= 6; i++) {
            System.out.print(fib(i) + "\t");
        }
    }

以上就是我们在讨论算法复杂度时大部分的表示方法了,需要说明的是算法的速度并非单单指时间,而是操作数的增速,说的是随着输入的增加,其运行时间将以什么样的速度增加,例如O(log n)比O(n)快,当需要搜索的元素越多时,O(log n)比O(n)快得越多。

二分查找法

在了解算法复杂度后,我们来介绍一个相对基础的算法“二分查找法”!其输入是一个有序的元素列表(必须有序),如果查找的元素包含在这个有序列表中,二分查找就会返回其位置,否则返回-1。

假设有一个1~100的数字:

目标是以最少的次数从这个100个数字中找到指定的数字。通常思路是,我们需要从1开始一个一个比较,如果指定的数字是100,那么用这种傻找的方式需要找100次。如果范围扩大到1000,查找1000,那么相应地就需要找1000次,依次类推。

很显然,这种方式不是很靠谱。那么有没有更好的方法呢?这就是我们要说的二分查找法了,它的思路是先从元素的中间开始查找,如直接查找第50(第一次)个元素,比较中间元素与目标元素之间的大小。

如果我们还是查找100的话,那么50比100小,这样我们就可以一次性排除前50个元素了,因为已经很明确的知道了前50个元素都比目标元素小了,这些元素也就没有必要继续参与比较了。

第二次,我们从51~100之间再取中间元素进行判断,取75进行判断,75依然比100小,那么51~75这一段的数字也就直接排除掉了。

第三次,我们从76~100之间取中间元素,88,88<100;第四次,继续查找取89~100之间的中间元素95,95<100;第五次,继续取96~100中间元素98,98<100;第六次,继续取99~100之间的中间元素100,100=100,完成查找。

通过这种方式,我们总共运行了6次就完成了查找动作,相比之前的100次查找要靠谱,而这就是二分查找算法的基本原理了。

按照这种方式,即使列表中包含40亿个有序元素,最多也只需要32次就能完成查找。所以如果用前面描述的大O表示法,表示二分查找算法的时间复杂度是O(log n)

好了,到这里就讲完二分查找算法的基本原理了,那么在具体的程序代码中,二分查找算法应该如何实现呢?以下为大家准备了Java/Go/Python三种版本的实现,大家可以在leetcode上尝试自己实现下。

#Java

public class Solution {

    public static int search(int[] nums, int target) {
        int low = 0;
        int high = nums.length - 1;
        int middle;
        while (low <= high) {
            middle = (low + high) / 2;
            if (nums[middle] == target) {
                return middle;
            } else if (nums[middle] < target) {
                low = middle + 1;
            } else{
                high = middle - 1;
            } 
        }
        return -1;
    }

    public static void main(String[] args) {
        int[] nums = {-1, 0, 3, 5, 9, 12};
        int result1 = search(nums, 9);
        int result2 = search(nums, 2);
        System.out.println("result is->" + result1);
        System.out.println("result is->" + result2);
    }
}

#Go

package main

import (
    "fmt"
)

func main() {
    nums := []int{-1, 0, 3, 5, 9, 12}
    result := search(nums, 2)
    fmt.Println(result)
}

func search(nums []int, target int) int {
    low, high, middle := 0, len(nums)-1, -1
    for low <= high {
        middle = (low + high) / 2
        if nums[middle] == target {
            return middle
        } else if nums[middle] < target {
            low = middle + 1
        } else {
            high = middle - 1
        }
    }
    return -1
}

#Python

class Solution:
    def search(result,nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        low,high,middle=0,len(nums)-1,-1
        while low<=high:
           middle=(low+high)//2
           if nums[middle]==target:
               return middle
           elif nums[middle]<target:
               low=middle+1
           else:
               high=middle-1
        return -1
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,172评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,346评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,788评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,299评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,409评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,467评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,476评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,262评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,699评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,994评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,167评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,499评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,149评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,387评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,028评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,055评论 2 352

推荐阅读更多精彩内容