八大排序

八大排序

八大排序就是内部排序,当n较大的时候,应该采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或者归并排序。

各排序算法的特点:当n较大,应该采取时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或者归并排序。
快速排序:是基于目前比较的内部排序中被认为最好的方法,当待排序的关键字随机分布时,快速排序的平均时间最短。
在这里插入图片描述

插入排序 - 直接插入排序
基本思想:将一个记录插入到已排序好的有序表中,从而得到一个记录数增1的有序表。即:先将有序列的第一个记录看成一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列为止。
要点:设立哨兵,作为临时存储和判断数组边界之用
如果遇到一个和插入元素相等的,那么插入元素想把插入的元素放在相等元素的后面,因此,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序的顺序,所以插入排序是稳定的。

public class InsertSort {
	public static void insertSort(int[] array) {
		int len = array.length;
		for(int i = 1; i < len; i++) {
			int temp = array[i];
			int j = i - 1;
			for(; j >= 0 && array[j] > temp; j--) {
				array[j + 1] = array[j];
			}
			array[j + 1] = temp;
			}
		}
	}

复杂度分析
从空间上来看,只需要一个记录的辅助空间,因此关键看它的空间复杂度。
最好的请况的时候是要排序的表本身就是有序的,一共进行了n-1次比较,没有进行移动,因此此时时间复杂度是n-1。
当最坏的情况是排序表是完全逆序的时候,需要比较n(n-1)/2次,移动次数为3+4+…+(n+3)=(n+4)(n-1)/2。
如果排序记录是随机的,那么根据概率相同的原则,平均比较和移动次数约为n2/4。因此我们可以得出直接插入排序法的时间复杂度为O(n2) 。

插入排序 - 希尔排序
基本原理:希尔排序也称为“缩小增量排序”,其基本原理是,现将待排序的数组元素分为多个子列,使得每个子队列的元素个数相对较少,然后对各个子序列分别进行直接插入排序。因此,需要采用跳跃分割的策略:将相距某个“增量”的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部基本有序。

public class ShellSort {
	public static void shellSort(int[] array) {
		int len = array.length;
		int increment = len / 2;
		while(increment > 0) {
			for(int i = increment; i < len; i ++) {
				int j = i;
				int temp = array[i];
				while(j >= increment && temp < array[j-increment]) {
					array[j] = array[j-increment];
					j = j -increment;
				}
				array[j] = temp;
			}
			increment = increment / 2; 
			}
		}
	}

注意:增量队列的最后一个增量值必须等于1才行,另外由于记录是跳跃式的移动,所以希尔排序并不是温稳定的算法。但是不管怎么说,希尔排序算法的发明,使时间复杂度突破了冒泡排序,简单选择排序和直接插入排序的O(n2)。

选择排序 - 简单选择排序
思路:简单选择排序是一种简单直观的排序算法,首先在未排序序列中找到最小元素,存放在排序序列的起始位置,然后从剩余未排列元素中继续寻找最小元素,继续将其放在已排序序列的末尾,直到所有元素均排序完毕。它的主要优点与数据移动有关。如果某个元素在正确的位置上,则它不会被移动。选择排序每次交换一对元素,它们当中至少有一个被移动到最终位置上。

public class SelectSort(){
	public void selectSort(int[] array){
		int min = 0;
		for(int i = 0; i < array.length; i++){
			min = i;
			for(int j = i + 1; j < array.length; j++){
				if(array[j] < array[min])	
					min = j;
			}
			if(i != min){
			int temp = array[i];
			array[i] = array[min];
			array[min] = temp;
			}
		}
	}
}

复杂度分析:
简单选择排序的特点就是交换数据次数相当少。分析时间复杂度发现,无论最好最差的情况,其比较次数都是一样多,第i趟需要n - i次关键字的比较,总次数为n(n-1)/2。而对于交换次数而言,最好的时候是0次,最差的时候是n -1 次,因此最终时间复杂度应该是比较交换的总次数。因此,总的时间复杂度依然是O(n2)。

选择排序 - 堆排序
堆是具有下列性质的完全二叉树:每个结点的值都大于或者等于其左右结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。同时完全二叉树具有下标i与2i和2i+1的双亲子女关系。
完美二叉树:在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为完美二叉树。
完全二叉树:对一棵具有n个结点的二叉树按层序遍历,如果编号为i(1<= i <=n)的结点与同样深度的完美二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树被称为完全二叉树。
堆排序(Heap Sort)就是利用堆进行排序的方法。基本思想是,将一个待排的序列构成一个大顶堆。此时,整个序列的最大值就是堆顶的根结点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1 个序列重新构成一个堆,这样就会得到n个元素中的次大值。如此反复执行,便能得到一个有序序列了。

