簡單算法匯總
一、全排列問題(Permutation)
問題描寫敘述:即給定{1,2,3},返回123,132,213,231,312,321
《Permutation》
1)無順序的全排列問題:
將序列P(n) = {1….. n}的全排列問題看成P(n)={1,P(n-1)} + {2,P(n-1)}…..的問題。即確定第一個元素的值為1。然後和剩下n-1個元素的全排列結果組合到一起;然後再將1和剩下的每一個元素進行交換。然後和其剩下的n-1個元素排列結果進行組合。顯然這是一個遞歸問題。
// 遞歸實現
public void permutation(int [] datas, int index ) {
if (datas == null || index < 0 || index >= datas. length) // 差錯控制
return;
// 遞歸終點
if (index == datas .length - 1) {
print_data( datas);
}
for (int i = index ; i < datas .length ; i ++) {
// 交換(註意i==index時。事實上並沒有交換)
swap( datas, i , index);
permutation( datas, index + 1);
swap( datas, i , index);
}
}
2)有反復值的全排列問題:
註意每次遞歸的時候去除反復值就可以,即有反復值就不進行交換。
// 非反復情況
public void permutationNoRepeat(int[] datas, int index ) {
if (datas == null || index < 0) // 差錯控制
return;
if (index >= datas .length - 1) {
print_data( datas);
return;
}
for (int i = index ; i < datas .length ; i ++) {
// 取出反復值
if ((i != index ) && (datas[i] == datas[index]))
continue;
swap( datas, i , index);
permutationNoRepeat( datas, index + 1);
swap( datas, i , index);
}
}
3)找到下一個更大值(next_permutation)
即1342。找到下一個更大值1423。
解題思路:
基本思想是從後往前遍歷。找到第一個遞增的二元對(即a[j] < a[j+1]);然後從後往前遍歷到j+1位置,找到第一個k值a[k]>a[j],交換k和j,然後將j+1後面的序列反轉;
註意增序列則表示字典序較小,減序列則表示字典序較大;假設遍歷找不到增序列,表示當前數值已經是最大值。
原理:
http://jingyan.baidu.com/article/63acb44a90370061fcc17e18.html
public boolean next_permutation(int[] datas) {
if (datas == null || datas.length == 0)
return true ;
int p1 = datas .length - 2;
int p2 = datas .length - 1;
for (; p1 >= 0; p1 --) {
if (datas [p1 ] < datas [p1 + 1])
break;
}
if (p1 == -1) {
reverse( datas, 0, datas. length - 1);
return true ;
} else {
for (; p2 > p1 ; p2 --) {
if (datas [p2 ] > datas [p1 ]) {
swap( datas, p1, p2);
reverse( datas, p1 + 1, datas. length - 1);
}
}
}
print_data( datas);
return false ;
}
// 顛倒數組
private void reverse(int[] datas, int start , int end) {
while (start < end ) {
swap( datas, start ++, end --);
}
}
4)有順序的全排列(能夠看做非遞歸實現)
解題思路:使用next_permutation也以獲得全排列,即不斷調用next_permutation來獲得更大值。然後依次輸出
public void permutation(int[] datas) {
if (datas == null)
return;
do {
print_data( datas);
} while (!next_permutation(datas )) ;
}
5)有特殊要求的全排列,比方要求4必須在3前面
解題思路:進行全排列,然後推斷4和3的位置再進行輸出;(?更好方法)
6)查找數字排列組合中的第k個組合:(Permutation Sequence)
解題思路:
即給定{1,2,3},和3,返回全排列(123,132,213,231,312,321)中的第三個,即213
註意:
這裏不須要將全排列計算出來。然後再得到第k個;
或者使用nextPermutation來計算第k個;這兩種時間復雜度都較大;
最好的做法:是直接利用數學知識進行計算:
還是分為兩層來看,第一位確定的話。後面P(n-1)的全排列為(n-1)!。則能夠依據算法推斷出第一位是哪個數值;剩下的類推。
public class Solution {
public String getPermutation( int n , int k) {
if ((n <= 0) || (n > 9) || ( k <= 0) || ( k > countN( n)))
return "" ;
// 記錄結果字符串
StringBuilder resBder = new StringBuilder();
// 記錄當前數字集合中剩下的未使用數字
List<Integer> remainList = new ArrayList <>();
// 初始化remainList
for (int i = 1; i <= n ; i ++)
remainList.add(i );
k--;
while (n > 1) {
int count = countN(n - 1);
int index = k / count ;
// 加入結果數字
resBder.append( remainList.get(index ));
// 更新,進行下一層循環
remainList.remove(index );
k %= count;
n--;
}
resBder.append( remainList.get(0));
return resBder .toString();
}
// 計算每一個數字的階乘
private int countN(int n ) {
int result = 1;
while (n > 0) {
result *= n--;
}
return result ;
}
}
一、二叉樹問題匯總:
1)二叉樹三種遍歷非遞歸實現
2)重建二叉樹
3)推斷樹A是否為樹B的子樹
4)二叉樹鏡像
5)從上往下打印二叉樹
6)二叉搜索樹的後序遍歷
7)二叉搜索樹轉化為雙向鏈表
8)二叉樹中和為某一值的路徑
9)求二叉樹的深度
10)平衡二叉樹
二、遞歸問題匯總
1)魔術索引問題
即一個遞增序列,滿足A[i]=i的稱為魔術索引;
1>無反復值的魔術索引問題:二分法
2>有反復值:縮小範圍法
2)跳臺階、斐波那契
3)機器人走方格:都註意使用空間存儲來優化。
4)N皇後問題:
回溯法:由於每一行僅僅能有一個皇後,所以使用一個一維數組index[]來記錄每一行的皇後的列的位置就可以,然後給數組中的每一個元素賦個初始值-1,表示當前行的位置還沒有確定;
然後從第一個皇後開始賦值,從0開始,再給第二個皇後賦值,也是從0開始遍歷。然後寫一個推斷當前index[]矩陣是否合法的函數,每次給一個皇後賦值的時候。都須要進行一次推斷,假設合法,則繼續給下一個皇後賦值;假設不合法,就取這一層相應的index裏面的記錄的數值比方說是m,然後給這個皇後賦值m+1,繼續推斷是否合法,反復之前操作;
假設在這一層,0-n-1全部都賦值完了,皇後仍然沒有找到合法的位置,那就採用回溯法,把這一層的index值又一次置為-1。回到上一層設置上一層的皇後的位置,往右移一步。反復之間操作。
直到賦值到第N層。全部皇後位置都合法之後,再將結果輸出;
由於可能的結果不止有一種。當輸出一種結果之後。回溯到N-1層,又一次開始之前的設置,檢查操作;
遞歸法:遞歸法比較簡單,比方八皇後問題,就能夠看成已知當中一個皇後位置。求其它7個的位置。然後再確定第二個皇後的位置。求剩下6個皇後的位置;以此類推進行遞歸,直至遞歸到最後一層,輸出結果。
三、最遠距離問題JumpGame:
即推斷[3,1,3,1,1,0,4]是否可到達。
解決方法:非常easy,一直往前走,計算每一步能到達的最遠位置index+A[index];和之前記錄的最遠位置reach做比較。大於則更新reach值。循環的終點是到了終點即i==n了或者i>reach了。這個時候推斷i==n(註意是n。由於在n-1後,還會再i++,然後循環才幹推出)。
public boolean canJump(int[] nums) {
int i = 0;
int n = nums.length;
for ( int reach = 0; i < n && i <= reach; ++i)
reach = Math. max(i + nums[i], reach);
return i == n;
}
四、構造順序矩陣和打印循環順序矩陣Matrix:
《leetcode-54 Spiral Matrix 順時針打印矩陣(《劍指offer》面試題20)》
《leetcode 58、Length of Last Word。59、Spiral Matrix II ;60、Permutation Sequence》
解題思路:定義上下左右四個維度的限定值,然後向右。向下,向左,向上進行遍歷,並註意更新相應值。
// 構造序列
public int [][] generateMatrix(int n) {
if (n < 0)
return null ;
int[][] matrix = new int[n][n];
// 記錄上下左右邊界值
int left = 0;
int right = n - 1;
int top = 0;
int bottom = n - 1;
// 註意起始值為1
int count = 1;
while ((left <= right ) && (top <= bottom)) {
// 往右走進行賦值
for (int j = left ; j <= right ; j ++)
matrix[ top][ j] = count++;
++ top; // 更新邊界值
// 向下走進行賦值
for (int i = top ; i <= bottom ; i ++)
matrix[ i][ right] = count++;
-- right;
// 向左走進行賦值
for (int j = right ; j >= left ; j --)
matrix[ bottom][ j] = count++;
-- bottom;
// 向上走進行賦值
for (int i = bottom ; i >= top ; i --)
matrix[ i][ left] = count++;
++ left;
}
return matrix ;
}
// 打印序列
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> result = new ArrayList<>();
if ((matrix == null) || (matrix.length == 0) || ( matrix[0]. length == 0))
return result ;
int left = 0; int right = matrix[0].length - 1;
int top = 0; int bottom = matrix.length - 1;
while ((left <= right ) && (top <= bottom)) {
// ==== 先向右遍歷 ===== //
for (int j = left ; j <= right ; j ++)
result.add( matrix[ top][ j]);
top++; // 遍歷後要註意更新四個維度的值
// ===== 向下遍歷 ===== //
for (int i = top ; i <= bottom ; i ++)
result.add( matrix[ i][ right]);
right--;
// ===== 向左遍歷 ===== //
for (int j = right ; j >= left ; j --)
result.add( matrix[ bottom][ j]);
bottom--;
// ===== 向上遍歷 ===== //
for (int i = bottom ; i >= top ; i --)
result.add( matrix[ i][ left]);
left++;
}
return result ;
}
五、連續子數組的最大和
解題思路:使用一個max記錄當前最大值,使用一個curSum來記錄遍歷數組時候的暫時的和;
遍歷數組,比方來到第i個元素,假設之前的curSum<0,那麽curSum再加上a[i]僅僅會使得相加值更小,因此取更大值。也就是令curSum=a[i],把前面的和序列舍棄掉;
假設curSum>=0,則能夠直接把兩個值相加來作為新的curSum;
然後再將curSum和max做比較,去更大值來更新max值。最後遍歷完一遍。返回max值就是最大的值。
六、數值的整數次方pow:
解題思路:註意處理n<0的情況,n<0時,要把a轉化成1/a;
還要註意處理底數為0。而指數卻為負數的不合法情況;
private double power(double x, int n) {
if (n < 0) {
n = - n; x = 1.0 / x;
}
double result = 1.0;
for ( double base = x ;n > 0; n >>= 1) {
if ((n & 0x1) == 1)
result *= base;
base *= base;
}
return result;
}
七、推斷是否為同位詞:
即“ate””eat” “tae”為同位詞;從一串字符串中找出同位詞:
解題思路:使用HashMap,先對全部詞進行字典序排序,然後比對。
八、字符串數組排序:
由於字符串實現了Comparable接口,能夠比較兩個字符串之間的大小,因此能夠像實現int數組排序一樣實現字符串數組的排序。
九、k個排序鏈表合並成一個排序鏈表(或者k個排序數組合並成一個排序數組)
解題思路:創建一個k階的最小堆,創建堆的時間復雜度o(n*logn);取最小值的事件復雜度為O(1);刪除最小值。加入一個新值。調整堆的時間復雜度為O(lgn);
每次取最小值。最為新鏈表(新數組)的下一個值;然後再從該數值相應數組中取出一個新值放在堆頂,然後調整堆。反復上述過程;
也能夠使用敗者樹來實現。
相關題:找到一個數組中的第k大的值
思路:維護一個k階最小堆。新值和堆頂元素進行比較。假設大於堆頂元素值。則替換堆頂元素,調整堆;
十、求兩個整數的最大公約數。最小公倍數(輾轉相除法)
public int maxgcp(int x, int y) {
int num1 = x, num2 = y;
// 向排序,即將較大值移到前面
if (x < y) {
int temp = x ;
x = y;
y = temp;
}
// 輾轉相除法
int r = 1;
while ( r != 0) {
r = x % y;
x = y;
y = r;
}
System. out.println("最大公約數:" + x );
System. out.println("最小公倍數:" + num1 * num2 / x );
return x;
}
十一、求格雷碼:
格雷碼是二進制轉化成的編碼,它的相鄰兩個數的格雷碼僅僅有一個位是不同的;最大數和最小數也僅僅有一個位不同;所以適應了真正電氣環境下數值不能連續變化多位的情況;
編碼:二進制轉化為格雷碼: 第一位保持不變,然後從最右邊一位開始,與左邊一位進行異或,得到的異或值即為格雷碼。
1001001010 ==> 1101101111
解碼:格雷碼轉化為二進制: 解碼從最左邊開始。一個為解碼值保持不前,然後從左邊第二位開始,每一位和前一位的解碼值進行異或,得到的值即為解碼值。
十二、兩個鏈表的公共交點:
解法:兩個鏈表相交,則鏈表相交的第一個公共點之後的全部節點一定是相交的;即兩個鏈表是呈Y型的。
1)兩個鏈表無環時的解法:
(1)能夠先遍歷兩條鏈表。得到兩個鏈表的長度m,n;然後雙指針,一個先走m-n步(假設m>n),然後兩個指針同一時候出發,第一個相交的節點即為公共節點;若一直遍歷結束也沒有公共節點,則兩個鏈表不相交;
(2)方法同一,但不須要遍歷完兩個鏈表來獲得兩個鏈表的長度;記兩個鏈表為A、B,能夠設置兩個指針p、q,同一時候出發,當q到達鏈表尾(即為NULL時),此時從鏈表A頭部出發一個指針a;當p到達鏈表尾時,此時從鏈表B頭部出發一個指針b;則a,b轉化為方法一中的問題;(兩個方法的時間復雜度同樣)
(3)假設兩個鏈表相交,由於其公共節點之後的鏈表同樣;此時將當中一個鏈表鏈接到還有一個鏈表之後,形成的新鏈表一定有環,則原問題轉化為求一個有環鏈表的環的公共交點問題。
2)考慮鏈表有環
假設兩個有環的鏈表相交。那麽它們的環必定為公共環。
假設交點不在環上,即在環前面的直鏈上,即轉化為前面的連個無環鏈表求解公共點的問題。第一個公共點就是第一個交點。
可是假設交點在環上,即環的入口點不同,那麽任一環的入口點都可為第一公共點。
十三、刪除字符串中的指定字符:
題目:輸入兩個字符串。從第一字符串中刪除第二個字符串中全部的字符。比如,輸入”They are students.”和”aeiou”。則刪除之後的第一個字符串變成”Thy r stdnts.”。
解題思路:基本思想是遍歷字符串s1,推斷字符串s1中的每一個字符在s2中是否存在。假設存在則刪除;
這裏就須要考慮兩個細節:
1)刪除字符問題
一個是刪除字符的處理;傳統方法是刪除之後。讓字符串數組後的全部字節往前移一位。這樣操作全然部字符的時間復雜度為O(n^2);明顯有能夠優化的空間:
使用兩個指針,pFast,pSlow;當有字符須要刪除時,pSlow不變。pFast前移;當有字符不須要刪除時,將pSlow和pFast指向的字符交換。最後取0-pSlow數組的字符組成的字符串就可以。基本思想是將後面不須要刪除的字符替換到前面來。
2)推斷字符是否須要刪除:
推斷字符是否須要刪除。即該字符是否在s2字符串中;能夠使用的方法如Hash。使用HashMap或則HashSet把s2中的字符存儲進來。然後遍歷s1每一個字符c在HashMap是否已經存在就可以;時間復雜度為O(1)。
同樣的方法能夠是使用一個boolean[256]數組。記錄char(共256個)是否存在;原理相似Hash
十四、計算一個字符串中的最長無反復字符串:
問題描寫敘述:計算給定一個字符串。如”abcabcbb” 則其最長無反復字符串為 “abc”;字符串”bbbbb”其最長無反復字符串為”b”;
解題思路:使用一個256的int數組indexs來記錄每一個char在字符串中出現的位置。
假設indexs[i]為-1,表示當前測試字符串中沒有出現過字節i,因此直接將indexs[i]賦值為i,即記錄其出現位置。
假設indexs[i]不為-1,則表示已經出現過。這裏要依據此算法分成兩種情況討論;這裏使用一個start記錄當前測試字符串的起始位置,即當前測試的字符串為start–i。假設indexs[i]小於start,表示該字節出如今測試字符串之前,因此能夠當做-1情況對待;再者,假設已經存在,則當前字符串已經不是滿足要求的唯一性字符串了。因此計算當前的非反復最大值,和系統當前記錄的最大值作比較,記錄更大值;然後測試下一個字符串,為滿足非反復條件。則下一個測試字符串的起始位置須要從index[start]+1開始;
十五、求數組的最大值與最小值
1)主要的遍歷法須要比較2N次。
2)採用雙元素法,記錄max和min;每次比較兩個值,較小值和min做比較,較大值和max作比較;這樣終於的比較次數為1.5*N次。
3)採用分治法;將數組分為兩部分。分別得到兩個子數組的最大值最小值,然後再合並在一起進行比較;
十六、找出數組中僅僅出現一次的數:
1)其它的數都出現了偶數次:直接使用全部異或就可以。
2)其它數出現的是奇數m次:則假設沒有該特殊值。其它全部值二進制時二進制各個位相加之和肯定都能被m整數整除。再加上異常值,則全部位對於n進行取余。得到的值必定是該特殊值的二進制表示。
十七、數組中出現次數超過一半的數:
解題思路:
解法一:將原問題轉化為求數組的中位數,採用高速排序的思想,每一次Partition取末位為哨兵。遍歷將小於、大於哨兵的數分別移至哨兵左右。最後返回哨兵在處理後的數組中的位置。
不斷縮小要處理的數組的長度大小。終於確定返回值為數組長度一半的元素。即為中位數。
解法二:由於題設該數字出現的次數大於其它全部數字出現的次數。故用兩個變量,一個表示數字num_data,一個表示次數;當下一個數字等於num_data時,則times加1;如若不等於,time減1;直至times等於0,則將num_data更換為下一個數字;由題知,最後得到的num_data的結果必為所要求得的值。
十八、旋轉數組:
問題描寫敘述:即給定一個數組如[1,2,3,4,5,6,7] ,及一個k=3,將後面k個數字旋轉到前面來。則旋轉之後的數組為[5,6,7,1,2,3,4].
解題思路:和旋轉字符串問題比較相似。即先將數組分為兩部分,後面k個數字為一組,前面n-k個為一組,將兩組分別反轉。然後再將整個數組反轉就可以。反轉能夠採用雙指針法。
十八、推斷一個數是否是2的n次方:
解題思路:
一個數num是2的n次方則二進制表示為(000010000…),則num-1是(000001111111…);其num與num-1二進制位必定沒有一個相等。
因此推斷num&(num - 1)是否等於0就可以。
十九、計算一個數的二進制中1的個數:
解題思路:
解法一:直接轉換成二進制然後循環右移取出二進制中每一位推斷是否為1就可以
解法二:使用n&(n-1);二進制中有幾個1,就循環幾次n&(n-1)得到0
二十、2Sum,3Sum,4Sum問題:
《leetcode-1 Two Sum 找到數組中兩數字和為指定和》
《Leetcode-15 3Sum》
《leetcode-18 4Sum》
《leetcode-16 3Sum Closest》
問題描寫敘述:給定一個數組和一個target結果值,求2個/3個/4個數字的和為target的解法。
**解題思路:**2Sum問題典型的解決方案是使用雙指針。從頭部尾部同一時候往中間走進行推斷;
首先對數組進行排序,時間復雜度為O(NlogN)
然後從i=0,j=end開始和末位的兩個數字開始。計算兩個之和sum,若sum大於目標值target,則須要一個較小的因子,j–。反之,i++;直至找到終於的結果
二十一、排序算法
1、冒泡排序(交換排序):
在循環遍歷中,每一次遍歷數組將最大的數字通過交換沈到最後一位
時間復雜度(O(n^2)):最壞O(n^2),最好O(n);空間復雜度:O(1); 穩定
優化:
1)設置一個flag標示,當一次遍歷有交換設置為true。沒有交換時則表示前面數組已經有序,無需再做遍歷
2)記錄每一次遍歷發生交換的最後位置。由於這個位置之後的數組肯定是有序的。最後交換位置為0時。循環結束
2、直接插入排序:
在循環遍歷中,第j次遍歷過程向已經排好序的數組a[1..j-1]插入a[j]
時間復雜度(O(n^2)):最壞O(n^2),最好O(n);空間復雜度:O(1)。穩定
優化:查找插入排序。在插入的時候使用二分法
3、希爾排序(插入排序):將數組分為非常多小序列,然後分別進行直接插入排序;待整個數組基本有序的時候,最後進行一次插入排序
時間復雜度(O(n^(1-2))):最壞O(n^2)。最好O(n)。空間復雜度:O(1);不穩定
實現。選取一個增量序列(遞減到1)(比方x/2序列)
void ShellInsertSort(int a[], int n, int dk) {
for(int i = dk ; i < n ; ++i ){
if(a [i] < a [i - dk]){ //若第i個元素大於i-1元素,直接插入。小於的話,移動有序表後插入
int j = i - dk ;
int x = a [i]; //復制為哨兵。即存儲待排序元素
a[i] = a[i - dk]; //首先後移一個元素
while(x < a[j]){ //查找在有序表的插入位置
a[j + dk] = a[j];
j -= dk; //元素後移
}
a[j + dk] = x; //插入到正確位置
}
print(a, n,i );
}
}
/**
* 先按增量d(n/2,n為要排序數的個數進行希爾排序
*
*/
void shellSort(int a[], int n){
int dk = n /2;
while(dk >= 1){
ShellInsertSort( a, n, dk);
dk = dk/2;
}
}
4、高速排序:
每一次循環選取一個中間值,將比這個值大的元素移動到中間的後面,比中間值小的移到中間值前面;這樣分成了兩個子序列,然後再對子序列進行高速排序。
時間復雜度:O(nlogn), 最好O(nlogn),最壞(基本有序時退化成冒泡)O(n^2);空間復雜度:O(1)+(遞歸棧的緩存空間最大O(n),最小O(logn)) 不穩定
private void quickSort(int[] a, int start , int end) {
if (start < end ) {
int mid = partition(a, start, end);
quickSort( a, start, mid - 1);
quickSort( a, mid + 1, end);
}
}
private int partition(int[] a, int low , int high) {
int temp = a [low ];
while (low < high ) {
while ((low < high ) && (a[high] >= temp))
-- high;
swap( a, low, high);
while ((low < high ) && (a[low] <= temp))
++ low;
swap( a, low, high);
}
a[ low] = temp;
return low ;
}
實現:單指針法,雙指針法
高速排序的改進:
1)中樞值的選取:傳統方法中使用最左元素或者最右元素,這樣在數組基本有序時的性能較差(會退化成冒泡算法)。
改進方法一:中樞值 pivot使用隨機數來代替(可是產生隨機數也會帶來性能損耗)
方法二:pivot選取first-middle-last中的中間大小的那個值。時間復雜度會降低到12/7 ln(n)
方法三:median-of-three對小數組來說有非常大的概率選擇到一個比較好的pivot。可是對於大數組來說就不足以保證能夠選擇出一個好的pivot,因此還有個辦法是所謂median-of-nine。這個怎麽做呢?它是先從數組中分三次取樣,每次取三個數,三個樣品各取出中數。然後從這三個中數當中再取出一個中數作為pivot,也就是median-of-medians。
取樣也不是亂來,各自是在左端點、中點和右端點取樣。什麽時候採用median-of-nine去選擇pivot。這裏也有個數組大小的閥值。這個值也全然是經驗值,設定在40,大小大於40的數組使用median-of-nine選擇pivot。大小在7到40之間的數組使用median-of-three選擇中數,大小等於7的數組直接選擇中數。大小小於7的數組則直接使用插入排序
5、歸並排序:
通過分治的思想,對數組序列進行分割。分割到最後每一個子序列中僅僅有一個元素的時候,然後再兩兩合並,最後每一次循環都是兩個有序數組合並成一個有序數組的操作。 穩定
O(nlogn); O(nlogn); O(nlogn); 空間復雜度O(n);
private void mergeSort(int[] datas, int[] copy , int start, int end) {
if (start < end ) {
int mid = (end + start ) / 2;
mergeSort( datas, copy, start, mid);
mergeSort( datas, copy, mid + 1, end);
merge( datas, copy, start, mid, end);
}
}
private void merge(int[] datas, int[] copy, int start , int mid, int end) {
int i = start ;
int j = mid + 1;
int index = start ;
while ((i <= mid ) && (j <= end)) {
if (datas [i ] <= datas [j ])
copy[ index++] = datas[ i++];
else
copy[ index++] = datas[ j++];
}
while (i <= mid )
copy[ index++] = datas[ i++];
while (j <= end )
copy[ index++] = datas[ j++];
System. arraycopy(copy, start, datas, start, end - start + 1);
}
非遞歸實現方法:
void mergeSort2(int n){
int s =2,i ;
while(s <=n ){
i=0;
while(i +s <=n ){
merge(i,i+s-1,i+s/2-1);
i+= s;
}
//處理末尾殘余部分
merge(i,n-1,i+s/2-1);
s*=2;
}
//最後再從頭到尾處理一遍
merge(0,n-1,s/2-1);
}
6、簡單選擇排序:第i次遍歷的時候。從i-n序列中找到最小值,與第i個元素進行交換。也就是每一趟遍歷都選取一個最小元素作為第i元素值。
時間復雜度:O(n^2);最優: O(n^2);最差:O(n^2); 空間復雜度O(1);不穩定
7、堆排序:
葉子節點的值都大於或等於根節點的值(最小堆)
建堆:從n/2+1開始剩下的都為葉子節點。因此從n/2到0遞減順序,分別以每一個節點為根節點建立最小堆。
堆的調整:比較左右子節點的值得到最小值。然後比較根節點與最小值。假設根節點的值要大。則不滿足最小堆的概念,須要進行調整。將兩者進行交換。然後再以交換後的位置為根節點,反復前面過程。
時間復雜度:O(nlogn);最優: O(nlogn);最差:O(nlogn); 空間復雜度O(1);不穩定
private void heapSort(int[] datas) {
buildHeap( datas);
for (int i = datas .length - 1; i >= 0; i--) {
System. out.printf("%d " , datas [0]);
// 交換data[0]和data[i];
swap( datas, i, 0);
// 註意這裏的length為i
HeapAdjust( datas, 0, i);
}
}
// 建立堆
private void buildHeap(int[] datas) {
for (int i = (datas .length - 1) / 2; i >= 0; i--) {
HeapAdjust( datas, i, datas. length);
}
}
// 調整堆中節點位置
private void HeapAdjust(int[] datas, int s , int length) {
int child = 2 * s + 1;
while (child < length ) {
// 假設右節點存在,而且小於左節點的值
if ((child + 1 < length) && ( datas[ child] > datas[ child + 1]))
child++;
// 假設大於子節點的值。則不滿足最小堆的概念。故要進行調整
if (datas [s ] > datas [child ]) {
swap( datas, s, child);
s = child;
child = 2 * s + 1;
} else {
break;
}
}
}
8、基數排序/桶排序:線性。時間復雜度為O(n);
排序算法性能比較:
時間復雜度:
O(n^2)的有:直接插入排序,冒泡排序,簡單選擇排序
O(n^(1-2)): 希爾排序
O(nlogn):高速排序,歸並排序,堆排序
O(n):線性:桶排序;基數排序
選擇上:
基本有序時,選擇直接插入排序,希爾排序;而高速排序會退化成冒泡排序;
平均性能上最優的是高速排序;在最壞情況下,性能上不如堆排序和歸並排序,而n較大時。歸並排序優於堆排序,可是其須要的存儲空間要大。
直接插入排序在基本有序或者n較小時最佳。
穩定性:
即值同樣的關鍵字在排序前後位置先後順序不變
穩定的:冒泡、插入、歸並、基數
不穩定:選擇、希爾、高速、堆;
設待排序元素的個數為n.
1)當n較大,則應採用時間復雜度為O(nlog2n)的排序方法:高速排序、堆排序或歸並排序序。
高速排序:是眼下基於比較的內部排序中被覺得是最好的方法。當待排序的關鍵字是隨機分布時,高速排序的平均時間最短。
堆排序 : 假設內存空間同意且要求穩定性的。
歸並排序:它有一定數量的數據移動。所以我們可能過與插入排序組合,先獲得一定長度的序列。然後再合並。在效率上將有所提高。
2) 當n較大,內存空間同意,且要求穩定性 =》歸並排序
3)當n較小。可採用直接插入或直接選擇排序。
直接插入排序:當元素分布有序,直接插入排序將大大降低比較次數和移動記錄的次數。
直接選擇排序 :元素分布有序,假設不要求穩定性,選擇直接選擇排序
5)一般不使用或不直接使用傳統的冒泡排序。
6)基數排序
它是一種穩定的排序算法,但有一定的局限性:
1、關鍵字可分解。
2、記錄的關鍵字位數較少,假設密集更好
3、假設是數字時,最好是無符號的。否則將添加相應的映射復雜度,可先將其正負分開排序。
二十二、外部排序:
1)依據內存能夠緩存的大小,將全部數據進行分段,加入到內存,進行內部排序。
2)然後進行k-路歸並;k路歸並使用敗者樹;
http://blog.csdn.net/whz_zb/article/details/7425152
勝者樹:錦標賽排序
敗者樹:使用節點來記錄失敗的元素;勝者往上一層進行比較,擴展一個節點來記錄終於的冠軍;
敗者樹重構僅僅須要新加入的元素和父節點進行比較(即敗者進行比較)。而勝者樹則是須要和右節點進行比較;
二十四、二分查找:
考慮有數值的同樣情況
private static int binarySearch(int[] datas, int start , int end, int target) {
int mid = 0;
while (start <= end ) {
mid = start + ( end - start) / 2;
if (datas [mid ] > target ) {
end = mid - 1;
} else
start = mid + 1;
}
// return mid表示查找到同樣值。或者未查找到時的可插入位置
// 假設是查找順序的可插入位置,則須要返回start
return mid ;
}
三、KMP:
// 獲取next數組
private void getNext(char[] p, int[] next ) {
// 初始化的值
int j = 0;
next[ j] = -1;
int k = next [0];
// j相應的獲取next[j+1]
while (j < p .length - 1) {
// 註意k的初始值
if (k == -1 || p[j] == p[k]) {
next[++ j] = ++ k;
} else {
k = next[ k];
}
}
}
private int KMP(String s, String p) {
if (s .length() < p.length())
return -1;
int i = 0;
int j = 0;
// 獲取next數組
int[] next = new int[p.length()];
getNext( p.toCharArray(), next);
while ((i < s .length()) && (j < p.length())) {
if (j == - 1 || s.charAt(i) == p.charAt(j)) {
++ i; ++ j;
} else {
j = next[ j];
}
}
// 返回匹配位置
if (j >= p .length())
return i - p .length();
return -1;
}
改進的算法:
// 獲取nextval數組
private void getNextval(char[] p, int[] next ) {
int j = 0;
next[ j] = -1;
int k = next [0];
while (j < p .length - 1) {
if (k == -1 || p[k] == p[j]) {
k++;
j++;
if (p [k ] != p [j ])
next[ j] = k;
else
next[ j] = next[ k];
} else {
k = next[ k];
}
}
}
簡單算法匯總