选择排序、冒泡排序、归并排序、快速排序、插入排序的算法原理与比较

一、选择排序

(1)算法分析

1、从第一个元素开始,分别与后面的元素向比较,找到最小的元素与第一个元素交换位置;

2、从第二个元素开始,分别与后面的元素相比较,找到剩余元素中最小的元素,与第二个元素交换;

3、重复上述步骤,直到所有的元素都排成由小到大为止。

(2)算法实现

/*
 * 选择排序
 * 需要比较n(n-1)/2次;
 * 选择排序的算法复杂度仍为O(n*n);
 */
public static void ChooseSort(int[] a) {
	int len = a.length;
	for (int x = 0; x < len; x++) {//依次取出第一个元素
		for (int y = x + 1; y < len; y++) {//从x+1开始,依次取出第二个元素
			int temp = 0;
			if (a[x] > a[y]) {//将第一个元素与之后所有元素进行比较,最小元素放第一个,一次进行
				temp = a[x];
				a[x] = a[y];
				a[y] = temp;
			}
		}
	}
}

(3)算法复杂度分析及经验归纳

1.N个元素排序,需要比较n(n-1)/2次;

2.选择排序的算法复杂度仍为O(n*n);

3.相比于冒泡排序,选择排序的交换次数大大减少,因此速度要快于冒泡排序

二、冒泡排序

 (1)算法分析

1、从第一个数据开始,与第二个数据相比较,如果第二个数据小于第一个数据,则交换两个数据的位置。

2、指针由第一个数据移向第二个数据,第二个数据与第三个数据相比较,如果第三个数据小于第二个数据,则交换两个数据的位置。

3、依此类推,完成第一轮排序。第一轮排序结束后,最大的元素被移到了最右面。

4、依照上面的过程进行第二轮排序,将第二大的排在倒数第二的位置。

5、重复上述过程,没排完一轮,比较次数就减少一次。

2算法实现

/*
 * 冒泡排序:两两比较,大数冒泡
 * 需要比较n(n-1)/2次;
 * 冒泡排序的算法复杂度较高,为O(n*n)
 */
public static void ButtleSort(int[] a) {
	int len = a.length;
	for (int x = 0; x < len - 1; x++) {//x表示排序轮数
		for (int y = 0; y < len - 1 - x; y++) {//y表示比较的次数
			int temp = 0;
			if (a[y] > a[y + 1]) {//两两比较,若前面的大于后面的,交换位置
				temp = a[y];
				a[y] = a[y + 1];
				a[y + 1] = temp;
			}
		}
	}
}

(3)算法复杂度分析及经验归纳

1.N个元素需要排序N-1轮;

2.y轮需要比较N-x次;

3.N个元素排序,需要比较n(n-1)/2次;

4.冒泡排序的算法复杂度较高,为O(n*n)

三、归并排序

(1)算法分析

1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列。

2.设定两个指针,最初位置分别为两个已经排序序列的起始位置。

3.比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置。

4.重复步骤3,直到某一指针超出序列尾将另一序列剩下的所有元素直接复制到合并序列尾。

(2)算法实现

/*
 * 归并排序
 * 时间复杂度是O(N*LogN)
 */
public static void Merge(int[]a,int low,int mid,int high){		
	int [] mergeArr = new int[high-low+1];//申请一个新空间来保存排序后数组		
	int i = low;		
	int j = mid + 1;		
	int k = 0;		
	while(i <= mid && j <= high){			
		if(a[i] < a[j]){				
			mergeArr[k] = a[i];				
			k++;				
			i++;			
		}else{				
			mergeArr[k] = a[j];				
			k++;				
			j++;			
		}		
	}		
	while(i <= mid){			
		mergeArr[k] = a[i];			
		k++;			
		i++;		
	}//把左边剩余的元素导入		
	while(j <= high){			
		mergeArr[k] = a[j];			
		j++;			
		k++;		
	}//把右边剩余的元素导入		
	for(int m = 0;m < mergeArr.length;m++){			
		a[m+low] = mergeArr[m];		
	}//将新排好序的数组放入元素相应的位置中	
}	
public static void MergeSort(int []a,int low,int high){		
	int mid = (low + high) / 2;		
	if(low < high){			
		MergeSort(a,low,mid);//左			
		MergeSort(a,mid+1,high);//右			
		Merge(a,low,mid,high);//左右合并		
	}	
}

(3)算法复杂度分析及经验归纳

归并排序的时间复杂度是O(nlogn),但是归并排序是一种稳定的排序方法,即相等的元素顺序不会改变,但是相比于快速排序来说归并要申请的空间更大,消耗空间更多。

四、快速排序

(1)算法分析

1.设置两个变量i,j,排序开始的时候,i=low;j = high;(第一趟low=0,high为数组长度N)。

2.以第一个数组元素为关键元素,赋值给key,即key = A[0]。

3.从j开始像前搜索,找到第一个比key小的数a[j],将a[j]和a[i]交换

4.从i开始向后搜素,找到第一个比key大的数a[i],将a[i]和a[j]交换

5.重复3,4步直到i==j   

6.对key左边和右边分别递归调用以上步骤                                     

(2)算法实现

/*
 * 快速排序
 * 时间复杂度:O(NlogN)
 */