package sort;

public class Heap {
	public static void heapSort(int[] array) {
		int len = array.length;
		if(array == null || len == 0)
			throw new IllegalArgumentException();
		
		//首先从下到上开始构建最大堆,起始点为第一个父节点,注意编号规律
		for(int i = len / 2 - 1; i >= 0; i--) {
			adjustHeap(array, i, len);
		}
		
		//将最大堆的顶点即最大值与最小值交换,然后将顶点的最小值调整下去
		for(int i = len - 1; i >= 0; i--) {
			swap(array, 0, i);
			adjustHeap(array, 0, i);
		}
	}
	
	/**
	 * 调整某个顶点下面的子堆,保证该顶点的子堆是符合规律的
	 * 注意:大顶堆只需要保证父节点的值大于子节点,不需要保证左右节点的大小关系
	 * @param arr
	 * @param 顶点的坐标
	 * @param 最大数组长度
	 */
	private static void adjustHeap(int[] arr, int i, int len) { 
		int temp = arr[i];
		int k = i;
		int index = 2 * k + 1;
		while(index < len) {
			//首先判断是否为单叶子节点
			if(index + 1 < len) {
				//若有两个叶节点,选出较大的那个叶节点
				if(arr[index] < arr[index + 1]) {
					index = index + 1;
				}
			}
			//再判断父节点与较大叶节点之间的大小关系,以决定是否需要交换
			if(arr[index] > temp) {
				arr[k] = arr[index];
				k = index;
				index = 2 * index + 1;
			}else {
				break;
			}
		}
		//先暂时保留i 点的值,最后找到了适合i点的位置,一次性放入
		arr[k] = temp;
	}
	
	//交换最大值与最小值
	private static void swap(int[] arr, int i, int j) {
		int temp = arr[i];
		arr[i] = arr[j];
		arr[j] = temp;
	}
	
	public static void main(String[] args) {
		int[] arr = {7, 3, 5, 2, 1, 10, 6};
		heapSort(arr);
		for(int i = 0; i < arr.length; i ++) {
			System.out.println(arr[i]);
		}
	}
}

复杂度分析:
堆排序的运行时间主要消耗在初始构建堆和在重建堆的反复筛选中。在 构建堆的过程中,因为我们是完全二叉树从最下层最右边的非终端结点开始构建,将它与其它华子进行比较和若有必要的交换,对于每个非终端结点来说,其实最多进行两次比较和互换操作,因此整个构建堆的时间复杂度是O(n)。
在正式排序时,第i次取堆顶重建堆需要O(log i)的时间,并且需要n - 1次取堆顶记录,因此重建堆的时间复杂度是O(n log n)。且对原始记录的排列状态并不敏感,因此它无论是最好、最坏和平均复杂度均为O(n log n)。且是一种不稳定的排序方法。
另外由于初始构建堆所需的比较次数比较多,因此并不适合待排序序列个数较少的情况。

交换排序-冒泡排序
冒泡排序是一种交换排序,它的基本思想是:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。

/**
 * 冒泡排序的核心操作在于交换,类似于一个泡泡从数组最后面移到前面
 * @author Sam
 *
 */
public class Bubble {
	/**
	 * 这个是最简单的交换排序,没有让相邻数进行交换,每次第一重for循环之后,保证数组第一个数字是所有数组最小的
	 * 有点类似于简单选择排序,不同的是选择排不会每一次比较之后都交换,而是选择出最小值一次完成交换
	 * 但是之前排好的序对后面的排序没有什么帮助,因此是有缺陷的
	 * @param array
	 */
	public static void exchangeSort(int[] array) {
		int len = array.length;
		if(array == null || len == 0)
			throw new IllegalArgumentException();
		
		for(int i = 0; i < len; i++) {
			for(int j = i + 1; j < len; j++) {
				if(array[i] > array[j])
					swap(array, i, j);
			}
		}
	}
	
	/**
	 * 这是正宗的冒泡排序
	 * @param array
	 */
	public static void bubbleSort(int[] array) {
		int len = array.length;
		if(array == null || len == 0)
			throw new IllegalArgumentException();
		
		//要注意两重循环的顺序是相反的
		for(int i = 1; i < len - 1; i++) {
			for(int j = len - 1; j >= i; j--) {    //注意:这里和交换排序不一样,这里是相邻进行交换
				if(array[j - 1] > array[j]) {
					swap(array, j-1, j);
				}
			}
		}
	}
	
