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)

相關文章
相關標籤/搜索