資料結構與演算法-排序
排序(不全以後補)
基數排序
原理:
https://www.jb51.net/article/129428.htm
package com.atguigu.sort; import org.junit.Test; import java.util.ArrayList; import java.util.List; /** * @anthor shkstart * @create 2020-08-16 15:03 */ public class JSsort { @Test public void test(){ int[] array = {135,242,192,93,345,11,24,19}; radixSort(array); } public void radixSort(int[] array){ int max = array[0]; for (int i=0;i<array.length;i++){ //找到陣列中的最大值 if(array[i]>max){ max = array[i]; } } int keysNum = 0; //關鍵字的個數,我們使用個位、十位、百位...當做關鍵字,所以關鍵字的個數就是最大值的位數 while(max>0){ max /= 10; keysNum++; } List<ArrayList<Integer>> buckets = new ArrayList<ArrayList<Integer>>(); for (int i=0;i<10;i++){ //每位可能的數字為0~9,所以設定10個桶 buckets.add(new ArrayList<Integer>()); //桶由ArrayList<Integer>構成 } for (int i=0;i<keysNum;i++){ //由最次關鍵字開始,依次按照關鍵字進行分配 for (int j=0;j<array.length;j++){ //掃描所有陣列元素,將元素分配到對應的桶中 //取出該元素對應第i+1位上的數字,比如258,現在要取出十位上的數字,258%100=58,58/10=5 int key =array[j]%(int)Math.pow(10, i+1)/(int)Math.pow(10, i); buckets.get(key).add(array[j]); //將該元素放入關鍵字為key的桶中 } //分配完之後,將桶中的元素依次複製回陣列 int counter = 0; //元素計數器 for (int j=0;j<10;j++){ ArrayList<Integer> bucket =buckets.get(j); //關鍵字為j的桶 while(bucket.size()>0){ array[counter++] = bucket.remove(0); //將桶中的第一個元素複製到陣列,並移除 } } System.out.print("第"+(i+1)+"輪排序:"); for (int n= 0;n < array.length;n++){ System.out.print(array[n]+ " "); } System.out.println(); } } }
快速排序
原理
不妨取首元素m = S[lo]作為候選,將其從向量中取出並做備份,騰出的空閒單元便於其它元素的位置調整。然後如圖(b)所示,不斷試圖移動lo和hi,使之相互靠攏。當然,整個移動過程中,需始終保證lo(hi)左側(右側)的元素均不大於(不小於)m。最後如圖(c)所示,當lo與hi彼此重合時,只需將原備份的m回填至這一位置,則S[lo = hi]=m便成為一個名副其實的軸點
package com.atguigu.sort; import org.junit.Test; /** * @anthor shkstart * @create 2020-08-16 15:03 */ public class Quicksort { @Test public void test() { int[] array = {135,242,192,93,345,11,24,19}; this._elem = array; this.quickSort(0,8); for (int i = 0;i < 8;i++){ System.out.print(this._elem[i] + " "); } } public int[] _elem = new int[8]; public void quickSort(int lo,int hi){ if (hi - lo < 2) return; int mi = partition1(lo,hi - 1); quickSort(lo,mi); quickSort(mi+1,hi); } /** * 子任務規模接近在這裡卻無法保證 * 若在最終有序向量中該候選元素的秩為r,則子向量的規模必為r和n - r - 1。 * 特別地,r = 0時子向量規模分別為0和n - 1左側子向量為空,而右側子向量與原向量幾乎等長 */ public int partition1(int lo,int hi){ swap(_elem[lo],_elem[(int) (lo + Math.random()*(100)%(hi - lo + 1))]); int pivot = _elem[lo]; while (lo < hi){ while ((lo < hi) && (pivot <= _elem[hi])){ hi--; } _elem[lo] = _elem[hi]; while ((lo < hi) && (_elem[lo] <= pivot)){ lo++; } _elem[hi] = _elem[lo]; } _elem[lo] = pivot; return lo; } public void swap(int a,int b){ int c = a; a = b; b = c; } /** * 將交替地將右(左)側元素轉移至左(右)側,並最終恰好將軸點置於正中央的位置。 * 這就意味著,退化的輸入向量能夠始終被均衡的切分,如此反而轉為最好情況, */ public int partition2(int lo,int hi){ swap(_elem[lo],_elem[(int) (lo + Math.random()*(100)%(hi - lo + 1))]); int pivot = _elem[lo]; while (lo < hi){ while ((lo < hi)){ if (pivot < _elem[hi]){ hi--; } else { _elem[lo++] = _elem[hi]; break; } } while ((lo < hi)){ if (pivot < _elem[hi]){ lo++; } else { _elem[hi--] = _elem[lo]; break; } } } _elem[lo] = pivot; return lo; } }
實現
/** * 子任務規模接近在這裡卻無法保證 * 若在最終有序向量中該候選元素的秩為r,則子向量的規模必為r和n - r - 1。 * 特別地,r = 0時子向量規模分別為0和n - 1左側子向量為空,而右側子向量與原向量幾乎等長 */ public int partition1(int lo,int hi){ swap(_elem[lo],_elem[(int) (lo + Math.random()*(100)%(hi - lo + 1))]); int pivot = _elem[lo]; while (lo < hi){ while ((lo < hi) && (pivot <= _elem[hi])){ hi--; } _elem[lo] = _elem[hi]; while ((lo < hi) && (_elem[lo] <= pivot)){ lo++; } _elem[hi] = _elem[lo]; } _elem[lo] = pivot; return lo; } public void swap(int a,int b){ int c = a; a = b; b = c; }
例項
改進
考查所有(或幾乎所有)元素均重複的退化情況。partition()演算法的版本A對此類輸入的處理完全等效於此前所舉的最壞情況。事實上對於此類向量,主迴圈內部前一子迴圈的條件中“pivot <= elem[hi]”形同虛設,故該子迴圈將持續執行,直至“lo < hi”不再滿足
/**
* 將交替地將右(左)側元素轉移至左(右)側,並最終恰好將軸點置於正中央的位置。
* 這就意味著,退化的輸入向量能夠始終被均衡的切分,如此反而轉為最好情況,
*/
public int partition2(int lo,int hi){
swap(_elem[lo],_elem[(int) (lo + Math.random()*(100)%(hi - lo + 1))]);
int pivot = _elem[lo];
while (lo < hi){
while ((lo < hi)){
if (pivot < _elem[hi]){
hi--;
} else {
_elem[lo++] = _elem[hi];
break;
}
}
while ((lo < hi)){
if (pivot < _elem[hi]){
lo++;
} else {
_elem[hi--] = _elem[lo];
break;
}
}
}
_elem[lo] = pivot;
return lo;
}
選取與中位數
眾數
- 原理
- 實現
package com.atguigu.sort;
import org.junit.Test;
/**
* @anthor shkstart
* @create 2020-08-16 20:39
*/
public class allFind {
@Test
public void test() {
int[] array = {135,242,192,93,345,11,24,19,19,19,19,19};
majority(array,0);
}
public int[] _elem = new int[8];
public Boolean majority(int[] A,int maj){
maj = majEleCandidate(A);
System.out.println(maj);
return majEleCheck(A,maj);
}
public Boolean majEleCheck(int[] A,int maj){
int occurrence = 0;
for (int i = 0;i < A.length;i++){
if (A[i] == maj) occurrence++;
}
return (2*occurrence > A.length);
}
public int majEleCandidate(int[] A){
int maj = 0;
for (int c = 0,i = 0; i < A.length;i++){
if (0 == c){
maj = A[i];
c = 1;
} else {
if (maj == A[i]) {
c++;
} else {
c--;
}
}
}
return maj;
}
}
中位數
蠻力演算法
/**
* 蠻力演算法
* @param s1
* @param lo1
* @param n1
* @param s2
* @param lo2
* @param n2
* @return
*/
public int trivialMedian(int[] s1,int lo1,int n1,int[] s2,int lo2,int n2){
int hi1 = lo1 + n1,hi2 = lo2 + n2;
Vector s = new Vector();
while ((lo1 < hi1) && (lo2 < hi2)){
while ((lo1 < hi1) && (s1[lo1] <= s2[lo2])) s.add(s1[lo1++]);
if (lo1 == 8) break;
//以免出現越界,可能換成向量不用寫這個
while ((lo2 < hi2) && (s2[lo2] <= s1[lo1])) s.add(s2[lo2++]);
}
while (lo1 < hi1) s.add(s1[lo1++]);
while (lo2 < hi2) s.add(s2[lo2++]);
return (int) s.elementAt((n1 + n2 +1)/2);
}
兩個都為n
/**
* 子串的長度相同
* @param s1
* @param lo1
* @param s2
* @param lo2
* @param n
* @return
*/
public int median1(int[] s1,int lo1,int[] s2,int lo2,int n){
if (n < 3) return trivialMedian(s1,lo1,n,s2,lo2,n);
int mi1 = lo1 + n / 2,mi2 = lo2 + (n - 1)/2;
if (s1[mi1] < s2[mi2]){
return median1(s1,mi1,s2,lo2,n + lo1 - mi1);
} else if (s1[mi1] > s2[mi2]){
return median1(s1,lo1,s2,mi2,n + lo2 -mi2);
} else {
return s1[mi1];
}
}
一般情況
public int median2(int[] s1,int lo1,int n1,int[] s2,int lo2,int n2){
if (n1 > n2) return median2(s2,lo2,n2,s1,lo1,n1);
//因為下面僅針對n1 <= n2的情況在討論
if (n2 < 6) //蠻力演算法
return trivialMedian(s1,lo1,n1,s2,lo2,n2);
if(2 * n1 < n2){
//若兩個向量的長度相差懸殊,則長者(S2)的兩翼可直接截除
return median2(s1,lo1,n1,s2,lo2 + (n2 - n1 -1)/2,n1 + 2 - (n2 -n1)%2);
//這裡怎麼取兩邊的可以看一下
}
int mi1 = lo1 + n1/2;
int mi2a = lo2 + (n1 - 1)/2;
//這裡其實是保證擷取的左右平衡
int mi2b = lo2 + n2 -1 - n1/2;
if (s1[mi1] > s2[mi2b]){
return median2(s1,lo1,n1/2 + 1,s2,mi2a,n2 - (n1 - 1)/2);
} else if (s1[mi1] < s2[mi2a]){
return median2(s1,mi1,(n1+1)/2,s2,lo2,n2 - n1/2);
} else {
return median2(s1,lo1,n1,s2,mi2a,n2 - (n1 - 1)/2 *2);
}
}
總的
package com.atguigu.sort;
import org.junit.Test;
import java.util.Vector;
/**
* @anthor shkstart
* @create 2020-08-16 21:07
*/
public class middleFind {
@Test
public void test(){
int[] array1 = {1,2,3,4,5,6,7,8};
int[] array2 = {9,10,11,12,13,14,15,16};
System.out.println(trivialMedian(array1,0,8,array2,0,8));
}
/**
* 蠻力演算法
* @param s1
* @param lo1
* @param n1
* @param s2
* @param lo2
* @param n2
* @return
*/
public int trivialMedian(int[] s1,int lo1,int n1,int[] s2,int lo2,int n2){
int hi1 = lo1 + n1,hi2 = lo2 + n2;
Vector s = new Vector();
while ((lo1 < hi1) && (lo2 < hi2)){
while ((lo1 < hi1) && (s1[lo1] <= s2[lo2])) s.add(s1[lo1++]);
if (lo1 == 8) break;
//以免出現越界,可能換成向量不用寫這個
while ((lo2 < hi2) && (s2[lo2] <= s1[lo1])) s.add(s2[lo2++]);
}
while (lo1 < hi1) s.add(s1[lo1++]);
while (lo2 < hi2) s.add(s2[lo2++]);
return (int) s.elementAt((n1 + n2 +1)/2);
}
/**
* 子串的長度相同
* @param s1
* @param lo1
* @param s2
* @param lo2
* @param n
* @return
*/
public int median1(int[] s1,int lo1,int[] s2,int lo2,int n){
if (n < 3) return trivialMedian(s1,lo1,n,s2,lo2,n);
int mi1 = lo1 + n / 2,mi2 = lo2 + (n - 1)/2;
if (s1[mi1] < s2[mi2]){
return median1(s1,mi1,s2,lo2,n + lo1 - mi1);
} else if (s1[mi1] > s2[mi2]){
return median1(s1,lo1,s2,mi2,n + lo2 -mi2);
} else {
return s1[mi1];
}
}
public int median2(int[] s1,int lo1,int n1,int[] s2,int lo2,int n2){
if (n1 > n2) return median2(s2,lo2,n2,s1,lo1,n1);
//因為下面僅針對n1 <= n2的情況在討論
if (n2 < 6) //蠻力演算法
return trivialMedian(s1,lo1,n1,s2,lo2,n2);
if(2 * n1 < n2){
//若兩個向量的長度相差懸殊,則長者(S2)的兩翼可直接截除
return median2(s1,lo1,n1,s2,lo2 + (n2 - n1 -1)/2,n1 + 2 - (n2 -n1)%2);
//這裡怎麼取兩邊的可以看一下
}
int mi1 = lo1 + n1/2;
int mi2a = lo2 + (n1 - 1)/2;
//這裡其實是保證擷取的左右平衡
int mi2b = lo2 + n2 -1 - n1/2;
if (s1[mi1] > s2[mi2b]){
return median2(s1,lo1,n1/2 + 1,s2,mi2a,n2 - (n1 - 1)/2);
} else if (s1[mi1] < s2[mi2a]){
return median2(s1,mi1,(n1+1)/2,s2,lo2,n2 - n1/2);
} else {
return median2(s1,lo1,n1,s2,mi2a,n2 - (n1 - 1)/2 *2);
}
}
}
基於優先順序佇列
第一種演算法如圖(a1)所示。首先,花費O(n)時間將全體元素組織為一個小頂堆;然後,經
過k次delMin()操作,則如圖(a2)所示得到位序為k的元素
@Test
public void test1() {
Integer[] array = {135,242,192,93,345,11,24,19,19,19,19,19};
PQ_CompHeap pq = new PQ_CompHeap(array,4);
for (int i = 0;i < array.length;i++){
pq.insert(array[array.length - 4 -1 + i]);
pq.delMax();
}
System.out.println(pq.getMax());
}
任取k個元素,並在O(k)時間以內將其組織為大頂堆。然後將剩餘的n - k個元素逐個插入堆中;每插入一個,隨即刪除堆頂,以使堆的規模恢復為k。待所有元素處理完畢之後,堆頂即為目標元素
@Test
public void test2() {
Integer[] array = {135,242,192,93,345,11,24,19,19,19,19,19};
PQ_CompHeap pq = new PQ_CompHeap(array,array.length);
for (int i = 0;i < 6;i++){
pq.delMin();
}
System.out.println(pq.getMin());
}
首先將全體元素分為兩組,分別構建一個規模為n - k的小頂堆G和一個規模為k的大頂堆H。接下來,反覆比較它們的堆頂g和h,只要g < h,則將二者交換,並重新調整兩個堆。如此,G的堆頂g將持續增大,H的堆頂h將持續減小。當g>=h時,h即為所要找的元素
@Test
public void test3() {
Integer[] array1 = {135,242,192,93,345,11,24,19,19,19,19,19};
Integer[] array2 = new Integer[array1.length - 4];
for (int i = 4;i < array1.length;i++){
array2[i-4] = array1[i];
}
PQ_CompHeap pq1 = new PQ_CompHeap(array2,array1.length - 4);
PQ_CompHeap pq2 = new PQ_CompHeap(array1,4);
while (pq1.getMin() < pq2.getMax()){
swap(pq1.getMin(),pq2.getMax());
}
System.out.println(pq2.getMax());
}
public void swap(Integer a,Integer b){
Integer c = a;
a = b;
b = c;
}
基於快速劃分
呼叫演算法partition()構造向量A的一個軸點A[i] = x。若i =k,則該軸點恰好就是待選取的目標元素,即可直接將其返回;沒有的話,相應減去不可能包含的部分如L段、G段
K-選取演算法
package com.atguigu.sort;
import org.junit.Test;
import java.util.Vector;
/**
* @anthor shkstart
* @create 2020-08-18 8:34
*/
public class K_Find {
@Test
public void test(){
int[] array = {135,242,192,93,345,11,24,19};
}
public static int Q = 5;
public int select(Vector A, int k){
if (A.size() < Q){
return trivialSelect(A,k);
}
Vector B = new Vector();
int[] C = new int[Q];
for (int i = 1;i <= A.size()/Q;i++){
for (int j = (i-1) * Q;j < Q*i;j++){
C[j - (i-1) * Q] =(int) A.elementAt((i-1) * Q);
}
sort(C);
B.add(meddile(C));
}
int m = select(B,C.length/2);
int i = 0;
Vector l = new Vector();
int e = 0;
Vector g = new Vector();
while (i < A.size()){
if ((int)A.elementAt(i) < m){
l.add((int)A.elementAt(i));
} else if((int)A.elementAt(i) == m){
e++;
} else {
g.add((int)A.elementAt(i));
}
i++;
}
if (l.size() >= k) {
return select(l,k);
} else if (l.size() + e >= k){
return m;
} else {
return select(g,k - l.size() - e);
}
}
public int trivialSelect(Vector A,int k){
return 0;
}
public void sort(int[] A){
}
public int meddile(int[] A){
return 0;
}
}
希爾排序
原理
實現(感覺很難按上面的方式寫,先複製了一個,以後改)
package sortdemo;
import java.util.Arrays;
/**
* Created by chengxiao on 2016/11/24.
*/
public class ShellSort {
public static void main(String []args){
int []arr ={1,4,2,7,9,8,3,6};
sort(arr);
System.out.println(Arrays.toString(arr));
int []arr1 ={1,4,2,7,9,8,3,6};
sort1(arr1);
System.out.println(Arrays.toString(arr1));
}
/**
* 希爾排序 針對有序序列在插入時採用交換法
* @param arr
*/
public static void sort(int []arr){
//增量gap,並逐步縮小增量
for (int gap=arr.length/2;gap>0;gap/=2){
//從第gap個元素,逐個對其所在組進行直接插入排序操作
for (int i=gap;i<arr.length;i++){
int j = i;
while(j-gap>=0 && arr[j]<arr[j-gap]){
//插入排序採用交換法
swap(arr,j,j-gap);
j-=gap;
}
}
}
}
/**
* 希爾排序 針對有序序列在插入時採用移動法。
* @param arr
*/
public static void sort1(int []arr){
//增量gap,並逐步縮小增量
for (int gap=arr.length/2;gap>0;gap/=2){
//從第gap個元素,逐個對其所在組進行直接插入排序操作
for (int i=gap;i<arr.length;i++){
int j = i;
int temp = arr[j];
if(arr[j]<arr[j-gap]){
while(j-gap>=0 && temp<arr[j-gap]){
//移動法
arr[j] = arr[j-gap];
j-=gap;
}
arr[j] = temp;
}
}
}
}
/**
* 交換陣列元素
* @param arr
* @param a
* @param b
*/
public static void swap(int []arr,int a,int b){
arr[a] = arr[a]+arr[b];
arr[b] = arr[a]-arr[b];
arr[a] = arr[a]-arr[b];
}
}
改進