數據結構第七章排序 排序(九種)(可以比較速度)

包括:

插入排序:直接插入排序,折半插入排序,希爾排序

交換排序:冒泡排序,快速排序

選擇排序:簡單選擇排序

其它排序:歸併排序,堆排序,桶排序

查詢“排序算法動圖”更好理解

import java.util.*;

public class Main
{
	public static void main(String[] args)
	{
		//獲取數字
		Scanner s1=new Scanner(System.in);
		System.out.println("請輸入隨機數列的數的個數,程序自動構建隨機數列(0-10000範圍內):");	
		int n=s1.nextInt();
		int[] a=randomArray(n);
		
		//插入排序
		{
			System.out.println("插入排序");	
			//直接插入排序用時和結果
			//straightInsertionSort(a);
		
			//折半插入排序用時和結果
			//binarySort(a);
		 
			//希爾排序用時和結果
			shellSort(a);
		}
		
		//交換排序
		{
			System.out.println("交換排序");	
			//冒泡排序用時和結果
			//bubbleSort(a);
		 
			//快速排序用時和結果
			quickSort(a);
		}
		
		//選擇排序
		{
			System.out.println("選擇排序");	
			//簡單選擇排序
			//selectSort(a);
		}
		
		//其它排序方式
		{
			System.out.println("其它排序方式");	
			//歸併排序
			mergeSort(a);
			
			//堆排序
			heapSort(a);
			
			//桶排序
			bucketSort(a);
		}
	}
	
	//構建給定位數隨機數
	public static int[] randomArray(int n)
	{
		int[] a=new int[n];
		for(int i=0;i<a.length;i++)
		{
			a[i]=(int)(Math.random()*10000);
		}
		//System.out.println("隨機數列"+Arrays.toString(a));
		return a;
	}
	
	
	
	//直接插入排序
	public static void straightInsertionSort(int[] a)
	{ 
		int[] b=Arrays.copyOf(a, a.length);//重新建一個數組,否則如果改了傳進來的數組,後面的排序用的數組就是排好的了
		long t1,t2;//引入時間
		t1 = System.currentTimeMillis();//計時器
		
		//功能段
		{
			int i=0,j=0;
			int c=0;//臨時存放元素
			for (i=1;i<b.length;i++)
			{
				if(b[i]<b[i-1])//小於纔要動,大於是不用動的,
				{
					c=b[i];
					for(j=i-1;(j>=0)&&(c<b[j]);j--)//我的b[0]放了東西的,沒有放哨兵,所以可能會有j<0導致數組下標越界,爲放這種情況就j<0就跳出,而且這個判斷要放前面來短路後面的c<b[j],否則j=-1時調用了判斷語句也可能超限。我覺得我這樣寫其實不是太好。
					{
						b[j+1]=b[j];
					}
					b[j+1]=c;
				}
			}
			
		}
		
		t2=System.currentTimeMillis();
        System.out.println("直接插入排序用時"+(t2-t1)+"ms");//輸出用時
        //System.out.println("排序結果"+Arrays.toString(b));
		return ;
	}
	
