算法:時間復雜度+二分查找法(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(n*logn)是O(logn)、O(n)兩種復雜度的一種組合,在后續的文章中要給大家介紹的“快速排序算法”(一種速度較快的排序算法),其時間復雜度就是O(n*logn),這里大家可以暫時先放一下,等后面具體講述此算法時,可以體會下相應的代碼示例。

 

 

平方級的復雜度,在后續要介紹的算法中,“選擇排序算法”(一種速度較慢的算法)的時間復雜度就是這個級別的。如👇

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的數字:

 

 

1

2

3

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

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM