JavaScript基礎複習(九) 常見JS算法

排序算法

Algorithm Average Best Worst extra space stable
冒泡排序 O(N^2) O(N) O(N^2) O(1) 穩定
直接插入排序 O(N^2) O(N) O(N^2) O(1) 穩定
折半插入排序 O(NlogN) O(NlogN) O(N^2) O(1) 穩定
簡單選擇排序 O(N^2) O(N^2) O(N^2) O(1) 不穩定
快速排序 O(NlogN) O(NlogN) O(N^2) O(logN)~O(N^2) 不穩定
歸併排序 O(NlogN) O(NlogN) O(NlogN) O(N) 穩定
堆排序 O(NlogN) O(NlogN) O(NlogN) O(1) 不穩定
計數排序 O(d*(N+K)) O(d*(N+K)) O(d*(N+K)) O(N+K) 穩定
  • 冒泡:

依次比較相鄰的兩個數,如果不符合排序規則,則調換兩個數的位置。這樣一遍比較下來,能夠保證最大(或最小)的數排在最後一位。再對最後一位以外的數組,重複前面的過程,直至全部排序完成。

function bubbleSort(arr){
	var len = arr.length;
	for(var i = 0;i<len;i++){
		for(var j = 0;j<len-1-i;j++){
			if(arr[j]>arr[j+1]){  //相鄰元素兩兩比較
				var temp = arr[j+1];  //元素交換
				arr[j+1] = arr[j];
				arr[j]=temp;
			}
		}
	}
	return arr;
}

改進:設置一標誌性變量pos,用於記錄每趟排序中最後一次進行交換的位置。由於pos位置之後的記錄均已交換到位,故在進行下一趟排序時只要掃描到pos位置即可。

function bubbleSort2(arr){
	var i=arr.length-1;  //初始時,最後位置保持不變
	while(i>0){
		var pos=0;//每趟開始時,無記錄交換
		for(var j=0;j<i;j++){
			if(arr[j]>arr[j+1]){
				pos=j;//記錄交換的位置
				var tmp=arr[j];
				arr[j]=arr[j+1];
				arr[j+1]=tmp;
			}
		}
		i=pos;//爲下一趟排序作準備
	}
	return arr;
}

  • 選擇:

選擇排序(Selection Sort)與冒泡排序類似,也是依次對相鄰的數進行兩兩比較。不同之處在於,它不是每比較一次就調換位置,而是一輪比較完畢,找到最大值(或最小值)之後,將其放在正確的位置,其他數的位置不變

function selectionSort(arr){
	var len=arr.length;
	var minIndex,temp;
	for(var i=0;i<len-1;i++){
		minIndex=i;
		for(var j=i+1;j<len;j++){
			if(arr[j]<arr[minIndex]){       //尋找最小的數
				minIndex=j;                //將最小數的索引保存
			}
		}
		temp=arr[i];
		arr[i]=arr[minIndex];
		arr[minIndex]=temp;
	}
	return arr;
}

  • 插入排序:

它將數組分成“已排序”和“未排序”兩部分,一開始的時候,“已排序”的部分只有一個元素,然後將它後面一個元素從“未排序”部分插入“已排序”部分,從而“已排序”部分增加一個元素,“未排序”部分減少一個元素。以此類推,完成全部排序。

function insertionSort(array) {
	for (var i = 1; i < array.length; i++) {
		var key = array[i];
		var j = i - 1;
		while (j >= 0 && array[j] > key) {
			array[j + 1] = array[j];
			j--;
		}
		array[j + 1] = key;
	}
	return array;
}

  • 希爾排序:

先取一個小於n的整數d1作爲第一個增量,把文件的全部記錄分組。所有距離爲d1的倍數的記錄放在同一個組中。先在各組內進行直接插入排序;然後,取第二個增量d2<d1重複上述的分組和排序,直至所取的增量=1(<…<d2<d1),即所有記錄放在同一組中進行直接插入排序爲止

function shellSort(arr) {
	var len = arr.length,
		temp,
		gap = 1;
	while(gap < len/5) {          //動態定義間隔序列
		gap =gap*5+1;
	}
	for (gap; gap > 0; gap = Math.floor(gap/5)) {
		for (var i = gap; i < len; i++) {
			temp = arr[i];
			for (var j = i-gap; j >= 0 && arr[j] > temp; j-=gap) {
				arr[j+gap] = arr[j];
			}
			arr[j+gap] = temp;
		}
	}
	return arr;
}

  • 快速排序:

先確定一個“支點”(pivot),將所有小於“支點”的值都放在該點的左側,大於“支點”的值都放在該點的右側,然後對左右兩側不斷重複這個過程,直到所有排序完成