	//折半插入排序
	public static void binarySort(int[] a)
	{ 
		int[] b=Arrays.copyOf(a, a.length);//重新建一個數組,否則如果改了傳進來的數組,後面的排序用的數組就是排好的了
		long t1,t2;//引入時間
		t1 = System.currentTimeMillis();//計時器
			
		//功能段
		{
			int i=0,j=0;
			int c=0;//臨時存放元素
			int low,high,mid;
			for (i=1;i<b.length;i++)
			{
				c=b[i];
				low=0;
				high=i-1;
				
				//折半查找
				while(low<=high)
				{
					mid=(low+high)/2;
					if(b[mid]>c)
					{
						high=mid-1;
					}
					else low=mid+1;
				}
				//和直接插入一樣後移元素
				//由於跳出情況是high<low,這個時候high+1就是該放的位置
				for(j=i-1;j>=high+1;j--)
					b[j+1]=b[j];
				b[high+1]=c;
			}		
		}
			
		t2=System.currentTimeMillis();
	    System.out.println("折半插入排序用時"+(t2-t1)+"ms");//輸出用時
	    //System.out.println("排序結果"+Arrays.toString(b));
	    return ;
	}
	
	
	//希爾排序:難
	public static void shellSort(int[] a)
	{ 
		int[] b=Arrays.copyOf(a, a.length);//重新建一個數組,否則如果改了傳進來的數組,後面的排序用的數組就是排好的了
		long t1,t2;//引入時間
		t1 = System.currentTimeMillis();//計時器
				
		//功能段
		{
			int i=0,j=0;
			int c=0;//臨時存放元素
				
			//設置步長,每次循環內兩個觀察點的距離
			int bc=0;
				
			for (bc=b.length/2;bc>=1;bc=bc/2)//步長每個循環整除2
			{
				for(i=bc;i<b.length;i++)//你或許會奇怪爲什麼這裏不是兩個循環,一個就夠了,這是因爲在bc相同的情況下,每個元素的處理代碼是一樣的,也就是說只要掃到每一個數就行了(下面的處理代碼是一樣的),一個for循環即可
												//i從bc開始算是因爲,根據插入排序是和從第二個數開始循環和第一個數比
				{
					//
					if(b[i]<b[i-bc])
					{
						c=b[i];					
						for(j=i-bc;(j>=0)&&(c<b[j]);j=j-bc)
						{
							b[j+bc]=b[j];
						}
					b[j+bc]=c;
					}
				}

			}
		}		
		t2=System.currentTimeMillis();
		System.out.println("希爾插入排序用時"+(t2-t1)+"ms");//輸出用時
		//System.out.println("排序結果"+Arrays.toString(b));
		return;
	}
	
	
	//冒泡排序
	public static void bubbleSort(int[] a)
	{ 
		int[] b=Arrays.copyOf(a, a.length);//重新建一個數組,否則如果改了傳進來的數組,後面的排序用的數組就是排好的了
		long t1,t2;//引入時間
		t1 = System.currentTimeMillis();//計時器
				
		//功能段
		{
			int i=0,j=0;
			int c;
			for(i=0;i<b.length;i++)
			{
				for(j=b.length-1;j>i;j--)
				{
					if(b[j]>b[j-1])
					{
						c=b[j-1];
						b[j-1]=b[j];
						b[j]=c;
					}
				}
			}
		}		
		t2=System.currentTimeMillis();
		System.out.println("冒泡排序用時"+(t2-t1)+"ms");//輸出用時
		//System.out.println("排序結果"+Arrays.toString(b));
		return;
	}
	
	//快速排序:難
	public static void quickSort(int[] a)
	{ 
		int[] b=Arrays.copyOf(a, a.length);//重新建一個數組,否則如果改了傳進來的數組,後面的排序用的數組就是排好的了
		long t1,t2;//引入時間
		t1 = System.currentTimeMillis();//計時器
				
		//功能段
		{
			QuickSort(b,0,b.length-1);
		}		
		t2=System.currentTimeMillis();
		System.out.println("快速排序用時"+(t2-t1)+"ms");//輸出用時
		//System.out.println("排序結果"+Arrays.toString(b));
		return;
	}
	
	public static void QuickSort(int[] A,int low,int high)//遞歸調用自己
	{
		if(low<high)
		{
			int zhongXin=huaFen(A,low,high);
			QuickSort(A,low,zhongXin-1);
			QuickSort(A,zhongXin+1,high);
		}
	}
	
	public static int huaFen(int[] A,int low,int high)//每一次的處理過程
	{
		int jiZun=A[low];
		while(low<high)
		{
			while(low<high&&A[high]>=jiZun){high--;}
			A[low]=A[high];
			while(low<high&&A[low]<=jiZun){low++;}
			A[high]=A[low];						
		}
		A[low]=jiZun;
		return low;
	}