	/**
	 * 优化的冒泡排序的关键在于对i的for循环中,增加了flag是否为true的判断,可以避免在已经有序的情况下的无意义循环
	 * @param array
	 */
	public static void optimizedBubbleSort(int[] array) {
		int len = array.length;
		if(array == null || len == 0)
			throw new IllegalArgumentException();
		
		boolean flag = true;
		for(int i = 1; i < len && flag; i++) {
			flag = false;
			for(int j = len -1; j >= i; j--) {
				if(array[j] < array[j - 1]) {
					swap(array, j, j-1);
					flag = true;
				}
			}
		}
	}
	
	public static void swap(int[] array, int i, int j) {
		int temp = array[i];
		array[i] = array[j];
		array[j] = temp;
		
	}
	
	public static void main(String[] args) {
		int[] arr = {7, 3, 5, 2, 1, 10, 6};
		optimizedBubbleSort(arr);
		for(int i = 0; i < arr.length; i ++) {
			System.out.println(arr[i]);
		}
	}
}

	

时间复杂度为O(n2)。

交换排序 - 快速排序
原理:选择一个关键值作为基准值(一般选择数组的第一个值),比基准值小的一边在左边序列(一般无序),比基准值大的一般在右边(一般无序)。
第一次循环:从后往前比较,用基准值和最后一个值比较,如果比基准值小,交换位置,如果没有就继续比较下一个,直到找到第一个比基准值小的值才交换。找到这个值之后再从前往后找,找到第一个比基准值大的,交换位置,如果没有则继续下一个。一次循环之后,往后的值都大于前面的值,此时对于基准值而言,左右两边是有序的。接着比较比较左右两边的序列,重复上述的循环。

public class QuickSort{ 
	
	public void quickSort(int[] array, int low, int high){
		int start = low;
		int end = high;
		int key = array[start];
		
		while(end > start) {
			while(end > start && array[end] >= key) {
				end --;
			}
			swap(array, end, start);
			while(end > start && array[start]
			}
			swap(array, end, start);
		}
		if(start > low)
			quickSort(array, low, start);
		if(start < high)
			quickSort(array, start + 1, high);
	}
	
	private void swap(int[] arr, int i, int j){
		int temp = arr[i];
		arr[i] = arr[j];
		arr[j] = temp;
	}
}

归并排序

public class Merging {
	public static void mergingSort(int[] array) {
		int len = array.length;
		if(array == null || len == 0) 
			throw new IllegalArgumentException();
		
		MSort(array, 0, len - 1);
		
	}
	
	private static void MSort(int[] arr, int low, int high) {
		int middle = (low + high) / 2;
		if(low < high) {
			MSort(arr, low, middle);
			MSort(arr, middle + 1, high);
			
			//将两个子列合并到一起
			merge(arr, low, middle, high);
		}
	}
	
	//合并子列
	private static void merge(int[] arr, int low, int middle, int high) {
		int[] temp = new int[high - low + 1];
		int i = low;
		int j = middle + 1;
		int k = 0;
		while(i <= middle && j <= high) {
			if(arr[i] < arr[j]) {
				temp[k++] = arr[i++];
			}else {
				temp[k++] = arr[j++];
			}
		}
		while(j <= high)
			temp[k++] = arr[j++];
		
		while(i <= middle)
			temp[k++] = arr[i++];
		
		for(int m = 0;m < temp.length; m++) {
			arr[m + low] = temp[m];
		}
	}
	
	public static void main(String[] args) {
		int[] arr = {7, 3, 5, 2, 1, 10, 6};
		mergingSort(arr);
		for(int i = 0; i < arr.length; i ++) {
			System.out.println(arr[i]);
		}
	}
}

复杂度:
归并排序的总时间复杂度是O(nlogn),不论是最好、最坏、平均的时间复杂度。其空间复杂度是O(n+log2n)。

基数排序

总结
在这里插入图片描述
从最好情况看,直接插入排序与冒泡排序更好,就是说,如果待排序列总是基本有序,那么就不应该考虑该技能的算法。
从最坏情况看,堆排序和归并排序更好。
工程上的排序是综合排序,在数组较小的时候选择插入排序,在数组较大的时候选择快速排序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值