專題——排序

排序

一.插入排序

1.排序原理
將新的元素重複的插入到已經排好序的子線性表中,直到線性表全部排好序

2.具體步驟
將list[i]插入到已經排好序的list[0]~list[i-1]中,首先需要設置一個臨時變量currentElement,然後從i-1向前進行比較,如果list[i-1]>currentElement,那麼list[i]=list[i-1];如果list[i-2]>current,那麼list[i-1]=list[i-2],依次類推,直到list[k]<=currentElement或者k<0停止,最後再將currentElement賦值給list[k+1]。

3.圖解
例如排序 2,9,5,4,8,1,6
在這裏插入圖片描述
4.代碼實現
實現方法可能有很多種,這裏就列出一種(^ v ^)

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] a = { 2, 9, 5, 4, 8, 1, 6 };
		for (int i = 1; i < a.length; i++) {
			int current = a[i];//設置臨時變量爲a[i]
			int k;
			for (k = i - 1; k >= 0 && a[k] > current; k--) {//從i-1開始往前比較
				a[k + 1] = a[k];
			}
			a[k + 1] = current;//比較完成後將臨時變量插入到a[k+1]的位置
		}
		for (int i : a) {
			System.out.print(i + " ");//1 2 4 5 6 8 9 
		}
	}

5.時間複雜度
這裏給出的插入排序是重複的將一個新元素插入到一個已經排好序的部分數組中,在進行第k次迭代時,要將元素插入到長度爲k的數組中,需要進行k次比較,以及k次的移動插入元素,設T(n)爲插入排序的時間複雜度,c表示諸如迭代中的賦值和額外的比較的操作總數
T(n)=(2+c)+(2x2+c)+(2x3+c)+……+(2x(n-1)+c)
=2(1+2+3+……+n-1)+(n-1)*c
=n(n-1)+cn-c
=O(n^2)

二.冒泡排序

1.排序原理
多次遍歷數組,每次遍歷連續比較相鄰元素,如果沒有按照規定順序排序,交換位置

2.具體步驟
遍歷數組並通過比較和交換將最大的值放到數組的最後一個位置,每一次遍歷都可以比較出最大的數,那麼比較list.length-1次就可以排好序了

3.圖解
例如排序 2,9,5,4,8,1,6
在這裏插入圖片描述
4.代碼實現

注意

  • 如果在第k次遍歷沒有進行交換,那麼證明數組已經排好序了,不用再進行第k+1及以後的比較
  • 最後 i 位數肯定是最大的,所以不用與最後 i 位數比較
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] a = { 2, 9, 5, 4, 8, 1, 6 };
		boolean b = true;//判斷是否進行了位置交換,初始化爲需要
		for (int i = 1; i < a.length && b; i++) {//只有上一次進行了位置交換才進行下一次遍歷
			b = false;//開關
			for (int j = 0; j < a.length - i; j++) {//比較a.length-i之前的數組,因爲最後的i位都是已經排好序的
				if (a[j] > a[j + 1]) {
					int temp = a[j];
					a[j] = a[j + 1];
					a[j + 1] = temp;
					b = true;//表明進行了位置交換
				}
			}
		}
		for (int i : a) {
			System.out.print(i + " ");
		}
	}

5.時間複雜度

最好的情況下需要比較n-1次,時間複雜度爲O(n)

最壞的情況下:
第一次循環需要比較n-1次
第二次 n-2次
第n-1次 1次
T(n)=(n-1+n-2+n-3+……+1)
=n*(n-1)/2
=O(n^2)

三.歸併排序

1.排序原理

講一個數組分爲兩部分,並且遞歸的調用歸併排序,直到兩部分都已經排好序了,再將他們合併到一起

2.具體步驟

先講一個數組分爲兩部分,list(1)和list(2),再將list(1)分爲list(1)(1)和list(1)(2)直到list(1)(1)不能分了爲止,然後排序好list(1)(1),再排序好list(2)(2),然後再歸併回list(1),list(2)用此相同操作,最後再將list(1)和list(2)歸併得到排好序的list。

3.圖解