	//簡單選擇排序
	public static void selectSort(int[] a)
	{ 
		int[] b=Arrays.copyOf(a, a.length);//重新建一個數組,否則如果改了傳進來的數組,後面的排序用的數組就是排好的了
		long t1,t2;//引入時間
		t1 = System.currentTimeMillis();//計時器
				
		//功能段
		{
			//其實我感覺比冒泡還簡單,每掃一遍把最小的數拿出來放前面
			int i,j;
			int min;
			int c;//用於交換的
			for (i=0;i<b.length;i++)
			{
				min=i;
				for(j=i+1;j<b.length;j++)
				{
					if(b[j]<b[min]) {min=j;}
				}
				if(min!=i)
				{
					c=b[i];
					b[i]=b[min];
					b[min]=c;
				}
			}
		}		
		t2=System.currentTimeMillis();
		System.out.println("簡單選擇排序用時"+(t2-t1)+"ms");//輸出用時
		//System.out.println("排序結果"+Arrays.toString(b));
		return;
	}
	
	//歸併排序,也要迭代,但是最壞和最好情況一樣,這一點比快排好
	public static void mergeSort(int[] a)
	{ 
		int[] b=Arrays.copyOf(a, a.length);//重新建一個數組,否則如果改了傳進來的數組,後面的排序用的數組就是排好的了
		long t1,t2;//引入時間
		t1 = System.currentTimeMillis();//計時器
				
		//功能段
		{
			int[] c=new int[b.length];//構建輔助數組從頭構建是因爲,這樣的話從頭到尾只有一個輔助數組
			MergeSort(b,0,b.length-1,c);
		}		
		t2=System.currentTimeMillis();
		System.out.println("歸併排序用時"+(t2-t1)+"ms");//輸出用時
		//System.out.println("排序結果"+Arrays.toString(b));
		return;
	}
	
	public static void  MergeSort(int[] b,int low,int high,int[] c)//遞歸
	{
		if(low<high)//low=high的時候只有一個數就不用再向下歸併了
		{
			int mid=(low+high)/2;
			MergeSort(b,low,mid,c);
			MergeSort(b,mid+1,high,c);
			Merge(b,low,mid,high,c);
		}
	}
	
	public static void Merge(int[] b,int low,int mid,int high,int[] c)//每一次的處理過程
	{

		//先把原數組的數複製到輔助數組上,因爲等會要操作的是原數組
		for(int k=low;k<=high;k++)
		{
			c[k]=b[k];
		}
		
		//其實下面這段你在一元多項式加法乘法裏面就試過,不過沒有下面這麼簡潔
		int i,j;//分別控制第一個數列和第二個,由於我是二路歸併也可以理解爲前半後半
		int k;//控制原數組新情況個元素位置
		for(i=low,j=mid+1,k=i;i<=mid&&j<=high;k++)//誰小誰先進原數組,注意i和k別設成起始值爲0
		{
			if(c[i]<=c[j])//比較c中左右兩段的元素
			{
				b[k]=c[i];
				i++;
			}
			else
			{
				b[k]=c[j];
				j++;
			}
		}
		//如果有沒排進去的
			while(i<=mid)//若第一個表沒檢測完,複製
			{
				b[k]=c[i];
				k++;
				i++;
			}

			while(j<=high)//若第二個表沒檢測完,複製
			{
				b[k]=c[j];
				k++;
				j++;
			}
	}
	
	//堆排序
	public static void heapSort(int[] a)
	{ 
		int[] b=Arrays.copyOf(a, a.length);//重新建一個數組,否則如果改了傳進來的數組,後面的排序用的數組就是排好的了
		long t1,t2;//引入時間
		t1 = System.currentTimeMillis();//計時器
				
		//功能段
		{
			//需要先構建一個爲堆的類
			MaxHeap test1 = new MaxHeap(b);
			int bLength = b.length;
			for (int i=0; i < bLength; i++) {
				b[i]=test1.deleteMax();
			}
			
			
		}		
		t2=System.currentTimeMillis();
		System.out.println("堆排序用時"+(t2-t1)+"ms");//輸出用時
		//System.out.println("排序結果"+Arrays.toString(b));
		return;
	}
	
