js總結常用排序演算法
排序演算法說明
排序演算法是比較常用的基本演算法,分類很多,最常見的就是氣泡排序,選擇排序,插入排序,以及快速排序,一直都想好好整理整理排序演算法,這兩天稍微花了點時間,通過看前輩們的帖子,以及十幾張草紙的努力,終於總結出了這個帖子.
(1)排序的定義:對一序列物件根據某個關鍵字進行排序(升序或降序)
(2)對排序演算法優劣術語的說明
穩定:如果a原本在b前面,而a=b,排序之後a仍然在b的前面;
不穩定:如果a原本在b的前面,而a=b,排序之後a可能會出現在b的後面;
內排序:所有排序操作都在記憶體中完成;
外排序:由於資料太大,因此把資料放在磁碟中,而排序通過磁碟和記憶體的資料傳輸才能進行;
時間複雜度: 一個演算法執行所耗費的時間。
空間複雜度: 執行完一個程式所需記憶體的大小。
(3)排序演算法圖片總結(圖片來源於網路)
n 資料規模
k "桶"的個數
in-place 佔用常數記憶體,不佔用額外記憶體
Out-place 佔用額外記憶體
(4)排序分類
1.氣泡排序(Bubble Sort)
氣泡排序可能是大多數人接觸的第一個排序演算法,對於學過C語言的人來說應該都是有一定了解的.
(1)演算法描述
氣泡排序是一種簡單的排序演算法.重複的走訪要排序的陣列,一次比較兩個數,如果它們順序錯誤就把它們兩兩交換過來,每一輪都會確定一個最大數直到沒有需要交換,排序完成.
js程式碼實現
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始陣列的顯示:"+arr);
function BubbleSort(arr) {
console.log("---------------------------------");
var temp="";
console.time("執行時間");
for(var i=0;i<arr.length;i++)
{
for(var j=0;j<arr.length-1;j++)
{
if (arr[j]>arr[j+1])
//如果兩個數的順序不滿足升序排序的規則
{
//交換兩個相鄰的數
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
//輸出每一輪的排序結果
var index=i+1;
console.log("冒泡第"+index+"輪結果:"+arr);
console.log("---------------------------------");
}
console.timeEnd("執行時間");
return arr;
}
console.log("氣泡排序的結果:"+BubbleSort(arr));
輸出結果
改進氣泡排序:每一輪都會確定一個最大值,所以確定後的最大值就無需改動了,那麼就可以設定一個標示性變數pos用於記錄每輪排序中最後一次進行交換的位置,下一趟掃描只要掃描到pos的位置即可.
js程式碼實現
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始陣列的顯示:"+arr);
function BubbleSort2(arr) {
console.log("---------------------------------");
var temp="";
var i=arr.length-1;
console.time("執行時間");
while (i>0)
{
var pos=0;
for(var j=0;j<arr.length-1;j++)
{
if(arr[j]>arr[j+1])//如果兩個數的順序不滿足升序排序的規則
{
pos=j;
//交換兩個相鄰的數
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
//輸出每一輪的排序結果
console.log("冒泡結果:"+arr);
console.log("---------------------------------");
i=pos;
}
console.timeEnd("執行時間");
return arr;
}
console.log("氣泡排序的結果:"+BubbleSort2(arr));
輸出結果
再次改進氣泡排序
傳統氣泡排序中每一輪只能找到一個最大值或最小值,可以考慮通過雙向冒泡一次找到兩個最終值(最大值和最小值),從而減少一半的排序次數
js程式碼實現
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始陣列的顯示:"+arr);
function BubbleSort3(arr) {
var low = 0;
var high= arr.length-1; //設定變數的初始值
var tmp,j;
console.time("改進後氣泡排序耗時");
while (low < high)
{
for (j= low; j< high; ++j) //正向冒泡,找到最大者
{
if (arr[j]> arr[j+1])
{
tmp = arr[j];
arr[j]=arr[j+1];
arr[j+1]=tmp;
}
}
--high; //修改high值, 前移一位
for (j=high; j>low; --j) //反向冒泡,找到最小者
{
if (arr[j]<arr[j-1])
{
tmp = arr[j];
arr[j]=arr[j-1];
arr[j-1]=tmp;
}
}
++low;//修改low值,後移一位
console.log("冒泡結果:"+arr);
console.log("---------------------------------");
}
console.timeEnd("改進後氣泡排序耗時");
return arr;
}
console.log("氣泡排序的結果:"+BubbleSort3(arr));
輸出結果
(2)演算法分析
- 最佳情況:T(n)=O(n)
當輸入的資料已經是正序的時候(已經是正序,還需要排序嗎?)
- 最差情況
當輸入的資料是反序的時候(直接反序就是正序了,何必這麼麻煩?)
- 平均情況
T(n)=O(n2)
2.選擇排序(Selection Sort)
對於選擇排序來說,資料規模越小越好,因為無論什麼資料進去時間複雜度都是O(n2),它唯一的好處就是不佔用額外的記憶體空間.選擇排序也算是一般比較常用的排序演算法.
(1)演算法簡介
選擇排序的基本原理:首先在未排序序列中找到最小(大)元素,存放在排序序列的位置,然後再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾,以此類推,知道所有元素均排序完畢.
js程式碼實現
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始陣列的顯示:"+arr);
function ChooseSort(arr) {
console.log("---------------------------------");
var temp="";
var minINdex; //定義存放最小值下標的變數
console.time("執行時間");
for(var i=0;i<arr.length;i++)
{
minINdex=i;//臨時定義最小值下標為i
for(var j=i+1;j<arr.length;j++)
{
if(arr[minINdex]>arr[j])//如果存在某個數小於當前minINdex對應的數
{
minINdex=j;//記錄下標到minINdex
}
}
if(minINdex!=i)//如果minINdex改變則交換數值
{
temp=arr[i];
arr[i]=arr[minINdex];
arr[minINdex]=temp;
}
//輸出每一輪的排序結果
var index=i+1;
console.log("選擇第"+index+"輪結果:"+arr);
console.log("---------------------------------");
}
console.timeEnd("執行時間");
return arr;
}
console.log("選擇排序的結果:"+ChooseSort(arr));
輸出結果
(2)演算法分析
- 最佳情況: T(n)=O(n2)
- 最差情況: T(n)=O(n2)
- 平均情況: T(n)=O(n2)
3.入排序(Insertion Sort)
(1)演算法簡介
插入排序的演算法是一種簡單直觀的排序演算法,它的工作原理是通過構建有序序列,對於未排序資料,在已排序序列中從後向前掃描,找到合適位置並插入,在從後到前的掃描過程中,需要反覆把已排序的排序元素向後挪位,為最新元素提供插入空間.
js程式碼實現
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始陣列的顯示:"+arr);
function InsertSort(arr) {
console.log("---------------------------------");
var length=arr.length; //獲得陣列的長度
var preIndex,current; //定義臨時變數,包括向前索引值和當前數
console.time("執行時間");
for(var i=1;i<length;i++)
{
preIndex=i-1;//前一個值的索引值
current=arr[i];//儲存當前值用於插入賦值
while(preIndex>-1&&arr[preIndex]>current) //如果preIndex合法且存在比當前值大的數
{
arr[preIndex+1]=arr[preIndex];//將有序陣列部分大於當前值的數的位置向後移動
preIndex--;//向前索引值變小
}
// 更新當前值
arr[preIndex+1]=current;//插入無序陣列中取出的值,即當前值
console.log("插入第"+i+"輪結果:"+arr);
console.log("---------------------------------");
}
console.timeEnd("執行時間");
return arr;
}
console.log("插入排序的結果:"+InsertSort(arr));
輸出結果
改進插入排序
使用二分查詢的方式查詢插入位置
js實現程式碼
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始陣列的顯示:"+arr);
function InsertSort2(arr) {
console.log("---------------------------------");
console.time("執行時間");
for(var i=0;i<arr.length;i++)
{
var key=arr[i];
var left=0;
var right=arr.length-1;
while (left<=right)
{
var middle=parseInt((left+right)/2);
if(key<=arr[middle])
{
right=middle-1;
}
else
{
left=middle+1;
}
}
for (var j=i-1;j>=left;j--)
{
arr[j+1]=arr[j];
}
arr[left]=key;
var index=i+1;
console.log("改進插入第"+index+"輪結果:"+arr);
console.log("---------------------------------");
}
console.timeEnd("執行時間");
return arr;
}
console.log("插入排序的結果:"+InsertSort2(arr));
輸出結果
(2)演算法分析
- 最佳情況: 輸入陣列按升序排列. T(n)=O(n)
- 最差情況:輸入陣列按降序排列. T(n)=O(n2)
- 平均情況: T(n)=O(n2)
4.希爾排序(Shell Sort)
簡單插入排序的改進版,它與插入排序的不同之處在於,它會優先距離比較遠的元素,希爾排序又稱縮小增量排序.
(1)演算法簡介
希爾排序的核心在於間隔序列的設定,既可以提前設好間隔序列,也可以動態定義間隔序列.
Js程式碼實現
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始陣列的顯示:"+arr);
function ShellSort(arr) {
var length=arr.length;//定義陣列長度
var temp;//定義臨時變數
var gap=1;//定義間隔
console.time("執行時間");
while (gap<length/3)//動態定義間隔序列
{
gap=gap*3+1;
}
for(gap;gap>0;gap=Math.floor(gap/3))
{
console.log("當前間隔值:"+gap);
for(var i=gap;i<length;i++)
{
temp=arr[i];//臨時記錄當前下標對應的陣列的值
for(var j=i-gap;j>=0 && arr[j]>temp;j-=gap)//如果存在和temp下標相隔gap或gap的倍數的值,並且這個值大於gap
{
arr[j+gap]=arr[j];//把符合條件的值向後移動gap個位置
}
arr[j+gap]=temp;//把temp放入空出的位置上
console.log("希爾結果:"+arr);
console.log("---------------------------------");
}
}
console.timeEnd("執行時間");
return arr;
}
console.log("希爾排序的結果:"+ShellSort(arr));
輸出結果
(2)演算法分析
- 最佳情況: T(n)=O(log2 n)
- 最差情況: T(n)=O(log2 n)
- 平均情況:T(n)=O(nlog n)
5.歸併排序(Merge Sort)
和選擇排序一樣,歸併排序的效能不受輸入資料的影響,但表現比選擇排序好的多,因為始終都是O(n log n)的時間複雜度,代價是需要額外的記憶體空間.
(1)演算法簡介
歸併排序是建立在歸併操作上的一種有效的排序演算法.該演算法是採用分治法的一個典型的應用.歸併排序是一種穩定的排序演算法,將已有序的子序列合併,得到完全有序的序列,即先使每個子序列有序,再使子序列間有序.如果將兩個有序列表合併成一個有序列表,稱為2-路歸併.
js程式碼實現
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始陣列的顯示:"+arr);
function mergeSort(arr) {
var length=arr.length;//獲得要排序的陣列的長度
if(length<2)//如果陣列的長度小於2,就直接返回原來的陣列,無需排序
{
return arr;
}
var middle=Math.floor(length/2);//獲得分割子序列的中間數
console.log("當前分割值:"+middle);
var left=arr.slice(0,middle);//分割得到左序列
var right=arr.slice(middle);//分割得到右序列
return merge(mergeSort(left),mergeSort(right));//迭代
}
function merge(left,right) //迭代方法
{
var result=[];//定義存放臨時排序變數
while(left.length>0&&right.length>0)//左序列和右序列的長度都大於零
{
if(left[0]<=right[0])//如果左序列的第一個值小於右序列的第一個值
{
result.push(left.shift());//把左序列的第一個值插入到result的後面
}
else {
result.push(right.shift());//把右序列的第一個值插入到result的後面
}
}
/*while(left.length)//如果左序列還有沒有處理的值,就把這些值依次插入result的後面
{
result.push(left.shift());
}
while (right.length)//如果右序列還有沒有處理的值,就把這些值依次插入到result
{
result.push(right.shift());
}
console.log("歸併結果:"+result);
console.log("---------------------------------");
return result;*/
//註釋部分和下面的三句等價
console.log("歸併結果:"+result);
console.log("---------------------------------");
return result.concat(left).concat(right);
}
console.log("歸併排序的結果:"+mergeSort(arr));
輸出結果
(2)演算法分析
- 最佳情況: T(n)=O(n)
- 最差情況: T(n)=O(nlog n)
平均情況: T(n)=O(nlog n)
6.快速排序(Quick Sort)
快排存在的意義就是快,效率高,是處理大資料最快的排序演算法之一
(1)演算法簡介
快排的基本思想:通過一輪排序將待排記錄分隔稱獨立的兩部分,其中一部分記錄的關鍵字均比另外一部分的關鍵字小,就可以分別對這兩部分記錄繼續進行排序,以達到這個序列有序.
js程式碼實現
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始陣列的顯示:"+arr);
function QuickSort(arr)
{
var i = 0;//定義向後查詢下標變數,用於尋找小於基準數的值
var j = arr.length - 1;//定義向前查詢下標變數,用於尋找大於基準數的值
console.time("執行時間");
var Sort = function(i, j)//讓基準值歸位
{
if (i == j) // 結束條件
{
return;
}
var key = arr[i];//記錄基準值
var stepi = i; // 記錄開始位置
var stepj = j; // 記錄結束位置
while (j > i)
{
// j <<-------------- 向前查詢,尋找小於基準數的值,必須j先移動
if (arr[j]>=key)
{
j--;
}
else//找到大於基準數的值的情況
{
arr[i] = arr[j]//把找到的大於基準數的值放在當前i對應的位置
//i++ ------------>>向後查詢,尋找大於基準數的值,在j找到大於基準數的時候再移動
while (j > ++i)
{
if (arr[i] > key)//如果找到大於基準數的值
{
arr[j] = arr[i];//把大於基準數的值放在當前j對應的位置
break;
}
}
}
//程式碼執行到這,就代表如果在迴圈結束前即找到了大於基準數的值,也找到了小於基準數的值,就已經成功交換了兩個值的位置
}
// 如果第一個取出的 key 是最小的數
if (stepi == i)
{
Sort(++i, stepj);//從最小值的後面開始繼續進行快速排序
return;
}
// 最後一個空位留給 key
//如果i=j,就把基準數歸位到i和j對應的同一個位置
arr[i] = key;
//左序列遞迴呼叫快速排序方法
Sort(stepi, i);
//右序列遞迴呼叫快速排序方法
Sort(j, stepj);
console.log("快排結果:"+arr);
console.log("---------------------------------");
}
//呼叫排序方法
Sort(i, j);
console.timeEnd("執行時間");
return arr;
}
console.log("快速排序的結果:"+QuickSort(arr));
輸出結果
快排的第二種實現方法
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始陣列的顯示:"+arr);
function QuickSort2(arr) {
//如果陣列長度小於1,直接返回陣列本身
if(arr.length<=1)
{
return arr;
}
var pivotindex=Math.floor(arr.length/2);//定義基準下標
var pivot=arr.splice(pivotindex,1)[0];//獲得基準值
var left=[];//左邊序列
var right=[];//右邊序列
for(var i=0;i<arr.length;i++)
{
if(arr[i]<pivot)//如果陣列中存在大於基準值的數
{
left.push(arr[i]);//把這個數存入左序列的後面
}
else//陣列中存在大於等於基準值的數
{
right.push(arr[i]);//把這個數存入右序列的後面
}
}
console.log("快排結果:"+arr);
console.log("---------------------------------");
//拼接左序列和基準值以及右邊序列
return QuickSort2(left).concat([pivot],QuickSort2(right));
}
console.log("快速排序的結果:"+QuickSort2(arr));
(2)演算法分析
- 最佳情況: T(n)=O(nlogn)
- 最差情況: T(n)=O(n2)
- 平均情況: T(n)=O(nlogn)
7.堆排序(Heap Sort)
(1)演算法簡介
堆排序是指利用堆這種資料結構所設計的一種排序演算法,堆積是一種近似於二叉樹的結構,並同時滿足堆積的性質:即子節點的建值或索引總是小於(或大於)它的父節點.
js程式碼實現
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始陣列的顯示:"+arr);
function heapSort(arr) {
console.time("執行耗時:");
//建堆
var temp;//定義臨時變數用於交換
var heapSize=arr.length;//獲得要操作的陣列的長度
//計算獲得初始堆
for(var i=Math.floor(heapSize/2);i>=0;i--)
{
// console.log(i);
//呼叫檢查是否符合堆的特點
heapify(arr,i,heapSize);
}
// console.log("初始堆:"+arr);
//堆排序
for (var j=heapSize-1;j>=1;j--)
{
//首先交換首元素和arr[j]的值
temp=arr[0];
arr[0]=arr[j];
arr[j]=temp;
//呼叫heapify方法調整當前的序列為符合堆特點的序列
heapify(arr,0,--heapSize);
console.log("堆排結果:"+arr);
console.log("---------------------------------");
}
console.timeEnd("執行耗時:");
return arr;
}
//檢查是否符合堆的特點
function heapify(arr,x,len) {
var l = 2 * x + 1;//獲得當前下標對應的數的左孩子
var r = 2 * x + 2;//獲得當前下標對應的數的右孩子
var largest = x;//父節點和左孩子以及右孩子中最大值對應的下標
var 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方法
heapify(arr,largest,len);
}
}
console.log("堆排序的結果:"+heapSort(arr));
輸出結果
(2)演算法分析
- 最佳情況: T(n)=O(nlogn)
- 最差情況: T(n)=O(nlogn)
- 平均情況: T(n)=O(nlogn)
8.計數排序(Counting Sort)
計數排序的核心在於將輸入的資料值轉化為鍵儲存在額外開闢的陣列空間中,作為一種線性的時間複雜度的排序,計數排序要求輸入的資料必須是有確定範圍的整數.
(1)演算法簡介
計數排序是一種穩定的排序演算法,使用一個額外的陣列 C,其中第i個元素是待排序陣列A中值等於i的元素的個數,然後根據陣列C來把A中的元素放到正確的位置,它只能對證書進行排序.
js程式碼實現
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始陣列的顯示:"+arr);
function countingSort(arr) {
var len=arr.length;
B=[];//定義臨時陣列用於儲存排序後的陣列
C=[];//定義輔助陣列,用於確定排序後的陣列中每個值的位置
min=max=arr[0];//初始化最大值和最小值為陣列的首項
console.time("執行時間:")
//構造C陣列
for(var i=0;i<len;i++)
{
min=min<=arr[i]?min:arr[i];//求最小值
max=max>=arr[i]?max:arr[i];//求最大值
C[arr[i]]=C[arr[i]]?C[arr[i]]+1:1;//為C陣列新增值,用於記錄等於i的數的個數,輔助排序
//陣列下標原陣列中的數的值,下標對應的C中的值是等於i的數的個數
}
//調整C陣列的值為累加形式,如果原陣列不存在相應的值,該項就保持上一個狀態值,如果存在,就加上它對應的C中的值
for(var j=min;j<max;j++)
{
C[j+1]=(C[j+1]||0)+(C[j]||0);
}
//通過C有目的的把原陣列的數一次插入到合適的位置
for(var k=len-1;k>=0;k--)
{
B[C[arr[k]]-1]=arr[k];
C[arr[k]]--;//C動態更新資料
console.log("計數排序結果:"+B);
console.log("---------------------------------");
}
console.timeEnd("執行時間:");
return B;
}
console.log("計數排序的結果:"+countingSort(arr));
輸出結果
(2)演算法分析
計數排序不是比較排序,當輸入的元素是n個0到k之間的整數,它的執行時間是O(n+k),陣列C的大小取決於陣列中資料的範圍,陣列範圍越大,需要的時間和記憶體就越大.
- 最佳情況: T(n)=O(n+k)
- 最差情況: T(n)=O(n+k)
平均情況: T(n)=O(n+k)
9.桶排序(Bucket Sort)
桶排序是計數排序的升級版,利用函式的對映關係,高效與否的關鍵在於這個對映函式的確定.
(1)演算法簡介
桶排序的原理:假設輸入的資料服從均勻分佈,將資料分到有限數量的桶裡,每個桶分別排序(有可能再使用其他的排序演算法或是以遞迴方式繼續使用桶排序進行排序)
js程式碼實現
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始陣列的顯示:"+arr);
function bucketSort(arr,num) {
if(arr.length<=-1)
{
return arr;
}
var len=arr.length;
var buckets=[];
var result=[];
var min,max;
min=max=arr[0];
var regex ='/^[1-9]+[0-9]*$/';
var space;
var n=0;
num=num||((num>1&®ex.test(num))?num:10);
console.time("執行耗時:");
for(var i=0;i<len;i++)
{
min=min<=arr[i]?min:arr[i];
max=max>=arr[i]?max:arr[i];
}
space=(max-min+1)/num;
for(var j=0;j<len;j++)
{
var index=Math.floor((arr[j]-min)/space);
if(buckets[index])//非空桶,插入排序
{
var k=buckets[index].length-1;
while(k>=0&&buckets[index][k]>arr[j])
{
buckets[index][k+1]=buckets[index][k];
k--;
}
buckets[index][k+1]=arr[j];
}
else//空桶,初始化
{
buckets[index]=[];
buckets[index].push(arr[j]);
}
}
while(n<num)
{
result=result.concat(buckets[n]);
n++;
console.log("桶排序結果:"+result);
console.log("---------------------------------");
}
console.timeEnd("執行耗時:");
return result;
}
console.log("桶排序的結果:"+bucketSort(arr,2));
輸出結果
(2)演算法分析
桶排序最好情況下使用線性時間O(n),桶排序時間複雜度取決於每個桶之間資料進行排序的時間複雜度,因為它的時間複雜度都為O(n),桶劃分的越小,桶之間的資料越小,排序所用的時間也會越少,但相應的空間消耗就會越大.
- 最佳情況: T(n)=O(n+k)
- 最差情況: T(n)=O(n+k)
平均情況: T(n)=O(n2)
10.基數排序(Radix Sort)
基數排序也是非比較類排序演算法,對每一位進行排序,從最低位進行排序,複雜度為O(kn),n為陣列長度,k為陣列中的數的最大的位數.
(1)演算法簡介
基數排序是按照低位優先排序,然後收集,再按照高位排序,然後收集,以此類推,直到最高位.
js程式碼實現
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始陣列的顯示:"+arr);
function RadixSort(arr,maxDigit) {
var mod = 10;
var dev = 1;
var counter = [];
console.time('基數排序耗時');
for (var i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
for(var j = 0; j < arr.length; j++) {
var bucket = parseInt((arr[j] % mod) / dev);
if(counter[bucket]== null) {
counter[bucket] = [];
}
counter[bucket].push(arr[j]);
}
var pos = 0;
for(var j = 0; j < counter.length; j++) {
var value = null;
if(counter[j]!=null) {
while ((value = counter[j].shift()) != null)
{
arr[pos++] = value;
console.log("基數排序結果:"+arr);
console.log("---------------------------------");
}
}
}
}
console.timeEnd('基數排序耗時');
return arr;
}
console.log("基數排序的結果:"+RadixSort(arr,2));
輸出結果
後記
js總結常用的排序演算法大概就總結這麼多吧,也看了很多前輩們的總結,發現前輩們很厲害,要把這寫完全搞懂還是得花很多心思的,可能有些地方總結的不全面或者不專業,歡迎大家在評論裡提出問題,可以共同學習.