javaScript知识点汇总(一)

前端面试知识点javascript

基本类型

基础
  1. undefined 不是关键词可能被修改 (指局部环境下) (现代浏览器已经修改undefined的 non-writable non-configurable的值 全局环境没法被修改)html

  2. null 是关键词前端

  3. void 0 为 unfefinedjava

  4. String 最大长度为 2^53 - 1 charAt、charCodeAt、length 等方法针对的都是 UTF16 编码 JavaScript 中的字符串是永远没法变动的node

  5. Number 类型中有效的整数范围是 -0x1fffffffffffff 至 0x1fffffffffffff面试

  6. 一样根据浮点数的定义,非整数的 Number 类型没法用 == 或 === (因此 0.1+0.2 == 0.3 //false) Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON // true 检查等式左右两边差的绝对值是否小于最小精度算法

  7. Number、String 和 Boolean,三个构造器是两用的,当跟 new 搭配时,它们产生对象,当直接调用时,它们表示强制类型转换。 使用 new Number(3) 获得的是对象shell

  8. 在 JavaScript 中,没有任何方法能够更改私有的 Class 属性,所以 Object.prototype.toString 是能够准确识别对象对应的基本类型的方法,它比 instanceof 更加准确。api

装箱拆箱

​ 在 JavaScript 标准中,规定了 ToPrimitive 函数,它是对象类型到基本类型的转换(即,拆箱转换)。数组

ToPrimitive 为内部函数,在将对象转换成原始值则会调用toPrimitive(input,preferedType?)

其中 input 为输入的值 preferedType是指望转换的类型

基本流程以下

  1. 若是input是原始值(原始类型),直接返回这个值
  2. 若是input是对象,则调用input。valueOf() 返回结果
  3. Else 调用 input.toString() 返回结果
  4. 不然抛出错误

若是转换的类型是String 2和3 顺序会变化

在操做符中,==,排序运算符,加减乘除,在对非原始值进行操做时,都会调用内部的toPrimitive()方法

tips: 加上操做符会将preferedType当作Number

[] + [] = ""
[] + {} = [object object]
{} + [] = 0

复制代码

装箱:将原始类型转成对象(原始类型有七种 boolean, number, string, undefined,null,bigInt,symbol)

var a=10 ,b="javascript" , c=true;
var o_a=new Number(a);
复制代码

​ 拆箱: 将引用类型对象转换为对应的值类型对象,它是经过引用类型的valueOf()或者toString()方法来实现的。若是是自定义的对象,你也能够自定义它的valueOf()/tostring()方法,实现对这个对象的拆箱。

var a=10;

var o_a=new Number(a);

var b=o_a.valueOf();
复制代码

js运行时

js属性

Javascript 数据属性
  • Value: 属性值
  • writable: 属性是否能被赋值
  • enumerable: 以为for in 可否枚举该属性
  • configurable: 决定该属性可否被删除或被修改特征值
Javascript 访问属性
  • getter: 函数或 undefined, 在读取属性值被调用。

  • setter: 函数或undefined,在设置属性值时被调用。

  • enumerable: 决定for in可否枚举该属性

  • configurable: 决定该属性可否被删除或改变特征值

操做方法

Object.getOwnPropertyDescripter
var a = { b: 1}
a.c = 2

Object.getOwnPropertyDescriptor(a,"b") 
// {value: 1, writable: true, enumerable: true, configurable: true} 默认都为true

Object.getOwnProertyDescriptor(a,"c")
// {value: 2, writable: true, enumerable: true, configurable: true}

复制代码
Object.defineProperty 定义属性
var o = { a: 1 }
Object.defineProperty(o, "b", {value: 2, writable: false, enumerable: false, configurable: true})
复制代码

javascript 类

Javascript 内置函数
  • object.create 根据指定的原型建立新对象,原型能够是nulll。
  • object.getPrototypeOf 获取一个对象的原型。
  • object.setPrototypeOf 设置一个对象的原型。

在 ES3 和以前的版本,JS 中类的概念是至关弱的,它仅仅是运行时的一个字符串属性。

在 ES5 开始,[[class]] 私有属性被 Symbol.toStringTag 代替

使用 Symbol.toStringTag 来自定义 Object.prototype.toString 的行为:

var o = { [Symbol.toStringTag]: "myObject" }
console.log(o + "");
复制代码

javascript 对象分类

分类
  • 宿主对象(Host Objects): 由 javaScript 宿主环境提供的对象,它们的行为彻底由宿主环境决定

    • window (固有)
    • 可建立的 document.createElemenet 建立的dom对象
    • 构造器 好比 new Image 建立img 元素
  • 内置对象(Built-in Object): 由 JavaScript 语言提供的对象。 (运行时runtime)

    • 固有对象(Intrinsic Object): 由标准规定,随着JavaScript运行时建立而自动建立的对象实例

      • 基础类库用到较多 150+固有对象
        • 举例如: ToPrimitive( input [, PreferredType]) 装箱拆箱
        • ToBoolean、ToNumber等
    • 原生对象(Native Object): 能够由用户经过 Array、RegExp 等内置构造器或者特殊语法建立的对象。

    • 普通对象(Ordinary Object): 由{}语法、Object 构造器或者class关键词定义类建立的对象,它可以被原型继承

    • Tips

      function a() {}
      a() // 包含[[call]]私有字段对象 表示能够做为函数被调用
      new a() // 包含 [[construct]] 构造器对象,能够做为构造器被调用
      // 对于部分宿主和内置对象两则有时效果不相同 好比 date image
      
      // 实现 私有变量
      function cls() {
        this.a = 100;
        return {
          getValue: ()=> this.a
        }
      }
      
      var b = new cls;
      b.getValue() // 100
      // a的值在外面没法访问到
      
      //如何建立一个对象
      //字面量建立
      var a = {}
      // dom api
      var d = document.createElement('p')
      // 内置对象
      var e = Object.create(null)
      // 装箱
      var f = Object(null)
      
      复制代码

js 执行机制

setTimeout会单独生产一个新的宏观任务,在上一个宏观任务执行完毕后执行。

执行上下文(在堆栈中)

ES3

  • scope:做用域,也经常被叫作做用域链。
  • variable object:变量对象,用于存储变量的对象。
  • this value:this 值。

ES5

  • lexical environment:词法环境,当获取变量时使用。
  • variable environment:变量环境,当声明变量时使用
  • this value:this 值。

ES2018

  • lexical environment:词法环境,当获取变量或者 this 值时使用。
  • variable environment:变量环境,当声明变量时使用。
  • code evaluation state:用于恢复代码执行位置。
  • Function:执行的任务是函数时使用,表示正在被执行的函数。
  • ScriptOrModule:执行的任务是脚本或者模块时使用,表示正在被执行的代码。
  • Realm:使用的基础库和内置对象实例。
  • Generator:仅生成器上下文有这个属性,表示当前生成器。

this

  • this 是执行上下文中很重要的一个组成部分。同一个函数调用方式不一样,获得的 this 值也不一样

  • 普通函数的 this 值由“调用它所使用的引用”决定,其中奥秘就在于:咱们获取函数的表达式,它实际上返回的并不是函数自己,而是一个 Reference 类型(记得咱们在类型一章讲过七种标准类型吗,正是其中之一)。Reference 类型由两部分组成:一个对象和一个属性值。不难理解 o.showThis 产生的 Reference 类型,即由对象 o 和属性“showThis”构成。

  • bind 、call、apply 函数经过函数调用时传入 this

  • javascript 标准 定义了 [[thisMode]]私有属性。[[thisMode]]有三个取值

    • lexical : 表示从上下文中找this, 这对应了箭头函数

    • Global: 表示当this为undefined时,取全局对象,对应了普通函数。

    • Strict: 当严格模式时使用,this严格按照调用时传入的值,可能为null或者undefined。

      Class 默认按照strict模式执行

      "use strict"
      function showThis() {
        console.log(this);
      }
      var o = {
        showThis: showThis
      }
      
      showThis(); // undefined 严格模式下 this按照传入的值 全部未定义
      o.showThis(); // o 
      复制代码

      函数建立新的执行上下文中的词法环境记录时,会根据 [[thisMode]] 来标记新纪录 [[ThisBindingStatus]] 私有属性。

      代码执行遇到 this 时,会逐层检查当前词法环境记录中的 [[ThisBindingStatus]],当找到有 this 的环境记录时获取 this 的值。

      这样的规则的实际效果是,嵌套的箭头函数中的代码都指向外层 this。

箭头函

  • 箭头函数不会绑定 this, 会捕获其所在的上下文this值,做为本身的this。嵌套的箭头函数中的代码都指向外层 this
  • 箭头函数是匿名函数,不能做为构造函数,不能使用new
  • 箭头函数不绑定arguments ,取而代之用reset参数...解决
  • 箭头函数经过call() 或apply 方法调用一个函数时,只传入了一个参数,对this并无影响
Completion 类型

Completion Record 用于描述异常、跳出等语句执行过程

Completion Record 表示一个语句执行完以后的结果,它有三个字段:

  • [[type]]表示完成的类型,有 break continue return throw 和 normal 几种类型;
  • [[value]] 表示语句的返回值,若是语句没有,则是 empty;
  • [[target]] 表示语句的目标,一般是一个 JavaScript 标签。

JavaScript 正是依靠语句的 Completion Record 类型,方才能够在语句的复杂嵌套结构中,实现各类控制。

普通语句执行后,会获得 [[type]] 为 normal 的 Completion Record,JavaScript 引擎遇到这样的 Completion Record,会继续执行下一条语句。

这些语句中,只有表达式语句会产生 [[value]],固然,从引擎控制的角度,这个 value 并无什么用处。

var i = 1 // 该语句 [[type]]:noraml [[value]]: empty [[target]]: empty
// undefined

{//语句块
  var i = 1; // [[type]]:noraml [[value]]: empty [[target]]: empty
  return i; // [[type]]:return [[value]]: 1 [[target]]: empty
} // return , 1, empty

//控制型语句

复制代码

穿透: 被跳过 | 消费:被执行 | 特殊处理:特殊处理

[[target]]

实际上,任何 JavaScript 语句是能够加标签的,在语句前加冒号便可:

firstStatement: var i = 1;
复制代码

大部分时候,这个东西相似于注释,没有任何用处。惟一有做用的时候是:与完成记录类型中的 target 相配合,用于跳出多层循环。

outer: while(true) { 
  inner: while(true) {
    break outer; 
  } 
} 
console.log("finished")
复制代码

在 JavaScript 中,咱们把不带控制能力的语句称为普通语句。

Statement completion 扩展

由于 JavaScript 语句存在着嵌套关系,因此执行过程实际上主要在一个树形结构上进行, 树形结构的每个节点执行后产生 Completion Record,根据语句的结构和 Completion Record,JavaScript 实现了各类分支和跳出逻辑。

Js 词法

词法规定了语言的最小语义单元:token

词法

  • WhiteSpace 空白字符
  • LineTerminator 换行符
  • Comment 注释
  • Token 词
    • IdentifierName 标识符名称,典型案例是咱们使用的变量名,注意这里关键字也包含在内了。(var let...)
    • Punctuator 符号,咱们使用的运算符和大括号等符号。
    • NumericLiteral 数字直接量,就是咱们写的数字。
    • StringLiteral 字符串直接量,就是咱们用单引号或者双引号引发来的直接量。
    • Template 字符串模板,用反引号` 括起来的直接量。

js语法

脚本和模块

首先,JavaScript 有两种源文件,一种叫作脚本,一种叫作模块。这个区分是在 ES6 引入了模块机制开始的,在 ES5 和以前的版本中,就只有一种源文件类型(就只有脚本)。

脚本是能够由浏览器或者 node 环境引入执行的,而模块只能由 JavaScript 代码用 import 引入执行

从概念上,咱们能够认为脚本具备主动性的 JavaScript 代码段,是控制宿主完成必定任务的代码;而模块是被动性的 JavaScript 代码段,是等待被调用的库。

现代浏览器能够支持用 script 标签引入模块或者脚本,若是要引入模块,必须给 script 标签添加 type=“module”。若是引入脚本,则不须要 type。

<script type="module" src="xxxxx.js"></script>

复制代码

export 有一种特殊的用法,就是跟 default 联合使用。export default 表示导出一个默认变量值,它能够用于 function 和 class。这里导出的变量是没有名称的,可使用import x from "./a.js"这样的语法,在模块中引入。

  • require是运行时调用,因此require理论上能够运用在代码的任何地方
  • require是赋值过程,其实require的结果就是对象、数字、字符串、函数等,再把require的结果赋值给某个变量
  • import是编译时调用
    • import是解构过程,可是目前全部的引擎都尚未实现import,咱们在node中使用babel支持ES6,也仅仅是将ES6转码为ES5再执行,import语法会被转码为require

函数体

函数体其实也是一个语句的列表。跟脚本和模块比起来,函数体中的语句列表中多了 return 语句能够用。

// 普通函数体
function foo(){ 
  //Function body
}
// 异步函数体
async function foo() {
  // todo
}
// 生成器函数体
function *foo() {
  ....
}
// 异步生成器函数体
async function *foo() {
  ...
}
复制代码

预处理

var声明

var 声明永远做用于脚本、模块和函数体这个级别,在预处理阶段,不关心赋值的部分,只管在当前做用域声明这个变量。

var 会穿透一切语句结构如 for if

function声明

function 声明出如今 if 等语句中的状况有点复杂,它仍然做用于脚本、模块和函数体级别,在预处理阶段,仍然会产生变量,它再也不被提早赋值:

console.log(foo);
if(true) { 
  function foo(){ }
}
// undefined
复制代码
class声明

class 的声明做用不会穿透 if 等语句结构,因此只有写在全局环境才会有声明做用

指令序言机制

脚本和模块都支持一种特别的语法,叫作指令序言(Directive Prologs)。

这里的指令序言最先是为了 use strict 设计的,它规定了一种给 JavaScript 代码添加元信息的方式。

"use strict"; // 指令序言
function f(){ 
  console.log(this);
};
f.call(null); // null
复制代码

语句块

循环语句

while , do while

for in 循环

for in 循环枚举对象的属性,这里体现了属性的 enumerable 特征。

let o = { a: 10, b: 20}

Object.defineProperty(o, "c", {enumerable:false, value:30})

for(let p in o)
  console.log(p);
复制代码

for of 循环和 for await of 循环

iterator 机制,咱们能够给任何一个对象添加 iterator,使它能够用于 for of 语句

let o = { 
  [Symbol.iterator]:() => ({ 
    _value: 0, next(){ 
      if(this._value == 10) return { done: true } 
      else return { value: this._value++, done: false };
    } 
  })
}
for(let e of o) console.log(e);// 0 1 2 3 4 5 6 7 8 9
复制代码

generator function 定义iterator

function* foo() {
  yield 0;
  yield 1;
  yield 2;
  yield 3;
}
for(let e of foo())
  console.log(e) // 0 1 2 3

复制代码

异步生成器函数

表达式语句

表达式语句实际上就是一个表达式,它是由运算符链接变量或者直接量构成的

PrimaryExpression 主要表达式

表达式的原子项:Primary Expression。它是表达式的最小单位,它所涉及的语法结构也是优先级最高的。

Primary Expression 包含了各类“直接量”,直接量就是直接用某种语法写出来的具备特定类型的值。咱们已经知道,在运行时有各类值,好比数字 123,字符串 Hello world,因此通俗地讲,直接量就是在代码中把它们写出来的语法。

任何表达式加上圆括号,都被认为是 Primary Expression,这个机制使得圆括号成为改变运算优先顺序的手段。

( a + b )
复制代码
MemberExpression 成员表达式

Member Expression 一般是用于访问对象成员的。它有几种形式:

a.b;
a["b"];
new.target;
super.b;
复制代码

Member Expression 最初设计是为了属性访问的,不过从语法结构须要,如下两种在 JavaScript 标准中当作 Member Expression:

f`a${b}c`;
复制代码

这是一个是带函数的模板,这个带函数名的模板表示把模板的各个部分算好后传递给一个函数。

new Cls();
复制代码

另外一个是带参数列表的 new 运算,注意,不带参数列表的 new 运算优先级更低,不属于 Member Expression。

NewExpression NEW 表达式

这种很是简单,Member Expression 加上 new 就是 New Expression(固然,不加 new 也能够构成 New Expression,JavaScript 中默认独立的高优先级表达式均可以构成低优先级表达式)。

CallExpression 函数调用表达式

除了 New Expression,Member Expression 还能构成 Call Expression。它的基本形式是 Member Expression 后加一个括号里的参数列表,或者咱们能够用上 super 关键字代替 Member Expression。

a.b(c);
super();
a.b(c)(d)(e);
a.b(c)[3];
a.b(c).d;
a.b(c)`xyz`;
复制代码
LeftHandSideExpression 左值表达式 (条件表达式)

New Expression 和 Call Expression 统称 LeftHandSideExpression,左值表达式。

左值表达式就是能够放到等号左边的表达式。

a() = b
复制代码

这样的用法实际上是符合语法的,只是,原生的 JavaScript 函数,返回的值都不能被赋值。所以多数时候,咱们看到的赋值将会是 Call Expression 的其它形式,如:

a().c = b;
复制代码

左值表达式最经典的用法是用于构成赋值表达式。

AssignmentExpression 赋值表达式

AssignmentExpression 赋值表达式也有多种形态,最基本的固然是使用等号赋值:

a = b
复制代码

这里须要理解的一个稍微复杂的概念是,这个等号是能够嵌套的:

a = b = c = d
复制代码

这样的连续赋值,是右结合的,它等价于下面这种:

a = (b = (c = d))
复制代码
Expression 表达式

赋值表达式能够构成 Expression 表达式的一部分。在 JavaScript 中,表达式就是用逗号运算符链接的赋值表达式。

在 JavaScript 中,比赋值运算优先级更低的就是逗号运算符了。咱们能够把逗号能够理解为一种小型的分号。

a = b, b = 1, null;
复制代码

逗号分隔的表达式会顺次执行,就像不一样的表达式语句同样。“整个表达式的结果”就是“最后一个逗号后的表达式结果”。好比咱们文中的例子,整个“a = b, b = 1, null;”表达式的结果就是“,”后面的null。

更新表达式 UpdateExpression

左值表达式搭配 ++ -- 运算符,能够造成更新表达式。

-- a;
++ a;
a --;
a ++;
复制代码

更新表达式会改变一个左值表达式的值。分为先后自增,先后自减一共四种。

一元运算表达式 UnaryExpression
delete a.b;
void a;
typeof a;
- a;
~ a;
! a;
await a;
复制代码

它的特色就是一个更新表达式搭配了一个一元运算符。

乘方表达式 ExponentiationExpression

乘方表达式也是由更新表达式构成的。它使用**号。

++ i ** 30
2 **30 //正确
-2 ** 30 //报错
// ** 运算为右结合
复制代码
乘法表达式 MultiplicativeExpression
x * 2;
10 % 2;
10 / 2;
复制代码
加法表达式 AdditiveExpression
+ 
-
复制代码
移位表达式 ShiftExpression

移位运算把操做数看作二进制表示的整数,而后移动特定位数。因此左移 n 位至关于乘以 2 的 n 次方,右移 n 位至关于除以 2 取整 n 次。

<< 向左移位
>> 向右移位
>>> 无符号向右移位
复制代码

普通移位会保持正负数。无符号移位会把减号视为符号位 1,同时参与移位:

-1 >>> 1 // 2^31
复制代码
关系表达式 RelationalExpression

移位表达式能够构成关系表达式

<=
>=
<
>
instanceof
in
复制代码
相等表达式 EqualityExpression
a instanceof "object" == true
复制代码
位运算表达式

位运算表达式含有三种:

  • 按位与表达式 BitwiseANDExpression
  • 按位异或表达式 BitwiseANDExpression
  • 按位或表达式 BitwiseORExpression
逻辑与表达式和逻辑或表达式
  • || &&
条件表达式 ConditionalExpression

条件表达式由逻辑或表达式和条件运算符构成,条件运算符又称三目运算符,它有三个部分,由两个运算符?和:配合使用。

condition ? branch1 : branch2
复制代码

算法

分类

基础概念
  • 原地排序: 指空间复杂度是o(1)的排序算法
  • 稳定性:通过排序后,相等元素的原有前后顺序不变
冒泡排序 bubble sort

算法思想:冒泡排序只会操做相邻的两个数据。每次冒泡操做都会对相邻的两个元素进行⽐较,看是否知足大小关系要求,若是不满⾜就 重复n次,完成n个数据的排序

// 冒泡排序 a表示数组, n表示数组大小
function bubbleSort(a,n) {
  if(n <= 1) return
  for(let i = 0;i < n; ++i) {
    // 提早退出冒泡排序循环的标志位
    let flag = false
    for (let j = 0; j < n -i -1; ++j) {
      if(a[j] > a[j+1]) { // 交换
        	[a[j],a[j+1]] = [a[j+1],a[j]]
	        flag = true
      }
    }
    if(!flag) break // 没有数据交换,提早退出
  }
} 
复制代码

稳定性:冒泡过程当中只有交换才会改变两个元素的先后顺序,为了了保证算法的稳定性,当相邻两个元素大小相等时,咱们不作交换,相同 顺序不会发⽣改变,因此是稳定的排序算法。

空间复杂度: 冒泡过程只涉及相邻元素的交换操做,只须要常量量级的临时空间,因此空间复杂度是O(1),是原地排序算法。

时间复杂度:

  • 最好状况是已经排好序,只须要1次冒泡,时间复杂度O(n),

  • 最坏状况是倒序排列的状况,须要进行n次冒泡,时间复杂度是O(n2)

  • 平均状况: 经过有序度和逆序度分析

  • 冒泡排序包含2个操做: 比较交换,每交换一次,有序度就+1,总交换次数是肯定的,便是逆序度,也就是 n*(n-1)/2初始有序 最好状况交换次数0,最坏状况交换次数n*(n-1)/2,平均状况下交换次数取二者平均值n*(n-1)/4,而⽐较操做确定比交换次数多, ⽽复杂度上限是O(n^2),因此平均状况的时间复杂度O(n^2)

    Tips: 有序度逆序度: 对于一个不彻底有序的数组,如四、五、六、三、二、1,有序元素对为3个(4,5)、(4,6)和(五、6) 对于⼀一个彻底有序的数组,如一、二、三、四、五、6,有序度就是n*(n-1)/2,也就是15,称为满有序度; 关于这3个概念,有一个公式:逆序度=满有序度-有序度,排序的过程就是增长有序度,减小逆序度的过程,最后达到满有序度 )

插入排序 insertion sort

对于⼀个有序数组,往里面添加一个新元素,遍历该数组,找到须要插入的位置,将其插入。

算法思想: 把数组中数据分为2个区间,已排序区未排序区。初始已排序区只有1个元素,就是数组的第⼀个元素。 核⼼思想是取未排序区的元素,在已排序区中找到合适的插入位置将其插入,重复这个过程,直到未排序区的元素为空,算法结束 。

// 插入排序, a表示数组,n表示数组大小
function insertionSort(a,n) {
  if(n <= 1) return
  for (let i = 1;i < n; ++i) {
    let value = a[i]
    let j = i - 1
    while(j >= 0 && a[j] > value) {
      a[j+1] = a[j] // 数据迁移 
      j--
    }
    a[j+1] = value // 插入数据
  }
  
}
复制代码

性能分析

  • 空间复杂度 O(1) 原地排序
  • 稳定性: 先后顺序不变,是稳定排序
  • 时间复杂度
    • 最好状况是O(n)
    • 最坏状况是倒叙,O(n^2)
    • 循环执行n次插入操做,平均复杂度O(N^2)
选择排序 selection sort

算法思想: 思路和插⼊排序相似,也分已排序区间未排序区间。可是选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区

// 选择排序 a表示数组 n表示数组大小
function selectionSort( a, n) {
  if	(n <= 1) return;
  for (let i = 0; i < n - 1; ++i) {
  	let minIndex = i;
    for	(let j = i + 1;j < n; ++j) {
      if (a[j] < a[minIndex]) {
        minIndex = j;
      }
    }
    [a[i], a[minIndex]] = [a[minIndex], a[i]] // 交换
  }
}
复制代码

性能分析:

  • 空间复杂度O(1)
  • 时间复杂度: 最好最坏和平均状况的时间复杂度都是O(n^2)
  • 稳定性:因为最小值会和前面的元素交换位置,故是非稳定排序
插入排序优化版: 希尔排序 shell sort

算法思想:

  • 选取一个步长gap(通常初次增量取gap=n/2),对所有元素进⾏分组,分组的依据是全部距离为gap的倍数的元素分为同一组 a[0-9], gap=10/2=5, 因此第一组a[0],a[5],第⼆组a[1],a[6],第三组a[2],a[7],第四组a[3],a[8],第五组a[4],a[9]
  • 对分好的组,在组内进行插⼊排序
  • 而后取下⼀个gap = gap/2,继续分组并组内进行插入排序
  • 重复以上步骤,直到gap=1,即全部元素在同一个组内,再进行插⼊排序,完成排序。

归并排序 merge sort

算法思想: 分治。要排序⼀个数组,先把数组从中间分红先后2部分,而后对先后部分别排序,再讲排序好的两部分结合在一块儿 。

性能分析:

  • 空间复杂度: 每次合并都要申请额外的内存空间,全部空间复杂度是O(n)
  • 稳定性: 合并国产中每次元素在和合并先后顺序不变- 稳定排序
  • 时间复杂度: O(nlogn)
快速排序 quick sort

算法思想:

待排序数组A[p..r],选择pr之间的任意⼀个数据做为pivot(分区点),遍历pr之间的数据,将小于pivot的放在左边,⼤于pi 边,pivot放中间。数组被分红3部分,前面A[p..q-1]都是⼩于pivot的,中间是pivot,后⾯面A[q+1..r]都是大于pivot的。而后再根据分治 归排序A[p..q-1]A[q+1..r],直到区间缩小为1,说明排序完成。

利用递归思想

quick_sort(p..r) = quick_sort(p..q-1) + quick_sort(q+1..r)
// 终止条件;
p >= r
// 快速排序 a是数组 n表示数组大小
quick_sort(a, n){
  quick_sort_c(a, 0, n-1)
}
// 递归 p r 为下标
quick_sort_c(a,p,r) {
  if (p >= r) return;
  q = partition(a, p, r)
  quick_sort_c(a, p ,q-1)
  quick_sort_c(a,q+1,r)
}
复制代码

partition函数实现思路

随机选择一个元素做为pivot(通常状况下,能够选择p到r区间的最后一个元素),而后对A[p...r]分区,函数返回pivot下标。若是不考虑内容消耗,能够临时申请2个数组x和y来帮助完成partition功能,只须要遍历A[p...r],把小于pivot的元素拷贝到数组x,把大于pivot的元素拷贝到s数组y,最后再把x中元素,pivot元素,y中元素按顺序拷贝到A[p...r]便可。注意这时排序就再也不是原地排序了。

为了了让partition函数不占用额外的内存空间,咱们须要在A[p..r]的原地完成分区操做,具体方法以下

处理相似选择排序,假设pivot=A[r],经过游标iA[p..r-1]分红2部分,A[p..i-1]中的元素都⼩于pivot,叫它"已处理理区间",把A[i...r-1]为"未处理理区间"。每次从未处理理区间A[i..r-1]中取⼀个元素A[j],与pivot对比,若是⼩于pivot,则将其加⼊到已处理区间的末尾,也就是A[i] 数组中插⼊数据致使的数据搬移,选择将A[i]A[j]交换,这样就能够保证O(1)时间复杂度下内将A[j]放到下标为i的位置。

性能分析:

  • 空间复杂度O(1) 原地排序

  • 稳定性: 非稳定排序

  • 时间复杂度:O(nlongn)

相关文章
相关标签/搜索