	//桶排序:4位數,這裏只是一個簡單的桶排序,實際情況裏桶裏面還會有排序
	public static void bucketSort(int[] a)
	{ 
		int[] b=Arrays.copyOf(a, a.length);//重新建一個數組,否則如果改了傳進來的數組,後面的排序用的數組就是排好的了
		long t1,t2;//引入時間
		t1 = System.currentTimeMillis();//計時器
				
		//功能段
		{
			int[] c=new int[10000];//數組c相當於創建了10000個桶
			int i;//數組的第i位的i
			int j;//數組c的第j位的j
			int k;//數組c的第j位的大小
			int m;
			//進桶
			for(i=0;i<b.length;i++)
			{
				m=b[i];
				c[m]++;
			}
			//導回數組b
			i=0;
			for(j=0;j<c.length;j++)
			{
				if(c[j]>0)
				{
					for(k=0;k<c[j];k++)
					{
						b[i]=j;
						i++;
					}
				}
			}
		}		
		t2=System.currentTimeMillis();
		System.out.println("桶排序用時"+(t2-t1)+"ms");//輸出用時
		//System.out.println("排序結果"+Arrays.toString(b));
		return;
	}
}

class MaxHeap {
	int maxSize = 10000000;//堆的最大大小
	int[] array = new int[maxSize+1];
	int nowSize = 0;//目前堆使用的部分的大小
	
	public MaxHeap() {
		array[0] = Integer.MAX_VALUE;
	}
	public MaxHeap(int[] inputArray) {
		int inputArrayLength = inputArray.length;
		nowSize =  inputArrayLength;
		array[0] = Integer.MAX_VALUE;
		for (int i = 1; i <= inputArrayLength; i++) {
			array[i] = inputArray[i-1];
		}
		buildMaxHeap();
	}
	
	public boolean insert (int data) {
		
		if (nowSize == maxSize) {
			System.out.println("最大堆已滿");
			return false;
		}
		nowSize++;
		int i = nowSize;
		for (;array[i/2] < data; i = i/2) {// 完全二叉樹 n/2是父節點
			array[i] = array[i/2];// 和插入排序的移動方式類似
		}
		array[i] = data;
		return true;
	}
	
	public int deleteMax() {
		if (nowSize == 0) {
			System.out.print("最大堆已爲空");
			return -100000;
		} else {
			int maxData = array[1];// 取出根節點最大值
			array[1] = array[nowSize];
			nowSize--;
			buildFromTreeRoot(1);
			return maxData;//這裏可以返回maxData
		}
	}   
	
	
	public void buildMaxHeap() {
		//先理子樹,子樹理完再理根,這樣時間複雜度只用O(n)
		for(int i = nowSize / 2; i > 0; i--) {
			buildFromTreeRoot(i);
		}	
	}
	
	//將根節點與兩邊比較和移動的函數
	public void buildFromTreeRoot(int p) {
		int temp = array[p];
		int Parent;
		int Child;
		for (Parent = p ; Parent * 2 <= nowSize ; Parent = Child) {
			Child = Parent * 2;
			/* 開始循環說了Parent * 2 <= nowSize,且Child = Parent * 2且Child != nowSize時,那麼可以肯定Child+1<=nowSize。
			 * 那麼就可以比較Child和Child++的大小了,反之如果且Child == nowSize,那麼Child==nowSize那麼自然後面的不用考慮。
			 */
			if ((Child != nowSize) && array[Child] < array[Child + 1]) { 
				Child++;//Child指向左右子節點中的最大者
			}
					
			if ( temp >= array[Child] ) {
				break;
			} else { 
				array[Parent] = array[Child];
			}
		}
		//跳出時沒有子結點,或者比子結點大可以跳出了
		array[Parent] = temp;
	}
	
	/**
	 * 層序遍歷
	 */
	public void printMaxHeap() {//打印數組相當於層序遍歷
		System.out.print("{");
		int i;
		for(i = 1; i <= nowSize; i++) {
			if (i != 1) {
				System.out.print(",");
			}
			System.out.print(array[i]);
		}
		System.out.print("}");
	}
}

 

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