排序算法
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;
}
- 階乘
- 二分查找