function qSort(arr){
	if(arr.length==0){
		return [];
	}
	var left=[];
	var right=[];
	var pivot=arr[0];
	for(var i=1;i<arr.length;i++){
		if(arr[i]<pivot){
			left.push(arr[i]);
		}else{
			right.push(arr[i]);
		}
	}
	return qSort(left).concat(pivot,qSort(right));
} 

  • 堆排序

將待排序的序列構造成一個最大堆,此時序列的最大值爲根節點;依次將根節點與待排序序列的最後一個元素交換,再維護從根節點到該元素的前一個節點爲最大堆,如此往復,最終得到一個遞增序列

function heapSort(array){
        //建堆
        var heapSize=array.length,temp;
        for(var i=Math.floor(heapSize/2)-1;i>=0;i--){
            heapify(array,i,heapSize);
        }
        //堆排序
        for(var j=heapSize-1;j>=1;j--){
            temp=array[0];
            array[0]=array[j];
            array[j]=temp;
            heapify(array,0,--heapSize);
        }
        return array;
}
function heapify(arr,x,len){
        var l=2*x+1,r=2*x+2,largest=x,temp;
        if(l<len&&arr[l]>arr[largest]){
            largest=l;
        }
        if(r<len&&arr[r]>arr[largest]){
            largest=r;
        }
        if(largest!=x){
            temp=arr[x];
            arr[x]=arr[largest];
            arr[largest]=temp;
            heapify(arr,largest,len);
        }
}


  • 合併排序:

將兩個已經排序的數組合並,要比從頭開始排序所有元素來得快。因此,可以將數組拆開,分成n個只有一個元素的數組,然後不斷地兩兩合併,直到全部排序完成

function mergeSort(arr){  //採用自上而下的遞歸方法
    var len=arr.length;
    if(len<2){
        return arr;
    }
    var middle=Math.floor(len/2),
        left=arr.slice(0,middle),
        right=arr.slice(middle);
    return merge(mergeSort(left),mergeSort(right));
}
function merge(left,right){
    var result=[];
    while(left.length&&right.length){
        if(left[0]<=right[0]){
            result.push(left.shift());
        }else{
            result.push(right.shift());
        }
    }
    while(left.length)
        result.push(left.shift());
    while(right.length)
        result.push(right.shift());
    return result;
}

數組操作

  • 數組去重
// 1、ES6新增方法
Array.from(new Set(array)); 
// 2、indexOf
function unique(arr){
    var data = [];
    for(var i = 0; i<arr.length;ix==) {
        if(data.indexOf(arr[i]) == -1) {
            data.push(arr[i])
        } 
    }
    return data;
}
// 3、對象
function unique(arr) {
    var table = {};
    var data = [];
    for(var i=0;i<arr.length;i++) {
        if(!table[arr[i]]) {
            table[arr[i]] = true;
            data.push(arr[i]);
        }
    }
    return data;
}
// 判斷某個值是否只出現一次
arr.indexOf(arr[i]) == arr.lastIndexOf(arr[i])
  • 數組中最大差值

最小與最大數的差值

// 耗能大
function MaxMin(arr) {
    arr.sort(function(n1, n2) {return n1-n2});
    return arr[arr.length-1] - arr[0];
}
// 
function getMaxProfit(arr) {
   var min = arr[0];
   var max = 0;
   for(var i=0; i<arr.length;i++){
       var current = arr[i];
       min = Math.min(min, current);
       var p = current - min;
       max = Math.max(max, p);
   }
   return max;
}

字符串操作

  • 判斷迴文
// 利用反轉
if(str == str.split('').reverse().join('')){
    console.log('是迴文')
}
// 利用字符下標
for(var i=0;i<str.length;i++){
    if(str.charAt(i)==str.charAt(len-1-i)){
        console.log('是迴文')
    }
}
// for循環反轉拼接,再比較  

  • 反轉
str.split('').reverse().join('')
  • 隨機生成
function randomString(n){
    var str = 'abcdefghijklmnopqrstuvwxyz0123456789';
    var tmp = '';
    for(var i=0;i<n;i++)
        tmp += str.charAt(Math.round(Math.random()*str.length));
    return tmp;
}
  • 統計字符串中次數最多的字母


深拷貝,淺拷貝

是什麼

js中有基礎類型和引用類型。
基礎類型是存儲在棧內存中的,按值存儲,按值訪問。基本類型有Number,String,Boolean,Null,Undefined,Symbol
引用類型是存儲在堆內存中的,值是可變的。在棧中保存對應的指針(一個指向堆的引用地址),指向堆中實際的值。比如數組,對象,正則等,除了基本數據類型,都是引用類型了。

