一、算法复杂度与简单排序算法

简介: 一、算法复杂度与简单排序算法

复杂度

时间复杂度

什么是常数操作

常数时间的操作:一个操作如果和样本的数据量没有关系,每次都是固定时间内完成的操作,叫做常数操作。

以下为常数操作:

int a = arr[i]   //数组中寻址
+ - * /          //加减乘除
复制代码

以下不是常数操作:

int a = list.get(i) // 这是链表结构,需要逐个遍历,跟list数据量有关
复制代码

时间复杂度的定义

时间复杂度是衡量常数操作数量的一个指标。即算出算法流程中发生了多少常数操作,进而总结出常数操作数量的表达式。

注意表达式中只要高阶项,不要低阶项,也不要高阶项的系数。

举个🌰:在算法流程中实际发生了aN2+bN+c{aN^{2}+bN+c}aN2+bN+c次常数操作,时间复杂度则为O(N2){O(N^{2})}O(N2)

// O(1)
let i = 0;
i+=1;
// O(n)
for(let i=0;i<n;i+=1){
    console.log(i);
}
// O(1)+O(n)=O(n)
let i = 0;
i+=1;
for(let i=0;i<n;i+=1){
    console.log(i);
}
// O(n)*O(n)=O(n^2)
for(let i=0;i<n;i+=1){
    for(let j=0;j<n;j+=1){
        console.log(i,j)
    }
}
// O(logN)
let i = 1;
while(i<n){
    console.log(i);
    i*=2;
}
复制代码

评级一个算法流程的好坏,先看时间复杂度的指标,如果指标无法区分出,就直接用真实数据进行测试。

空间复杂度

如果只需要有限个变量就可以完成算法,则空间复杂度为O(1)O(1)O(1),如果必须开辟一个数组,则空间复杂度为O(n)O(n)O(n)

Q1:如果一段代码中有3个循环,它们的循环次数都是n,那么这段代码的时间复杂度是 O(3n) 还是 O(n)?

答:

  • 循环并列,时间复杂度O(n)
  • 循环嵌套,时间复杂度O(n^3)

Q2:假设每天睡觉前,你都会数2的次方,1、2、4、8⋯⋯,每次你都数到 n 才睡着,那么你数了几个数?时间复杂度是多少?

答:O(n)

简单排序算法

选择排序

思路:

  • 找到数组中的最小值,选中它并将其放置在第一位
  • 接着找到第二小的值,选中它并将其放置在第二位
  • 以此类推,执行n-1轮
Array.prototype.selectionSort = function(){
    for(let i=0; i<this.length - 1;i++){  
        let minIndex = i;
        for(let j=i;j<this.length;j++){
            if(this[j]<this[minIndex]){
                minIndex = j
            }
        }
        if(minIndex !== i){
            const temp = this[i];
            this[i] = this[minIndex];
            this[minIndex] = temp;
        }
    }
}
const arr = [5,7,3,2,1];
arr.selectionSort();
复制代码

时间复杂度为O(N2)O(N^2)O(N2),空间复杂度为O(1)O(1)O(1)

冒泡排序

思路:

  • 比较所有相邻元素,如果第一个比第二个大,则交换它们
  • 一轮下来,可以保证最后一个数是最大的(第一轮确定了第n位置的数,第二轮确定了第n-1位置的数)
  • 执行n-1轮,就可以完成排序
Array.prototype.bubbleSort = function(){
    for(let i = this.length;i>0;i--){
        for(let j = 0;j<i;j++){
            if(this[j]>this[j+1]){
                let temp = this[j];
                this[j] = this[j+1];
                this[j+1]=temp;
            }
        }
    }
}
const arr = [5, 4, 3, 2, 1];
arr.bubbleSort();
复制代码

时间复杂度为O(N2)O(N^2)O(N2),空间复杂度为O(1)O(1)O(1)

插入排序

思路:

  • 从第二个数开始往前比
  • 比它大就往后排
  • 以此类推进行到最后一个数,从而依次确定0,1,2...位置的数

特殊:根据数据的不同,实际的常数操作次数不同。不过时间复杂度是按最差情况估计,所以说插入排序的时间复杂度为O(N2)O(N^2)O(N2)

Array.prototype.insertSort = function () {
    for (let i = 1; i < this.length; i++){
        for(let j=i-1;j>=0 && this[j] > this[j+1];j--){
          console.log(this[j])
          const temp = this[j];
          this[j] = this[j+1];
          this[j+1] = temp;
        }
    }
}
const arr = [5, 4, 3, 2, 1];
arr.insertSort();
复制代码

时间复杂度为O(N2)O(N^2)O(N2),空间复杂度为O(1)O(1)O(1)

工具:

VisuAlgo-数据结构和算法动态可视化 (Chinese)

二分搜索

思路:

  • 从数组中间元素开始,如果中间元素正好是目标值,则搜索结束
  • 如果目标值大于或小于中间元素,则在大于或小于中间元素的那一半数组中搜索

