Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
小新 編譯自 Medium
量子位 出品 | 公眾號(hào) QbitAI
Q-Learning是強(qiáng)化學(xué)習(xí)中最常用的算法之一。
Medium上有篇文章,討論了這種算法的一個(gè)重要部分:搜索策略。
量子位搬運(yùn)過來,以下為博客譯文:
我們先介紹下有關(guān)概念和符號(hào)。
強(qiáng)化學(xué)習(xí)
強(qiáng)化學(xué)習(xí)(Reinforcement Learning, RL)屬于機(jī)器學(xué)習(xí)的一個(gè)分支,利用智能體(agent)通過狀態(tài)感知、選擇動(dòng)作和接收獎(jiǎng)勵(lì)來與環(huán)境互動(dòng)。每一步中,智能體都會(huì)通過觀察環(huán)境狀態(tài),選擇并執(zhí)行一個(gè)動(dòng)作,來改變其狀態(tài)并獲得獎(jiǎng)勵(lì)。
馬爾可夫決策過程
在傳統(tǒng)環(huán)境中,馬爾可夫決策過程(Markov Decision Processes, MDP)可以解決不少RL問題。這里,我們不會(huì)深入討論MDP的理論,有關(guān)MDP算法的更多內(nèi)容可參考:
https://en.wikipedia.org/wiki/Markov_decision_process
我們用森林火災(zāi)來解釋下MDP算法,代碼實(shí)現(xiàn)可使用python MDP Toolbox:
http://pymdptoolbox.readthedocs.io/en/latest/api/example.html
森林管理包括兩個(gè)動(dòng)作,等待和砍伐。每年要做出一個(gè)決定,一是為林中動(dòng)物保持古老森林,二是砍伐木材來而賺錢。而且,每年有p概率發(fā)生森林火災(zāi),有1-p的概率為森林生長(zhǎng)。
先定義MDP算法中一些參數(shù)S、A、P、R和γ,其中:
S是狀態(tài)空間(有限),包括3種不同年齡樹木,年齡級(jí)分別為0-20年、21-40年和40年以上;
A是動(dòng)作空間(有限),即等待或砍伐;
P和R分別是轉(zhuǎn)移矩陣和獎(jiǎng)勵(lì)矩陣,很容易寫出它的閉合形式;
γ是數(shù)值在0與1之間的貼現(xiàn)因子,用來平衡短時(shí)和未來獎(jiǎng)勵(lì)的關(guān)系;
策略π是當(dāng)前狀態(tài)下決策的靜態(tài)分布;
該模型的目標(biāo)是在未給出MDP動(dòng)態(tài)知識(shí)的情況下找到一個(gè)最優(yōu)策略π*。
要注意,如果具有這個(gè)動(dòng)態(tài)知識(shí),直接用最優(yōu)值迭代方法就能找到最優(yōu)策略。
1def optimal_value_iteration(mdp, V0, num_iterations, epsilon=0.0001):2 V=np.zeros((num_iterations+1, mdp.S))3 V[0][:]=np.ones(mdp.S)*V04 X=np.zeros((num_iterations+1, mdp.A, mdp.S))5 star=np.zeros((num_iterations+1,mdp.S))6 for k in range(num_iterations):7 for s in range(mdp.S):8 for a in range(mdp.A):9 X[k+1][a][s]=mdp.R[a][s] + mdp.discount*np.sum(mdp.P[a][s].dot(V[k]))10 star[k+1][s]=(np.argmax(X[k+1,:,s]))11 V[k+1][s]=np.max(X[k+1,:,s])12 if (np.max(V[k+1]-V[k])-np.min(V[k+1]-V[k])) < epsilon:13 V[k+1:]=V[k+1]14 star[k+1:]=star[k+1]15 X[k+1:]=X[k+1]16 break17 else: pass18 return star, V, X
獎(jiǎng)勵(lì)變化曲線
最優(yōu)策略是等到森林處于古老且茂盛的狀態(tài)時(shí)進(jìn)行砍伐,這容易理解,因?yàn)樵谏痔幱谧罟爬系臓顟B(tài)時(shí)砍伐的獎(jiǎng)勵(lì)是等待讓森林生長(zhǎng)的獎(jiǎng)勵(lì)的5倍,有r1=10,r2=50。
Q-Learning算法
Q-Learning算法中的“Q”代表著策略π的質(zhì)量函數(shù)(Quality function),該函數(shù)能在觀察狀態(tài)s確定動(dòng)作a后,把每個(gè)狀態(tài)動(dòng)作對(duì) (s, a) 與總期望的折扣未來獎(jiǎng)勵(lì)進(jìn)行映射。
Q-Learning算法屬于model-free型,這意味著它不會(huì)對(duì)MDP動(dòng)態(tài)知識(shí)進(jìn)行建模,而是直接估計(jì)每個(gè)狀態(tài)下每個(gè)動(dòng)作的Q值。然后,通過在每個(gè)狀態(tài)下選擇具有最高Q值的動(dòng)作,來繪制相應(yīng)的策略。
如果智能體不斷地訪問所有狀態(tài)動(dòng)作對(duì),則Q-Learning算法會(huì)收斂到最優(yōu)Q函數(shù)[1]。
下面我們給出關(guān)于Q-Learning算法的Python實(shí)現(xiàn)。
要注意,這里的學(xué)習(xí)率α是w=4/5時(shí)的多項(xiàng)式,這里使用了引用[3]的結(jié)果。
這里使用的ε-greedy搜索策略,后面會(huì)詳細(xì)介紹。
1def q_learning(mdp, num_episodes, T_max, epsilon=0.01):2 Q=np.zeros((mdp.S, mdp.A))3 episode_rewards=np.zeros(num_episodes)4 policy=np.ones(mdp.S)5 V=np.zeros((num_episodes, mdp.S))6 N=np.zeros((mdp.S, mdp.A))7 for i_episode in range(num_episodes):8 # epsilon greedy exploration9 greedy_probs=epsilon_greedy_exploration(Q, epsilon, mdp.A)10 state=np.random.choice(np.arange(mdp.S))11 for t in range(T_max):12 # epsilon greedy exploration13 action_probs=greedy_probs(state)14 action=np.random.choice(np.arange(len(action_probs)), p=action_probs)15 next_state, reward=playtransition(mdp, state, action)16 episode_rewards[i_episode] +=reward17 N[state, action] +=118 alpha=1/(t+1)**0.819 best_next_action=np.argmax(Q[next_state]) 20 td_target=reward + mdp.discount * Q[next_state][best_next_action]21 td_delta=td_target - Q[state][action]22 Q[state][action] +=alpha * td_delta23 state=next_state24 V[i_episode,:]=Q.max(axis=1)25 policy=Q.argmax(axis=1) 26 return V, policy, episode_rewards, N
獎(jiǎng)勵(lì)變化曲線
探索與利用的平衡
序列學(xué)習(xí)算法會(huì)涉及到一個(gè)基本選擇:
利用:根據(jù)當(dāng)前信息做出最佳決策;
探索:做出其他決策來收集更多信息。
合理平衡好探索和利用的關(guān)系,對(duì)智能體的學(xué)習(xí)能力有重大影響。過多的探索會(huì)阻礙智能體最大限度地獲得短期獎(jiǎng)勵(lì),因?yàn)檫x擇繼續(xù)探索可能獲得較低的環(huán)境獎(jiǎng)勵(lì)。另一方面,由于選擇的利用動(dòng)作可能不是最優(yōu)的,因此靠不完全知識(shí)來利用環(huán)境會(huì)阻礙長(zhǎng)期獎(jiǎng)勵(lì)的最大化。
ε-greedy搜索策略
該策略在每一步利用概率ε來選擇隨機(jī)動(dòng)作。
這可能是最常用也是最簡(jiǎn)單的搜索策略,即用ε調(diào)整探索動(dòng)作。在許多實(shí)現(xiàn)中,ε會(huì)隨著時(shí)間不斷衰減,但也有不少情況,ε會(huì)被設(shè)置為常數(shù)。
1def epsilon_greedy_exploration(Q, epsilon, num_actions):2 def policy_exp(state):3 probs=np.ones(num_actions, dtype=float) * epsilon / num_actions4 best_action=np.argmax(Q[state])5 probs[best_action] +=(1.0 - epsilon)6 return probs7 return policy_exp
不確定優(yōu)先搜索策略
不確定優(yōu)先(Optimism in Face of Uncertainty)搜索策略,最開始被用來解決隨機(jī)式多臂賭博機(jī)問題 (Stochastic Multi-Armed Bandit),這是一個(gè)很經(jīng)典的決策問題,賭徒要轉(zhuǎn)動(dòng)一個(gè)擁有n個(gè)槽的老虎機(jī),轉(zhuǎn)動(dòng)每個(gè)槽都有固定回報(bào)概率,目標(biāo)是找到回報(bào)概率最高的槽并且不斷地選擇它來獲取最高的回報(bào)。
賭徒面臨著利用還是探索的問題,利用機(jī)器獲得最高的平均獎(jiǎng)勵(lì)或探索其他未玩過的機(jī)器,以期望獲得更高的獎(jiǎng)勵(lì)。
這個(gè)問題與Q-Learning算法中的探索問題非常相似:
利用:在給定狀態(tài)下選擇具有最高Q值的動(dòng)作;
探索:做出其他決策來探索更多信息,通過選擇不了解或不夠了解的環(huán)境。
不確定優(yōu)先狀態(tài):只要我們對(duì)某個(gè)槽的回報(bào)不確定時(shí)不確定手臂的結(jié)果,我們就會(huì)考慮當(dāng)前環(huán)境來選擇最佳的手臂。
不確定優(yōu)先算法有兩方面:
若當(dāng)前處于最佳環(huán)境,那算法會(huì)直接選擇最佳的手臂;
若當(dāng)前不處于最佳環(huán)境,則算法會(huì)盡量降低不確定性。
置信區(qū)間上界(Upper Confidence Bound, UCB)是一種常用的不確定優(yōu)先算法[2],我們把它結(jié)合到Q-Learning方法中,有:
Q(s, a):狀態(tài)s下動(dòng)作a縮放后的Q值;
N(t,s,a):在時(shí)刻t和狀態(tài)s下動(dòng)作a被選擇的次數(shù)。
此時(shí),智能體的目標(biāo)為Argmax {Q(s, a)/ a ∈ A},這意味著在狀態(tài)s中選擇具有最高Q值的動(dòng)作。但是在t時(shí)刻Q(s,a)值是未知的。
在t時(shí)刻,Q估計(jì)值為Q(t, s, a),則有Q(s,a)=+ (Q(s,a) ? )。
(Q(s,a) ? )為相應(yīng)誤差項(xiàng)。
霍夫不等式 (Hoeffding’s inequality)可用來處理這類誤差。事實(shí)上,當(dāng)t變化時(shí),有:
優(yōu)先策略可寫成:Argmax {Q+(t, s, a)/ a ∈ A},且有:
當(dāng)β大于0時(shí),執(zhí)行探索動(dòng)作;當(dāng)β為0時(shí),僅利用已有估計(jì)。
這種界限方法是目前最常用的,基于這種界限后面也有許多改進(jìn)工作,包括UCB-V,UCB*,KL-UCB,Bayes-UCB和BESA[4]等。
下面給出經(jīng)典UCB算法的Python實(shí)現(xiàn),及其在Q-Learning上的應(yīng)用效果。
1def UCB_exploration(Q, num_actions, beta=1):2 def UCB_exp(state, N, t):3 probs=np.zeros(num_actions, dtype=float)4 Q_=Q[state,:]/max(Q[state,:]) + np.sqrt(beta*np.log(t+1)/(2*N[state]))5 best_action=Q_.argmax()6 probs[best_action]=17 return probs8 return UCB_exp
獎(jiǎng)勵(lì)變化曲線
UCB搜索算法應(yīng)該能很快地獲得高額獎(jiǎng)勵(lì),但是前期搜索對(duì)訓(xùn)練過程的影響較大,有希望用來解決更復(fù)雜的多臂賭博機(jī)問題,因?yàn)檫@種方法能幫助智能體跳出局部最優(yōu)值。
下面是兩種策略的對(duì)比圖。
總結(jié)與展望
Q-Learning是強(qiáng)化學(xué)習(xí)中最常用的算法之一。在這篇文章中,我們討論了搜索策略的重要性和如何用UCB搜索策略來替代經(jīng)典的ε-greedy搜索算法。
更多更細(xì)致的優(yōu)先策略可以被用到Q-Learning算法中,以平衡好利用和探索的關(guān)系。
[1] T. Jaakkola, M. I. Jordan, and S. P. Singh, “On the convergence of stochastic iterative dynamic programming algorithms” Neural computation, vol. 6, no. 6, pp. 1185–1201, 1994.
[2] P. Auer, “Using Confidence Bounds for Exploitation-Exploration Trade-offs”, Journal of Machine Learning Research 3 397–422, 2002.
[3] E. Even-Dar, and Y. Mansour, “Learning Rates for Q-learning”, Journal of Machine Learning Research 5 1–25, 2003.
[4] A. Baransi, O.-A. Maillard, and S. Mannor, “Sub-sampling for multi-armed bandits”, Joint European Conference on Machine Learning and Knowledge Discovery in Databases, 115–131, 2014.
原文:https://medium.com/sequential-learning/optimistic-q-learning-b9304d079e11
— 完 —
誠(chéng)摯招聘
量子位正在招募編輯/記者,工作地點(diǎn)在北京中關(guān)村。期待有才氣、有熱情的同學(xué)加入我們!相關(guān)細(xì)節(jié),請(qǐng)?jiān)诹孔游还娞?hào)(QbitAI)對(duì)話界面,回復(fù)“招聘”兩個(gè)字。
量子位 QbitAI · 頭條號(hào)簽約作者
?'?' ? 追蹤AI技術(shù)和產(chǎn)品新動(dòng)態(tài)
礎(chǔ)算法
一、排序
//冒泡排序 function bubbleSort(arr) { for(var i=1, len=arr.length; i < len - 1; ++i) { for(var j=0; j <=len - i; ++j) { if (arr[j] > arr[j + 1]) { let temp=arr[j]; arr[j]=arr[j + 1]; arr[j + 1]=temp; } } } }
//插入排序 過程就像你拿到一副撲克牌然后對(duì)它排序一樣 function insertionSort(arr) { var n=arr.length; // 我們認(rèn)為arr[0]已經(jīng)被排序,所以i從1開始 for (var i=1; i < n; i++) { // 取出下一個(gè)新元素,在已排序的元素序列中從后向前掃描來與該新元素比較大小 for (var j=i - 1; j >=0; j--) { if (arr[i] >=arr[j]) { // 若要從大到小排序,則將該行改為if (arr[i] <=arr[j])即可 // 如果新元素arr[i] 大于等于 已排序的元素序列的arr[j], // 則將arr[i]插入到arr[j]的下一位置,保持序列從小到大的順序 arr.splice(j + 1, 0, arr.splice(i, 1)[0]); // 由于序列是從小到大并從后向前掃描的,所以不必再比較下標(biāo)小于j的值比arr[j]小的值,退出循環(huán) break; } else if (j===0) { // arr[j]比已排序序列的元素都要小,將它插入到序列最前面 arr.splice(j, 0, arr.splice(i, 1)[0]); } } } return arr; }
//快速排序 function qSort(arr) { //聲明并初始化左邊的數(shù)組和右邊的數(shù)組 var left=[], right=[]; //使用數(shù)組第一個(gè)元素作為基準(zhǔn)值 var base=arr[0]; //當(dāng)數(shù)組長(zhǎng)度只有1或者為空時(shí),直接返回?cái)?shù)組,不需要排序 if(arr.length <=1) return arr; //進(jìn)行遍歷 for(var i=1, len=arr.length; i < len; i++) { if(arr[i] <=base) { //如果小于基準(zhǔn)值,push到左邊的數(shù)組 left.push(arr[i]); } else { //如果大于基準(zhǔn)值,push到右邊的數(shù)組 right.push(arr[i]); } } //遞歸并且合并數(shù)組元素 return [...qSort(left), ...[base], ...qSort(right)]; //return qSort(left).concat([base], qSort(right)); }
補(bǔ)充:
在這段代碼中,我們可以看到,這段代碼實(shí)現(xiàn)了通過pivot區(qū)分左右部分,然后遞歸的在左右部分繼續(xù)取pivot排序,實(shí)現(xiàn)了快速排序的文本描述,也就是說該的算法實(shí)現(xiàn)本質(zhì)是沒有問題的。
雖然這種實(shí)現(xiàn)方式非常的易于理解。不過該實(shí)現(xiàn)也是有可以改進(jìn)的空間,在這種實(shí)現(xiàn)中,我們發(fā)現(xiàn)在函數(shù)內(nèi)定義了left/right兩個(gè)數(shù)組存放臨時(shí)數(shù)據(jù)。隨著遞歸的次數(shù)增多,會(huì)定義并存放越來越多的臨時(shí)數(shù)據(jù),需要Ω(n)的額外儲(chǔ)存空間。
因此,像很多算法介紹中,都使用了原地(in-place)分區(qū)的版本去實(shí)現(xiàn)快速排序,我們先介紹什么是原地分區(qū)算法。
原地(in-place)分區(qū)算法描述
通過以上3個(gè)步驟,就將以基準(zhǔn)值為中心,數(shù)組的左右兩側(cè)數(shù)字分別比基準(zhǔn)值小或者大了。這個(gè)時(shí)候在遞歸的原地分區(qū),就可以得到已排序后的數(shù)組。
原地分區(qū)算法實(shí)現(xiàn)
// 交換數(shù)組元素位置 function swap(array, i, j) { var temp=array[i]; array[i]=array[j]; array[j]=temp; } function partition(array, left, right) { var index=left; var pivot=array[right]; // 取最后一個(gè)數(shù)字當(dāng)做基準(zhǔn)值,這樣方便遍歷 for (var i=left; i < right; i++) { if (array[i] <=pivot) { swap(array, index, i); index++; } } swap(array, right, index); return index; }
因?yàn)槲覀冃枰f歸的多次原地分區(qū),同時(shí),又不想額外的地址空間所以,在實(shí)現(xiàn)分區(qū)算法的時(shí)候會(huì)有3個(gè)參數(shù),分別是原數(shù)組array,需要遍歷的數(shù)組起點(diǎn)left以及需要遍歷的數(shù)組終點(diǎn)right
最后返回一個(gè)已經(jīng)排好序的index值用于下次遞歸,該索引對(duì)應(yīng)的值一定比索引左側(cè)的數(shù)組元素小,比所有右側(cè)的數(shù)組元素大。
再次基礎(chǔ)上我們還是可以進(jìn)一步的優(yōu)化分區(qū)算法,我們發(fā)現(xiàn) <=pivot可以改為<pivot,這樣可以減少一次交換
原地分區(qū)版快速排序?qū)崿F(xiàn)
function quickSort(array) { function swap(array, i, j) { var temp=array[i]; array[i]=array[j]; array[j]=temp; } function partition(array, left, right) { var index=left; var pivot=array[right]; // 取最后一個(gè)數(shù)字當(dāng)做基準(zhǔn)值,這樣方便遍歷 for (var i=left; i < right; i++) { if (array[i] < pivot) { swap(array, index, i); index++; } } swap(array, right, index); return index; } function sort(array, left, right) { if (left > right) { return; } var storeIndex=partition(array, left, right); sort(array, left, storeIndex - 1); sort(array, storeIndex + 1, right); } sort(array, 0, array.length - 1); return array; }
二、字符串
//判斷回文字符串 function palindrome(str) { var reg=/[\W\_]/g; var str0=str.toLowerCase().replace(reg, ""); var str1=str0.split("").reverse().join(""); return str0===str1; }
function reverseString(str) { return str.split("").reverse().join(""); }
function findMaxDuplicateChar(str) { var cnt={}, //用來記錄所有的字符的出現(xiàn)頻次 c=''; //用來記錄最大頻次的字符 for (var i=0; i < str.length; i++) { var ci=str[i]; if (!cnt[ci]) { cnt[ci]=1; } else { cnt[ci]++; } if (c=='' || cnt[ci] > cnt[c]) { c=ci; } } console.log(cnt) return c; }
三、數(shù)組
//數(shù)組去重 function uniqueArray(arr) { var temp=[]; for (var i=0; i < arr.length; i++) { if (temp.indexOf(arr[i])==-1) { temp.push(arr[i]); } } return temp; //or return Array.from(new Set(arr)); }
四、查找
//二分查找 function binary_search(arr, l, r, v) { if (l > r) { return -1; } var m=parseInt((l + r) / 2); if (arr[m]==v) { return m; } else if (arr[m] < v) { return binary_search(arr, m+1, r, v); } else { return binary_search(arr, l, m-1, v); } }
五、樹的搜索/遍歷
//深搜 非遞歸實(shí)現(xiàn) function dfs(node) { var nodeList=[]; if (node) { var stack=[]; stack.push(node); while(stack.length !=0) { var item=stack.pop(); nodeList.push(item); var children=item.children; for (var i=children.length-1; i >=0; i--) { stack.push(children[i]); } } } return nodeList; } //深搜 遞歸實(shí)現(xiàn) function dfs(node, nodeList) { if (node) { nodeList.push(node); var children=node.children; for (var i=0; i < children.length; i++) { dfs(children[i], nodeList); } } return nodeList; }
//廣搜 非遞歸實(shí)現(xiàn) function bfs(node) { var nodeList=[]; if (node !=null) { var queue=[]; queue.unshift(node); while (queue.length !=0) { var item=queue.shift(); nodeList.push(item); var children=item.children; for (var i=0; i < children.length; i++) queue.push(children[i]); } } return nodeList; } //廣搜 遞歸實(shí)現(xiàn) var i=0; //自增標(biāo)識(shí)符 function bfs(node, nodeList) { if (node) { nodeList.push(node); if (nodeList.length > 1) { bfs(node.nextElementSibling, nodeList); //搜索當(dāng)前元素的下一個(gè)兄弟元素 } node=nodeList[i++]; bfs(node.firstElementChild, nodeList); //該層元素節(jié)點(diǎn)遍歷完了,去找下一層的節(jié)點(diǎn)遍歷 } return nodeList; }
高階函數(shù)衍生算法
1.filter去重
filter也是一個(gè)常用的操作,它用于把Array的某些元素過濾掉,然后返回剩下的元素。也可以這么理解,filter的回調(diào)函數(shù)把Array的每個(gè)元素都處理一遍,處理結(jié)果返回false則過濾結(jié)果去除該元素,true則留下來
用filter()這個(gè)高階函數(shù),關(guān)鍵在于正確實(shí)現(xiàn)一個(gè)“篩選”函數(shù)。
其實(shí)這個(gè)篩選函數(shù)有多個(gè)參數(shù),filter(function (element, index, self),演示一個(gè)使用filter去重,像這樣:
var r, arr=['apple', 'strawberry', 'banana', 'pear', 'apple', 'orange', 'orange', 'strawberry']; r=arr.filter(function (element, index, self) { return self.indexOf(element)===index; //拿到元素,判斷他在數(shù)組里第一次出現(xiàn)的位置,是不是和當(dāng)前位置一樣,一樣的話返回true,不一樣說明重復(fù)了,返回false。 });
2.sort排序算法
排序也是在程序中經(jīng)常用到的算法。無論使用冒泡排序還是快速排序,排序的核心是比較兩個(gè)元素的大小。如果是數(shù)字,我們可以直接比較,但如果是字符串或者兩個(gè)對(duì)象呢?直接比較數(shù)學(xué)上的大小是沒有意義的,因此,比較的過程必須通過函數(shù)抽象出來。通常規(guī)定,對(duì)于兩個(gè)元素x和y,如果認(rèn)為x < y,則返回-1,如果認(rèn)為x==y,則返回0,如果認(rèn)為x > y,則返回1,這樣,排序算法就不用關(guān)心具體的比較過程,而是根據(jù)比較結(jié)果直接排序。
值得注意的例子
// 看上去正常的結(jié)果: ['Google', 'Apple', 'Microsoft'].sort(); // ['Apple', 'Google', 'Microsoft']; // apple排在了最后: ['Google', 'apple', 'Microsoft'].sort(); // ['Google', 'Microsoft", 'apple'] // 無法理解的結(jié)果: [10, 20, 1, 2].sort(); // [1, 10, 2, 20]
解釋原因
第二個(gè)排序把a(bǔ)pple排在了最后,是因?yàn)樽址鶕?jù)ASCII碼進(jìn)行排序,而小寫字母a的ASCII碼在大寫字母之后。
第三個(gè)排序結(jié)果,簡(jiǎn)單的數(shù)字排序都能錯(cuò)。
這是因?yàn)锳rray的sort()方法默認(rèn)把所有元素先轉(zhuǎn)換為String再排序,結(jié)果’10’排在了’2’的前面,因?yàn)樽址?’比字符’2’的ASCII碼小。
因此我們把結(jié)合這個(gè)原理:
var arr=[10, 20, 1, 2]; arr.sort(function (x, y) { if (x < y) { return -1; } if (x > y) { return 1; } return 0; }); console.log(arr); // [1, 2, 10, 20]
上面的代碼解讀一下:傳入x,y,如果x<y,返回-1,x與前面排,如果x>y,返回-1,x后面排,如果x=y,無所謂誰拍誰前面。
還有一個(gè),sort()方法會(huì)直接對(duì)Array進(jìn)行修改,它返回的結(jié)果仍是當(dāng)前Array,一個(gè)栗子:
插入排序是將數(shù)組分為待排序和已排序兩個(gè)區(qū)間。依次從待排序區(qū)間中取出一項(xiàng),用該項(xiàng)跟已排序區(qū)間項(xiàng)逐個(gè)對(duì)比,通過位移來實(shí)現(xiàn)插入到對(duì)應(yīng)位置的排序方式。插入排序平均時(shí)間復(fù)雜度是:O(n^2)
步驟是:
插入排序有多種實(shí)現(xiàn)方式,這里介紹常見的3種:
1、通用實(shí)現(xiàn)方式,自左往右遍歷待排序數(shù)組,再從當(dāng)前的左側(cè)位置開始自右往左循環(huán)已排序數(shù)組,再逐個(gè)比較和移動(dòng)被比較項(xiàng),最后將當(dāng)前項(xiàng)填入到空缺位置上。
2、利用數(shù)組splice方法,類似打撲克牌,先拿出要排序的牌,然后找準(zhǔn)位置插入。這種方式利用了原生API,減少了數(shù)組反復(fù)移動(dòng)位置的操作。性能上之前差不多。
3、新建數(shù)組法與splice結(jié)合法,這種方式會(huì)多建立一個(gè)數(shù)組,也就會(huì)多占用一個(gè)空間,但理解起來最容易,也利用了JS語言的特性。
插入排序與冒泡、選擇都是比較簡(jiǎn)單好懂的排序方式,性能上也差不多。插入排序通俗來講就像打撲克牌排序,你抓了一手牌之后。假如是:2、1、5、3、4,你會(huì):
1、先把牌分成兩組,假定左側(cè)第一張牌為一組(標(biāo)識(shí)A,這時(shí)只有2),其他牌為另外一組(標(biāo)識(shí)B,包括1、5、3、4)。
2、從B組里面從左起選擇第一張牌(位置空出等待填充),也就是1,拿這張牌與A組里面從右往左挨個(gè)對(duì)比,當(dāng)遇到比這張牌還小時(shí)就在這個(gè)位置停留下來(如果A組全部比這張牌都大那就在A組最前面停留下來,如果A組里沒有比這張牌大的就在當(dāng)前位置停留)。
3、然后將A組里比這張牌(也就是1)大的牌逐個(gè)往右移動(dòng)1位,原B組空出位置被填充,此時(shí)剛才停留的位置空出,將1這張牌插入在這里。這時(shí)候A組增加一個(gè)數(shù)字,變?yōu)椋?、2,B組減少1個(gè),變?yōu)椋?、3、4。
4、移動(dòng)指針,繼續(xù)指向B組的第一個(gè),也就是5。用5這張牌重復(fù)第二部,即拿5去跟A組自右往左逐個(gè)比較,然后插入到A組。此時(shí)A組:1、2、5,B組:3、4。
5、將B組里數(shù)字按照第二部重復(fù)操作,直到B組為空時(shí)整個(gè)循環(huán)結(jié)束。此時(shí)A組為:1、2、3、4、5。
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。