基本類型的複製,是不會相互影響的。因爲直接改變的就是棧中的值。而引用類型的複製,在修改其中一個的時候,另一個也會跟着發生變化。是因爲複製的是棧中的指針,當改變值時,指針會仍然指向堆中實際的值,所以也就會跟着變化。

舉一些例子吧:
// 基礎類型
var a = 2;
b = a;
console.log(b)  //2
b = 3;
console.log(a, b) //2,3

// 引用類型
var arr = [2,4,6];
var bcc = arr;//傳址 ,對象中傳給變量的數據是引用類型的,會存儲在堆中;
var cxx = arr[0];//傳值,把對象中的屬性/數組中的數組項賦值給變量,這時變量cxx是基本數據類型,存儲在棧內存中;改變棧中的數據不會影響堆中的數據
console.log(bcc);//2,4,6
console.log(cxx);//2
//改變數值 
bcc[1] = 6;
cxx = 7;
console.log(arr[1]);//6
console.log(arr[0]);//2

從上面我們可以得知,當我改變bcc中的數據時,arr中數據也發生了變化;但是當我改變cxx的數據值時,arr卻沒有發生改變。

這就是傳值與傳址的區別。因爲arr是數組,屬於引用類型,所以它賦予給bcc的時候傳的是棧中的地址(相當於新建了一個不同名“指針”),而不是堆內存中的對象。而cxx僅僅是從arr堆內存中獲取的一個數據值,並保存在棧中。所以bcc修改的時候,會根據地址回到arr堆中修改,cxx則直接在棧中修改,並且不能指向arr堆內存中。

淺拷貝

簡單來說,引用類型的直接複製,在修改其中一個的時候,另一個就會跟着變化。這種直接複製的方式 就是淺拷貝。淺拷貝是拷貝一層,深層次的對象級別的就拷貝引用

還有一種淺拷貝的方式:

ES6中的Object.assign方法,Object.assign是ES6的新函數。Object.assign() 方法可以把任意多個的源對象自身的可枚舉屬性拷貝給目標對象,然後返回目標對象。但是 Object.assign() 進行的是淺拷貝,拷貝的是對象的屬性的引用,而不是對象本身。

爲了解決這個問題,引入深拷貝的方式,其實就是拷貝多層,每一級別的數據都會拷貝出來;

深拷貝的實現方式
  • 手動複製

    把一個對象的屬性複製給另一個對象的屬性

  • 對象只有一層的話可以使用上面的:Object.assign()函數

  • JSON的方式,先轉爲字符串,再轉爲對象

    JSON.parse(JSON.stringify(obj))

    缺點:會拋棄對象的constructor。也就是深拷貝之後,不管這個對象原來的構造函數是什麼,在深拷貝之後都會變成Object。而且只有可以轉爲json格式對象的才能這樣使用。

  • 遞歸拷貝

function deepClone(initalObj, finalObj) {    
  var obj = finalObj || {};    
  for (var i in initalObj) {        
    var prop = initalObj[i];        // 避免相互引用對象導致死循環,如initalObj.a = initalObj的情況
    if(prop === obj) {            
      continue;
    }        
    if (typeof prop === 'object') {
      obj[i] = (prop.constructor === Array) ? [] : {};            
      arguments.callee(prop, obj[i]);
    } else {
      obj[i] = prop;
    }
  }    
  return obj;
}
var str = {};
var obj = { a: {a: "hello", b: 21} };
deepClone(obj, str);
console.log(str.a);
  • Object.create()方法
function deepClone(initalObj, finalObj) {    
  var obj = finalObj || {};    
  for (var i in initalObj) {        
    var prop = initalObj[i];        // 避免相互引用對象導致死循環,如initalObj.a = initalObj的情況
    if(prop === obj) {            
      continue;
    }        
    if (typeof prop === 'object') {
      obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);
    } else {
      obj[i] = prop;
    }
  }    
  return obj;
}

其他常見

  • 十六進制顏色值的隨機生成
function randomColor(){
 var arrHex=["0","2","3","4","5","6","7","8","9","a","b","c","d"],
     strHex="#",
     index;
     for(var i=0;i < 6; i++){
      index=Math.round(Math.random()*15);
      strHex+=arrHex[index];
     }
 return strHex;
}
  • 階乘
  • 二分查找

JavaScript 面試中常見算法問題詳解

一些常見算法的JavaScript實現

常見的js算法面試題收集,es6實現

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章