JavaScript數據結構之 —— 10排序算法

排序算法

開始排序算法之前,我們先創建一個數組來表示待排序和搜索的數據結構:

function ArrayList(){
	var array = []; //存儲
	this.insert = function(item){ //添加元素
		array.push(item);
	};
	this.toString= function(){ //方便查看數組輸出結果
		return array.join();
	};
}

網上找的一張圖:
在這裏插入圖片描述

冒泡排序

人們開始學習排序算法時,通常都先學冒泡算法,因爲它在所有排序算法中最簡單。然而,從運行時間的角度來看,冒泡排序是最差的一個,接下來你會知曉原因。

冒泡排序比較任何兩個相鄰的項,如果第一個比第二個大,則交換它們。元素項向上移動至正確的順序,就好像氣泡升至表面一樣,冒泡排序因此得名。
實現冒泡排序:

this.bubbleSort = function(){
	var length = array.length; // 存儲數組長度
	for (var i=0; i<length; i++){ // 外循環:每一項進行一輪排序
		for (var j=0; j<length-1; j++ ){ //從第一項到倒數第二項
			if (array[j] > array[j+1]){ //相鄰項比較,前項大就交換位置
				swap(j, j+1); 
			}
		}
	}
};

// 聲明一個交換函數
var swap = function(index1, index2){
	var aux = array[index1]; // 用一箇中間值來存儲某一交換項的值
	array[index1] = array[index2];
	array[index2] = aux;
};

改進後的冒泡排序:

this.modifiedBubbleSort = function(){
	var length = array.length;
	for (var i=0; i<length; i++){
		for (var j=0; j<length-1-i; j++ ){ 
		// 因爲每一輪排序,都會將一個最大的放到最後
		// 如果從內循環減去外循環中已跑過的輪數
		// 就可以避免內循環中所有不必要的比較
			if (array[j] > array[j+1]){
				swap(j, j+1);
			}
		}
	}
};

改進後的排序效果如下圖,注意已經在正確位置上的數字沒有被比較。即便我們做了這個小改變,改進了一下冒泡排序算法,但我們還是不推薦該算法,它的複雜度是O(n2)。:
在這裏插入圖片描述
在這裏插入圖片描述

選擇排序

選擇排序大致的思路是找到數據結構中的最小值並將其放置在第一位,接着找到第二小的值並將其放在第二位,以此類推。

this.selectionSort = function(){
	var length = array.length, 
	indexMin;
	for (var i=0; i<length-1; i++){ // 外層循環比較次數
		indexMin = i; //存儲本輪最小值
		for (var j=i; j<length; j++){ // 獲取剩下元素的最小值
			if(array[indexMin]>array[j]){ 
			// 比較並保證 indexMin 爲最小值的下標
				indexMin = j; 
			}
		}	
		if (i !== indexMin){ // 本輪第一個,存儲爲本輪最小值
			swap(i, indexMin);
		}
	}
};

選擇排序看起來比冒泡簡單些,不過同樣也是一個複雜度爲O(n2)的算法。和冒泡排序一樣,它包含有嵌套的兩個循環,這導致了二次方的複雜度。

在這裏插入圖片描述
在這裏插入圖片描述

插入排序

插入排序每次排一個數組項,以此方式構建最後的排序數組。假定第一項已經排序了,接着,它和第二項進行比較,第二項是應該待在原位還是插到第一項之前呢?這樣,頭兩項就已正確排序,接着和第三項比較(它是該插入到第一、第二還是第三的位置呢?),以此類推。

this.insertionSort = function(){
	var length = array.length, 
	j, temp;
	for (var i=1; i<length; i++){ // 外層循環比較第 i 項
		j = i; 
		temp = array[i]; // 存儲臨時變量
		while (j>0 && array[j-1] > temp){ 
		// 前一位如果大於當前位,就把前一位的值放到當前位置
			array[j] = array[j-1]; 
			j--;
		}
		// 最後把臨時變量放進比較結束後最前面的位置
		array[j] = temp; 
	}
};

看個圖以便理解:
這些值將被插入排序算法按照下面形容
的步驟進行排序。
(1) 3已被排序,所以我們從數組第二個值5開始。3比5小,所以5待在原位(數組的第二位)。3和5排序完畢。
(2) 下一個待排序和插到正確位置上去的值是1(目前在數組的第三位)。5比1大,所以5被移至第三位去了。我們得分析1是否應該被插入到第二位——1比3大嗎?不,所以3被移到第二位去了。接着,我們得證明1應該插入到數組的第一位上。因爲0是第一個位置且沒有負數位,所以1必須被插入到第一位。1、3、5三個數字已經排序。。。以此類推。