时间复杂度为O(log⁡N)O(\log{N})O(logN),即O(log⁡2N)O(\log_2{N})O(log2N)(每次比较都使搜索范围缩小一半),空间复杂度为O(1)O(1)O(1)

二分法的扩展

  • 在一个有序数组中,找某个数是否存在
  • 在一个有序数组中,找>=某个数最左侧的位置
  • 局部最小值问题 例如:在一个无序数组arr中,任意两个相邻的数不相等,求一个局部最小的位置,要求时间复杂度为O(N)O(N)O(N)

归并排序

总体思路:

  • 分:把数组分为两个部分,再递归的对子数组进行“分”操作,直到分成一个个单独的数
  • 合:把两个数合并为有序数组,再对有序数组进行合并,直到全部子数组合并为一个完整数组 合并的思路:
  • 新建一个空数组res,用于存放最终排序后的数组
  • 比较两个有序数组的头部,较小者出队并推入res中
  • 如果有两个数组还有值,重复上一步骤

js版本的实现

Array.prototype.mergeSort = function () {
    const rec = (arr) => {
        if (arr.length === 1) {
            return arr;
        }
        const mid = arr.length >> 1;
        const left = arr.slice(0, mid);
        const right = arr.slice(mid, arr.length);
        const orderLeft = rec(left)
        const orderRight = rec(right)
        const res = []
        while (orderLeft.length || orderRight.length) {
            if (orderLeft.length && orderRight.length) {
                res.push(orderLeft[0] < orderRight[0] ? orderLeft.shift() : orderRight.shift())
            } else if (orderLeft.length) {
                res.push(orderLeft.shift())
            } else if (orderRight.length) {
                res.push(orderRight.shift())
            }
        }
        return res;
    }
    const res = rec(this)
    res.forEach((n, i) => { this[i] = n;})
}
const arr = [5, 7, 3, 2, 1];
arr.mergeSort();
复制代码

左神版本的实现

public static void process(int[] arr, int L, int R){
    if(L==R){
        return;
    }
    int mid = L+((R-L)>>1);
    process(arr, L,mid);
    process(arr, mid+1,R);
    merge(arr,L,mid,R);
}
public static void merge(int[] arr, int L, int M, int R){
    int[] help=new int[R-L+1];
    int i = 0;
    int p1=L;
    int p2=M+1;
    while(p1<=M &&p2<=R){
        help[i++]=arr[p1]<=arr[p2]?arr[p1++]:arr[p2++];
    }
    while(p1<=M){
        help[i++]=arr[p1++];
    }
    while(p2<=R){
        help[i++]=arr[p2++];
    }
    for(i=0;i<help.length;i++){
        arr[L+i]=help[i];
    }
}
复制代码

总的时间复杂度为O(n∗logN)O(n*log{N})O(nlogN)(分的时间复杂度为O(logN)O(log{N})O(logN),合的时间复杂度是O(n)O(n)O(n));空间复杂度为O(N)O(N)O(N)

有关链接:

我是如何进微软的?

七天刷爆LeetCode,顶级算法大神



相关文章
|
1月前
|
机器学习/深度学习 存储 缓存
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
文章主要介绍了排序算法的分类、时间复杂度的概念和计算方法,以及常见的时间复杂度级别,并简单提及了空间复杂度。
25 1
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
|
5月前
|
算法 搜索推荐 测试技术
【调度算法】快速非支配排序算法
【调度算法】快速非支配排序算法
53 3
|
1月前
|
搜索推荐 算法
数据结构与算法学习十四:常用排序算法总结和对比
关于常用排序算法的总结和对比,包括稳定性、内排序、外排序、时间复杂度和空间复杂度等术语的解释。
19 0
数据结构与算法学习十四:常用排序算法总结和对比
|
1月前
|
机器学习/深度学习 存储 算法
【数据结构与算法基础】——算法复杂度
【数据结构与算法基础】——算法复杂度
|
1月前
|
机器学习/深度学习 搜索推荐 算法
探索数据结构:初入算法之经典排序算法
探索数据结构:初入算法之经典排序算法
|
5月前
|
搜索推荐 算法 数据挖掘
十大排序算法详解-上篇:比较排序算法【python 动态图解】
十大排序算法详解-上篇:比较排序算法【python 动态图解】
|
5月前
|
存储 算法 搜索推荐
【数据结构和算法】--- 基于c语言排序算法的实现(2)
【数据结构和算法】--- 基于c语言排序算法的实现(2)
37 0
|
5月前
|
搜索推荐 算法 大数据
​【数据结构与算法】冒泡排序:简单易懂的排序算法解析
​【数据结构与算法】冒泡排序:简单易懂的排序算法解析
|
5月前
|
搜索推荐 算法 C语言
【数据结构和算法】--- 基于c语言排序算法的实现(1)
【数据结构和算法】--- 基于c语言排序算法的实现(1)
42 0
|
6月前
|
存储 算法 搜索推荐
黑马c++ STL常用算法 笔记(3) 排序算法
黑马c++ STL常用算法 笔记(3) 排序算法