public static void quickSort(int[] a, int low, int high) {
	if (low > high) {
	    return;
	} // 递归的出口
	int i = low;
	int j = high;
	int key = a[low];
	while (i < j) {// 比较j---key
		while (a[j] > key && i < j) {
			j--;
		} // 找到第一个比key小的数
		while (a[i] <= key && i < j) {
			i++;
		} // 找到第一个比key大的数
		if (i < j) {// 如果i小于j,交换a[i],a[j]
			int temp = a[i];
			a[i] = a[j];
			a[j] = temp;
		}
	}
	int p = a[i];
	a[i] = a[low];
	a[low] = p;// 调整key的位置
	quickSort(a, low, i - 1);
	quickSort(a, i + 1, high);
}

(3)算法复杂度分析及经验归纳

   快速排序的时间复杂度是O(nlogn),但是快速排序是一种不稳定的排序方法,也就是说当有多个相同的值的时候在排序结束的时候它们的相对位置会发生改变

五、插入排序

(1)算法分析

1、将指针指向某个元素,假设该元素左侧的元素全部有序,将该元素抽取出来,然后按照从右往左的顺序分别与其左边的元素比较,遇到比其大的元素便将元素右移,直到找到比该元素小的元素或者找到最左面发现其左侧的元素都比它大,停止;

   2、此时会出现一个空位,将该元素放入到空位中,此时该元素左侧的元素都比它小,右侧的元素都比它大;

  3、指针向后移动一位,重复上述过程。每操作一轮,左侧有序元素都增加一个,右侧无序元素都减少一个。

(2)算法实现

/*
 * 插入排序
 * 平均比较次数为N*(N-1)/4
 * 时间复杂度仍然为O(n*n)
 */
public static void InsertSort(int[] a) {
	int len = a.length;//要测数据的长度
	int temp = 0;
	for (int i = 1; i < len; i++) {
		int j = i - 1;// 初始化 j 第一次为0
		temp = a[i]; // 提取当前比较的值 temp 第一次为2
		// 循环将 temp 也就是 xx[i] 的值与它所在坐标之前的数字进行比较。如果发现大于temp的数字,则将此元素后移
		for (; j >= 0 && temp < a[j]; j--){
			a[j + 1] = a[j];
		}
		/**
		 * 这里有一个逻辑小问题,容易被忽略 。我们初始化的 j 为0,j+1 不就是 1 了么?貌似就有点懵逼了
		 * 请注意上面的for循环,我们拿第一次循环来看,j = 0 ;而且temp = 2 小于了 xx[j] 也就是3 ,也通过,
		 * 紧跟着进行了一次 j-- 所以 j 等于了 -1 。 如果不进入循环,那么 xx[j+1] 就等于了 x[i] , 也就是不作变动
		 */
		a[j + 1] = temp;
	}
}

(3)算法复杂度分析及经验归纳

1.时间复杂度,由于仍然需要两层循环,插入排序的时间复杂度仍然为O(n*n)。
2.比较次数:在第一轮排序中,插入排序最多比较一次;在第二轮排序中插入排序最多比较二次;以此类推,最后一轮排序时,最多比较N-1次,因此插入排序最多比较次数1+2+...+N-1=N*(N-1)/2。尽管如此,实际上插入排序很少会真的比较这么多次,因为一旦发现左侧有比目标元素小的元素,比较就停止了,因此,插入排序平均比较次数为N*(N-1)/4。

3.移动次数:插入排序的移动次数与比较次数几乎一致,但移动的速度要比交换的速度快得多。

因此,插入排序的速度约比冒泡排序快一倍(比较次数少一倍),比选择排序还要快一些

六、测试

public static void menu() {
	int[] num1 = { 10, 100, 1000, 10000, 100000};//将要测的数据规模放入数组中
	System.out.println("1.选择排序      2.冒泡排序      3.合并排序     4.快速排序      5.插入      6.退出");
	System.out.println("请选择排序方法:");
	Scanner sc = new Scanner(System.in);
	int num = sc.nextInt();
	for (int j = 0; j < num1.length; j++) {//遍历并取出数据规模数值
		long resultTime = 0;//运行一次所用的时间
		int sum = 0;//计算20次样本总的时间
		int num2 = num1[j];//将数据规模数值放入num2数组中
		System.out.println("数据规模为" + (num2) + "时:");
		System.out.println("20组样本(ms):");
		for (int n = 1; n <= 20; n++) {//遍历取出20次运行的结果
			long startTime = System.currentTimeMillis();//提取程序一次开始时的时间
			Random r = new Random();
			int[] a = new int[num2];//将要测的数据规模数值放入数组a中
			for (int i = 0; i < num2; i++) {
				a[i] = r.nextInt(num2) + 1;//自动生成要排序的数据
			}
			switch (num) {
			case 1:
				ChooseSort(a);
				break;
			case 2:
				ButtleSort(a);
				break;
			case 3:
				MergeSort(a, 0, a.length - 1);
				break;
			case 4:
				quickSort(a, 0, a.length - 1);
				break;
			case 5:
				InsertSort(a);
				break;
			case 6:
				System.exit(0);
				break;
			}
			long endTime = System.currentTimeMillis();//提取程序一次结束时的时间
			resultTime = endTime - startTime;
			sum += resultTime;
			System.out.print(resultTime + " ");//输出每次运行的时间
		}
		System.out.println();
		long avgTime = (sum / 20);//20次运行时间的平均值
		System.out.println("排序所花时间为:" + avgTime + "ms");
	}
}
public static void main(String[] args) {
	while (true) {
		menu();// 调用方法
	}
}

 

 

 

 

你可能感兴趣的:(选择排序、冒泡排序、归并排序、快速排序、插入排序的算法原理与比较)