排序小型數組時,此算法比選擇排序和冒泡排序性能要好。
在這裏插入圖片描述

在這裏插入圖片描述

歸併排序

歸併排序是第一個可以被實際使用的排序算法。前三個排序算法性能不好,但歸併排序性能不錯,其複雜度爲O(nlogn)。

其思想是將原始數組切分成較小的數組,直到每個小數組只有一個位置,接着將小數組歸併成較大的數組,直到最後只有一個排序完畢的大數組。

this.mergeSort = function(){
	array = mergeSortRec(array);
};

var mergeSortRec = function(array){
	var length = array.length;
	if(length === 1) { 
	// 由於算法是遞歸的,我們需要一個停止條件
	// 在這裏此條件是判斷數組的長度是否爲1
		return array; 
	}
	// 如果數組長度比1大,那麼我們得將其分成小數組。
	// 爲此,首先得找到數組的中間位,
	// 找到後我們將數組分成兩個小數組,分別叫作left和right
	var mid = Math.floor(length / 2), 
	left = array.slice(0, mid), 
	right = array.slice(mid, length); 
	return merge(mergeSortRec(left), mergeSortRec(right)); 
};

下面的步驟是調用merge函數負責合併和排序小數組來產生大數組,直到回到原始數組並已排序完成。爲了不斷將原始數組分成小數組,我們得再次對left數組和right數組
遞歸調用mergeSortRec,並同時作爲參數傳遞給merge函數。

var merge = function(left, right){
	var result = [], // 存儲結果的數組
	il = 0,
	ir = 0;
	while(il < left.length && ir < right.length) { 
	// 左右兩個數組都有值存在,就挨個比較左右兩個數組的元素
	// 取出最小值,push 到本輪數組中
	// 直到有一個數組的所有元素都被取出
		if(left[il] < right[ir]) {
			result.push(left[il++]); 
		} else{
			result.push(right[ir++]); 
		}
	}
	// 如果左側數組剩餘元素,就取出它剩下的元素,放到新數組中
	while (il < left.length){ 
		result.push(left[il++]);
	}
	while (ir < right.length){ 
		result.push(right[ir++]);
	}
	return result; // 返回排序後的新數組
};

merge函數接受兩個數組作爲參數,並將它們歸併至一個大數組。排序發生在歸併過程中。
首先,需要聲明歸併過程要創建的新數組以及用來迭代兩個數組(left和right數組)所需的兩個變量。迭代兩個數組的過程中,我們比較來自left數組的項是否比來自right
數組的項小。如果是,將該項從left數組添加至歸併結果數組,並遞增迭代數組的控制變量;否則,從right數組添加項並遞增相應的迭代數組的控制變量。接下來,將left
數組或者right數組所有剩餘的項添加到歸併數組中。最後,將歸併數組作爲結果返回。
在這裏插入圖片描述
算法首先將原始數組分割直至只有一個元素的子數組,然後開始歸併。歸併過程也會完成排序,直至原始數組完全合併並完成排序。
在這裏插入圖片描述

快速排序

這個算法首先要在列表中選擇一個元素作爲基準值(pivot)。數據排序圍繞基準值進行,
將列表中小於基準值的元素移到數組的前面,將大於基準值的元素移到數組的後面。
實現過程如下:
(1) 選擇一個基準元素,將列表分隔成兩個子序列;
(2) 對列表重新排序,將所有小於基準值的元素放在基準值的前面,所有大於基準值的元
素放在基準值的後面;
(3) 分別對較小元素的子序列和較大元素的子序列重複步驟 1 和 2。

function qSort(list) {
	if (list.length == 0 || list.length == 1) {
	// 如果數組長度爲零或一,就不需要排序,直接返回
		return list;
	}
	// 創建左右數組用來存數據
	var left = [],right = [];
	// 設置基準值
	var pivot = list[0]; 
	for (var i = 1; i < list.length; i++) {
		if (list[i] < pivot) {
			left.push(list[i]);
		} else {
			right.push(list[i]);
		}
	}
	// 左右兩個數組分別調用這個函數
	// 然後把所有的值組合成一個數組
	// 排序完成
	return qSort(left).concat(pivot, qSort(right));
}

測試排序效果:

for (var i = 0; i < 10; ++i) {
	a[i] = Math.floor((Math.random()*100)+1);
}
console.log('排序前:',a);
console.log('排序後:',qSort(a));

在這裏插入圖片描述
在這裏插入圖片描述

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