搜尋
首頁web前端js教程JS數組sort方法如何使用

JS數組sort方法如何使用

Jun 04, 2018 am 09:39 AM
javascriptsort使用

這次帶給大家JS數組sort方法如何使用,JS數組sort方法使用的注意事項有哪些,下面就是實戰案例,一起來看一下。

演算法課上,我們會接觸到很多種排序演算法,什麼冒泡排序選擇排序、快速排序、堆排序等等。那麼javascript的sort方法採用哪一種排序演算法呢?要搞清楚這個問題,呃,直接看v8原始碼好了。 v8中對Array.sort的實作是採用javascript完成的,粗看下來,使用了快速排序演算法,但明顯比我們熟悉的快速排序複雜。那麼到底複雜在什麼地方?為什麼要搞這麼複雜?這是我們今天要探討的問題。

快速排序演算法

快速排序演算法之所以稱為快速排序演算法,是因為它能達到最佳和平均時間複雜度均為O(nlogn ),是一種應用非常廣泛的排序演算法。它的原理並不複雜,先找出一個基準元素(pivot,任意元素均可),然後讓所有元素跟基準元素比較,比基準元素小的,放到一個集合中,其他的放到另一個集合中;再對這兩個集合執行快速排序,最終得到完全排序好的序列。

所以快速排序的核心是不斷把原數組做切割,切成小數組後再對小數組進行相同的處理,這是一種典型的分治的演算法設計思路。實作一個簡單的快速排序演算法並不困難。我們不妨試試看:

function QuickSort(arr, func) {
	if (!arr || !arr.length) return [];
	if (arr.length === 1) return arr;
	var pivot = arr[0];
	var smallSet = [];
	var bigSet = [];
	for (var i = 1; i <p style="text-align: left;">這是一個非常基礎的實現,選取數組的第一項作為基準元素。 </p><p style="text-align: left;"><strong>原地(in-place)排序</strong></p><p style="text-align: left;">我們可以注意到,上面的演算法中,我們其實是創建了一個新的陣列作為計算結果,從空間使用的角度看是不經濟的。 javascript的快速排序演算法中並沒有像上面的程式碼那樣創建一個新的數組,而是在原始數組的基礎上,透過交換元素位置實現排序。所以,類似push、pop、splice這幾個方法,sort方法也是會修改原數組<a href="http://www.php.cn/wiki/60.html" target="_blank">物件</a>的! </p><p style="text-align: left;">我們前面說過,快速排序的核心在於切割陣列。那如果只是在原數組上交換元素,怎麼做到切割數組呢?很簡單,我們並不需要真的把陣列切割出來,只需要記住每個部分起止的索引號碼。舉個例子,假設有一個陣列[12, 4, 9, 2, 18, 25],選取第一項12為基準元素,那麼依照原始的快速排序演算法,會把這個陣列切割成兩個小陣列: [4, 9, 2], 12, [18, 25]。但是我們同樣可以不切割,先透過比較、交換元素,將原數組修改成[4, 9, 2, 12, 18, 25],再根據基準元素12的位置,認為0~2號元素是一組,4~5號元素是一組,為了表達方便,我在這裡將比基準元素小的元素組成的分區叫小數分區,另一個分區叫大數分區。這很像電腦硬碟的分區,並不是真的把硬碟分成了C盤、D盤,而是記錄下一些起止位置,在邏輯上分成了若干個分區。類似的,在快速排序演算法中,我們也把這個過程叫做分區(partition)。所以相應的,我也要修改一下之前的說法了,快速排序演算法的核心是分區。 </p><p style="text-align: left;">說了這麼多,還是實作一個分割區的快速排序吧:</p><pre class="brush:php;toolbar:false">function swap(arr, from, to) {
	if (from == to) return;
	var temp = arr[from];
	arr[from] = arr[to];
	arr[to] = temp;
}
 
function QuickSortWithPartition(arr, func, from, to) {
	if (!arr || !arr.length) return [];
	if (arr.length === 1) return arr;
	from = from || 0;
	to = to || arr.length - 1;
	var pivot = arr[from];
	var smallIndex = from;
	var bigIndex = from + 1;
	for (; bigIndex <p style="text-align: left;">看起來程式碼長了很多,不過不算複雜。首先由於涉及陣列元素交換,所以先實作一個swap方法來處理元素交換。在快速排序演算法中,增加了兩個參數,from和to,分別表示目前要處理這個陣列的哪個部分,from是起始索引,to是終止索引;如果這兩個參數缺失,則表示處理整個陣列。 </p><p style="text-align: left;">同样的,我用最简单的方式选取基准元素,即所要处理分区的第一个元素。然后我定义了smallIndex和bigIndex两个变量,分别表示的是左侧小数分区的终止索引和右侧大数分区的终止索引。什么意思?就是说从第一个元素(基准元素)到第smallIndex个元素间的所有元素都比基准元素小,从第smallIndex + 1到第bigIndex个元素都比基准元素大。一开始没有比较时,很显然这两部分分区都是空的,而比较的过程很简单,直接是bigIndex向右移,一直移到分区尾部。每当bigIndex增加1,我们会进行一次判断,看看这个位置上的元素是不是比基准元素大,如果大的话,不用做处理,它已经处于大数分区了;但如果比基准元素小,就需要进行一次交换。怎么交换呢?首先将smallIndex增加1,意味着小数分区增加了一个元素,但此时smallIndex位置的元素很明显是一个大数(这个说法其实不对,如果之前大数分区里面没有元素,此时smallIndex和bigIndex相等,但对交换没有影响),而在bigIndex位置的元素是一个小数,所以只要把这两个位置的元素交换一下就好了。</p><p style="text-align: left;">最后可别忘了一开始的起始元素,它的位置并不正确,不过只要将它和smallIndex位置的元素交换位置就可以了。同时我们得到了对应的小数分区[from...smallIndex - 1]和大数分区[smallIndex + 1...to]。再对这两个分区递归排序即可。</p><p style="text-align: left;"><strong>分区过程的优化</strong></p><p style="text-align: left;">上面的分区过程(仅仅)还是有一定的优化空间的,因为上面的分区过程中,大数分区和小数分区都是从左向右增长,其实我们可以考虑从两侧向中间遍历,这样能有效地减少交换元素的次数。举个例子,例如我们有一个数组[2, 1, 3, 1, 3, 1, 3],采用上面的分区算法,一共碰到三次比基准元素小的情况,所以会发生三次交换;而如果我们换个思路,把从右往左找到小于基准和元素,和从左往右找到大于基准的元素交换,这个数组只需要交换一次就可以了,即把第一个3和最后一个1交换。</p><p style="text-align: left;">我们也来尝试写一下实现:</p><pre class="brush:php;toolbar:false">function QuickSortWithPartitionOp(arr, func, from, to) {
	if (!arr || !arr.length) return [];
	from = from || 0;
	to = to || arr.length - 1;
	if (from >= to - 1) return arr;
	var pivot = arr[from];
	var smallEnd = from + 1;
	var bigBegin = to;
	while (smallEnd  0 && smallEnd <p style="text-align: left;"><strong>分区与性能</strong></p><p style="text-align: left;">前面我们说过,快速排序算法平均时间复杂度是O(nlogn),但它的最差情况下时间复杂度会衰弱到O(n2)。而性能好坏的关键就在于分区是否合理。如果每次都能平均分成相等的两个分区,那么只需要logn层迭代;而如果每次分区都不合理,总有一个分区是空的,那么需要n层迭代,这是性能最差的场景。</p><p style="text-align: left;">那么性能最差的场景会出现吗?对于一个内容随机的数组而言,不太可能出现最差情况。但我们平时在编程时,处理的数组往往并不是内容随机的,而是很可能预先有一定顺序。设想一下,如果一个数组已经排好序了,由于之前的算法中,我们都是采用第一个元素作为基准元素,那么必然会出现每次分区都会有一个分区为空。这种情况当然需要避免。</p><p style="text-align: left;">一种很容易的解决方法是不要选取固定位置的元素作为基准元素,而是随机从数组里挑出一个元素作为基准元素。这个方法很有效,极大概率地避免了最差情况。这种处理思想很简单,我就不另外写代码了。</p><p style="text-align: left;">然而极大概率地避免最差情况并不等于避免最差情况,特别是对于数组很大的时候,更要求我们在选取基准元素的时候要更谨慎些。</p><p style="text-align: left;"><strong>三数取中(median-of-three)</strong></p><p style="text-align: left;">基准元素应当精心挑选,而挑选基准元素的一种方法为三数取中,即挑选基准元素时,先把第一个元素、最后一个元素和中间一个元素挑出来,这三个元素中大小在中间的那个元素就被认为是基准元素。</p><p style="text-align: left;">简单实现一下获取基准元素的方法:</p><pre class="brush:php;toolbar:false">function getPivot(arr, func, from, to) {
	var middle = (from + to) >> 1;
	var i0 = arr[from];
	var i1 = arr[to];
	var i2 = arr[middle];
	var temp;
	if (func(i0, i1) > 0) {
		temp = i0;
		i0 = i1;
		i1 = temp;
	}
	if (func(i0, i2) > 0) {
		arr[middle] = i0;
		arr[from] = i2;
		arr[to] = i1;
		return i0;
	} else {
		arr[from] = i0;
		if (func(i1, i2) > 0) {
			arr[middle] = i1;
			arr[to] = i2;
			return i1;
		} else {
			arr[middle] = i2;
			arr[to] = i1;
			return i2;
		}
	}
}

这个例子里我完全没管基准元素的位置,一是降低复杂度,另一个原因是下面讨论重复元素处理时,基准元素的位置没什么意义。不过我把最小的值赋给了第一个元素,最大的值赋给了第二个元素,后面处理重复元素时会有帮助。

当然,仅仅是三数取中获得的基准元素,也不见得是可靠的。于是有一些其他的取中值的方法出现。有几种比较典型的手段,一种是平均间隔取一个元素,多个元素取中位数(即多取几个,增加可靠性);一种是对三数取中进行递归运算,先把大数组平均分成三块,对每一块进行三数取中,会得到三个中值,再对这三个中值取中位数。

不过查阅v8的源代码,发现v8的基准元素选取更为复杂。如果数组长度不超过1000,则进行基本的三数取中;如果数组长度超过1000,那么v8的处理是除去首尾的元素,对剩下的元素每隔200左右(200~215,并不固定)挑出一个元素。对这些元素排序,找出中间的那个,并用这个元素跟原数组首尾两个元素一起进行三数取中。这段代码我就不写了。

针对重复元素的处理

到目前为止,我们在处理元素比较的时候比较随意,并没有太多地考虑元素相等的问题。但实际上我们做了这么多性能优化,对于重复元素引起的性能问题并没有涉及到。重复元素会带来什么问题呢?设想一下,一个数组里如果所有元素都相等,基准元素不管怎么选都是一样的。那么在分区的时候,必然出现除基准元素外的其他元素都被分到一起去了,进入最差性能的case。

那么对于重复元素应该怎么处理呢?从性能的角度,如果发现一个元素与基准元素相同,那么它应该被记录下来,避免后续再进行不必要的比较。所以还是得改分区的代码。

function QuickSortWithPartitionDump(arr, func, from, to) {
	if (!arr || !arr.length) return [];
	from = from || 0;
	to = to || arr.length - 1;
	if (from >= to - 1) return arr;
	var pivot = getPivot(arr, func, from, to);
	var smallEnd = from;
	var bigBegin = to;
	for (var i = smallEnd + 1; i  0) {
			while (bigBegin > i && order > 0) {
				bigBegin--;
				order = func(arr[bigBegin], pivot);
			}
			if (bigBegin == i) break;
			swap(arr, i, bigBegin);
			if (order <p style="text-align: left;">简单解释一下这段代码,上文已经说过,在getPivot方法中,我将比基准小的元素放到第一位,把比基准大的元素放到最后一位。定义三个变量smallEnd、bigBegin、i,从from到smallEnd之间的元素都比基准元素小,从smallEnd到i之间的元素都和基准元素一样大,从i到bigBegin之间的元素都是还没有比较的,从bigBegin到to之间的元素都比基准元素大。了解这个关系就好理解这段代码了。遍历从smallEnd + 1到bigBegin之间的元素: <br>* 如果这个元素小于基准,那么smallEnd增加1,这时smallEnd位置的元素是等于基准元素的(或者此时smallEnd与i相等),交换smallEnd与i处的元素就可以了。 <br>* 如果这个元素大于基准,相对比较复杂一点。此时让bigBegin减小1,检查大数分区前面一个元素是不是大于基准,如果大于基准,重复此步骤,不断让bigBegin减小1,直到找到不比基准大的元素(如果这个过程中,发现bigBegin与i相等,则中止遍历,说明分区结束)。找到这个不比基准大小元素时需要区分是不是比基准小。如果比基准小,需要做两步交换,先将i位置的大数和bigBegin位置的小数交换,这时跟第一种case同时,smallEnd增加1,并且将i位置的小数和smallEnd位置的元素交换。如果和基准相等,则只需要将i位置的大数和bigBegin位置的小数交换。 <br>* 如果这个元素与基准相等,什么也不用做。</p><p style="text-align: left;"><strong>小数组优化</strong></p><p style="text-align: left;">对于小数组(小于16项或10项。v8认为10项以下的是小数组。),可能使用快速排序的速度还不如平均复杂度更高的选择排序。所以对于小数组,可以使用选择排序法要提高性能,减少递归深度。</p><pre class="brush:php;toolbar:false">function insertionSort(a, func, from, to) {
	for (var i = from + 1; i = from; j--) {
			var tmp = a[j];
			if (func(tmp, element) > 0) {
				a[j + 1] = tmp;
			} else {
				break;
			}
		}
		a[j + 1] = element;
	}
}

v8引擎没有做的优化

由于快速排序的不稳定性(少数情况下性能差,前文已经详细描述过),David Musser于1997设计了内省排序法(Introsort)。这个算法在快速排序的基础上,监控递归的深度。一旦长度为n的数组经过了logn层递归(快速排序算法最佳情况下的递归层数)还没有结束的话,就认为这次快速排序的效率可能不理想,转而将剩余部分换用其他排序算法,通常使用堆排序算法(Heapsort,最差时间复杂度和最优时间复杂度均为nlogn)。

v8引擎额外做的优化

快速排序递归很深,如果递归太深的话,很可以出现“爆栈”,我们应该尽可能避免这种情况。上面提到的对小数组采用选择排序算法,以及采用内省排序算法都可以减少递归深度。不过v8引擎中,做了一些不太常见的优化,每次我们分区后,v8引擎会选择元素少的分区进行递归,而将元素多的分区直接通过循环处理,无疑这样的处理大大减小了递归深度。我大致把v8这种处理的过程写一下:

function quickSort(arr, from, to){
  while(true){
    // 排序分区过程省略
    // ...
    
    if (to - bigBegin <p style="text-align: left;">不得不说是一个很巧妙的实现。</p><p>相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!</p><p>推荐阅读:</p><p><a href="http://www.php.cn/js-tutorial-400420.html" target="_blank">如何操作Vue去除路径中的#号</a><br></p><p><a href="http://www.php.cn/js-tutorial-400417.html" target="_blank">如何使用vue中实现点击空白处隐藏div实现</a><br></p>

以上是JS數組sort方法如何使用的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
了解JavaScript引擎:實施詳細信息了解JavaScript引擎:實施詳細信息Apr 17, 2025 am 12:05 AM

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。

Python vs. JavaScript:學習曲線和易用性Python vs. JavaScript:學習曲線和易用性Apr 16, 2025 am 12:12 AM

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Python vs. JavaScript:社區,圖書館和資源Python vs. JavaScript:社區,圖書館和資源Apr 15, 2025 am 12:16 AM

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。

從C/C到JavaScript:所有工作方式從C/C到JavaScript:所有工作方式Apr 14, 2025 am 12:05 AM

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

JavaScript引擎:比較實施JavaScript引擎:比較實施Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

超越瀏覽器:現實世界中的JavaScript超越瀏覽器:現實世界中的JavaScriptApr 12, 2025 am 12:06 AM

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

使用Next.js(後端集成)構建多租戶SaaS應用程序使用Next.js(後端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:23 AM

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

如何使用Next.js(前端集成)構建多租戶SaaS應用程序如何使用Next.js(前端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:22 AM

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
1 個月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
1 個月前By尊渡假赌尊渡假赌尊渡假赌

熱工具

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),