1. 程式人生 > 實用技巧 >資料結構與演算法-排序

資料結構與演算法-排序

排序(不全以後補)

基數排序

原理:
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];
	}
}

改進