在這裏插入圖片描述
4.代碼實現

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] list = { 2, 9, 5, 4, 8, 1, 7, 6 };
		devide(list);// 歸併排序
		for (int i : list) {// 循環輸出
			System.out.print(i + " ");
		}
	}

	public static void devide(int[] list) {
		if (list.length > 1) {
			int[] left = new int[list.length / 2];
			System.arraycopy(list, 0, left, 0, list.length / 2);
			devide(left);// 遞歸直到數組就剩一個元素,肯定是有序的

			int reaminLength = list.length - list.length / 2;
			int[] right = new int[reaminLength];
			System.arraycopy(list, list.length / 2, right, 0, reaminLength);
			devide(right);// 遞歸另一部分,直到數組只剩一個元素
			sort(left, right, list);// 排序併合並剛纔的兩部分數組
		}
	}

	public static void sort(int[] left, int[] right, int[] list) {
		int currentLeft = 0;// 左邊數組索引
		int currentRight = 0;// 右邊數組索引
		int currentList = 0;// 臨時數組索引
		while (currentLeft < left.length && currentRight < right.length) {// 如果兩個數組中元素都沒有耗盡
			if (left[currentLeft] < right[currentRight]) {// 從左邊數組第一個開始和右邊數組第一個進行比較
				list[currentList++] = left[currentLeft++];// 如果左邊的小於右邊的,則將左邊數組的這個元素放入到臨時數組中,索引同時向後移
			} else {
				list[currentList++] = right[currentRight++];// 同上
			}
		}
		while (currentLeft < left.length) {// 這裏比較的是如果上方while循環提前將right耗盡了,則將left中剩餘元素加到list後面
			list[currentList++] = left[currentLeft++];
		}
		while (currentRight < right.length) {// 同上
			list[currentList++] = right[currentRight++];
		}

	}

5.時間複雜度
排序兩端的數組需要的時間複雜度是T(n/2)+T(n/2),要歸併兩個子數組,需要進行n-1次的排序,然後在進行n次將元素放入到臨時數組中
T(n)=T(n/2)+T(n/2)+(n-1)+n
=O(n*logn)

四.快速排序

1.排序原理

設置一個主元素,將數組分爲兩部分,進行遞歸快速排序,使得第一部分的數全部小於主元素,第二部分的數全部大於主元素,直到數組全部排好

2.具體步驟
設置第一個數爲主元素,設置一個左指針和一個右指針,如果右指針所對應的元素小於主元素,那麼就將此元素放入到左指針所對應的元素上,左指針向後移,此時開始從左指針向右移動,像這樣循環往復,最後將主元素插入到指針結束移動的位置

3.圖解:
這是引用《java語言程序設計——進階篇》中的一幅圖:
在這裏插入圖片描述
4.代碼

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] a = { 2, 9, 5, 4, 8, 1, 6, 7 };
		quickSort(a, 0, a.length - 1);
		for (int i : a) {
			System.out.print(i + " ");
		}
	}

	public static void quickSort(int[] a, int low, int high) {
		if (low > high) {
			return;
		}
		int leftIndex = low;// 設置左邊索引從low開始向右移動
		int rightIndex = high;// 設置右邊索引從high開始向左移動
		boolean b = true;// 設置默認從右邊開始比較
		int mainElement = a[low];// 設置主元素:該數組中的第一個元素,無論是大數組還是小數組
		L: while (leftIndex < rightIndex) {// 在左邊索引小於右邊索引的時候進行循環
			if (b) {// 判斷索引從右邊開始向左移動
				for (int i = rightIndex; i > leftIndex; i--) {// 索引從右邊開始向左移動
					if (a[i] < mainElement) {// 如果i位置元素<主元素
						a[leftIndex++] = a[i];// 左邊索引所對應的位置賦爲a[i]
						b = false;// 改爲從左邊向右比較
						continue L;// 直接進行while循環,就不會執行下面的右邊索引等於左邊索引
					} else {
						rightIndex--;// 如果大於則將右邊索引向左移動
					}
				}
				rightIndex = leftIndex;// 全部遍歷之後沒有一個元素比主元素小,那麼說明排序是正確的,就將右索引值賦給左索引
			} else {// 判斷索引從左邊開始向右移動 下方代碼與上方類似,只不過方向相反
				for (int j = leftIndex; j < rightIndex; j++) {
					if (a[j] > mainElement) {
						a[rightIndex--] = a[j];
						b = true;
						continue L;
					} else {
						leftIndex++;
					}
				}
				leftIndex = rightIndex;
			}
		}
		a[leftIndex] = mainElement;// 全部排好後,將主元素插入到索引leftIndex或者rightIndex中,因爲兩個索引相同
		quickSort(a, low, leftIndex - 1);// 遞歸排序左索引之前的子數組
		quickSort(a, leftIndex + 1, high);// 遞歸排序左索引之後的子數組
	}

5.時間複雜度

在最差的情況下,劃分由n個元素組成的數組需要進行n次比較和n次移動,因此劃分時間需要O(n)。
在最差的情況下,每次主元素會將數組劃分爲一個大數組和一個空數組,這個大的子數組在規模上會比上一次劃分少1,該算法需要(n-1)+(n-2)+……+1=O(n^2)
在最好的情況下,每次主元素將數組劃分爲規模差不多相等的子數組
T(n)=T(n/2)+=T(n/2)+n=O(n*logn)

五.堆排序

1.排序原理

創建一個Heap對象,使用add方法將元素添加到堆中,然後使用remove方法降序刪除元素

2.具體步驟
首先創建一個Heap類,然後創建Heap對象,將元素添加到堆中,再降序刪除元素

3.圖解
這裏的堆所指的是二叉堆,二叉堆具備以下兩個條件:

  1. 形狀屬性:它必須是一顆完全二叉樹
  2. 堆屬性:每個節點大於等於他的任意一個孩子

而完全二叉樹的概念是:一棵二叉樹的每一層都是滿的,或者最後一層可以不填滿並且最後一層的葉子都是向左放置的

下面這個圖就是一個二叉堆:
在這裏插入圖片描述
注意
在數組中存儲爲[39,32,33,16,24,27,30]
不難發現,
左孩子的索引是父索引乘2+1;
右孩子的索引是父索引乘2+2;

下面這個圖雖然是二叉樹,但是不是一個二叉堆:
在這裏插入圖片描述
4.代碼實現

  1. 首先創建一個堆類
public class Heap<E extends Comparable<E>> {
	private ArrayList<E> list = new ArrayList<>();

	public Heap() {
	}

	public Heap(E newObject) {
		add(newObject);// 執行add方法
	}

	public void add(E newObject) {
		list.add(newObject);// 將對象加入到list中
		int currentIndex = list.size() - 1;// 設置當前索引爲最後一個元素的下標,即剛纔插入的元素的下標
		while (currentIndex > 0) {// 只要當前索引值值大於0,就繼續循環
			int parentIndex = (currentIndex - 1) / 2;// 獲得父節點索引
			if (list.get(currentIndex).compareTo(list.get(parentIndex)) > 0) {// 如果子孩子的值大於父節點的值
				E tempObject = list.get(currentIndex);// 交換孩子節點和父節點的位置
				list.set(currentIndex, list.get(parentIndex));
				list.set(parentIndex, tempObject);
			} else
				break;// 跳出循環
			currentIndex = parentIndex;// 將父節點索引賦給孩子節點
		}

	}

	public E remove() {
		if (list.size() == 0) {// 如果數組列表長度爲零,返回null
			return null;
		}
        E removeElement=list.get(0);//刪除最大的元素,並最終返回,接下來的步驟是將此二叉樹重新整理爲二叉堆
        list.set(0, list.get(list.size()-1));//將最後一個元素放到根節點的位置
        list.remove(list.size()-1);//移除最後一個節點
        int currentIndex=0;//從第一個元素開始向下
        while(currentIndex<list.size()) {//如果當前索引<數組列表的長度
        	int leftIndex=currentIndex*2+1;//左孩子索引
        	int rightIndex=currentIndex*2+2;//右孩子索引
        	if(leftIndex>=list.size()) {break;}//如果左孩子索引大於列表長度
        	int maxIndex=leftIndex;//設置最大索引爲左孩子索引
        	if(rightIndex<list.size()) {
        		if(list.get(maxIndex).compareTo(list.get(rightIndex))<0) {//如果左孩子索引大於右孩子索引
        			maxIndex=rightIndex;//右索引賦給最大索引
        		}
        	}
        	if(list.get(currentIndex).compareTo(list.get(maxIndex))<0) {//如果當前元素小於最大值索引
        		E tempObject=list.get(currentIndex);//交換元素
        		list.set(currentIndex, list.get(maxIndex));
        		list.set(maxIndex, tempObject);
        		currentIndex=maxIndex;//最大索引賦給當前索引
        	}
        	else break;
        	
        }
        return removeElement;
	}

	public int getSize() {//返回heap大小
		return list.size();
	}
}
  1. 測試堆排序
	public static void main(String[] args) {
		// TODO Auto-generated method stub
    Integer[] list= {-44,-5,-3,3,3,1,-4,0,1,2,4,5,53};
    heapSort(list);//堆排序
    for(int i:list) {//打印
    	System.out.print(i+" ");
    }
	}
	public static <E extends Comparable<E>> void heapSort(E[] list) {
		Heap<E> heap=new Heap<>();
		for(int i=0;i<list.length;i++) {
			heap.add(list[i]);//將列表中元素添加到堆中
		}
		for(int i=list.length-1;i>=0;i--) {
			list[i]=heap.remove();//將堆中元素取出放入list中
		}
	}

5.時間複雜度

由於堆是一個完全二叉樹所以第一層有一個節點,第二層有兩個節點,第n層有2^(n-1)個節點
1+2+……+2^(n-2)<h
h<1+2+……+2^(n-1)
所以堆的高度爲O(logn)
向堆中添加一個元素最多需要n步,有n個元素的數組的初始堆需要O(nlogn)時間

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章