排序算法

冒泡排序

原理:比较两个相邻的元素,将值大的元素交换至右端。
思路:依次比较相邻的两个数,将小数放在前面,大数放在后面。即在第一趟:首先比较第1个和第2个数,将小数放前,大数放后。然后比较第2个数和第3个数,将小数放前,大数放后,如此继续,直至比较最后两个数,将小数放前,大数放后。重复第一趟步骤,直至全部排序完成。
冒泡排序的优点:每进行一趟排序,就会少比较一次,因为每进行一趟排序都会找出一个较大值。
用时间复杂度来说:
  1.如果我们的数据正序,只需要走一趟即可完成排序。所需的比较次数C和记录移动次数M均达到最小值,即:Cmin=n-1;Mmin=0;所以,冒泡排序最好的时间复杂度为O(n)。
  2.如果很不幸我们的数据是反序的,则需要进行n-1趟排序。每趟排序要进行n-i次比较(1≤i≤n-1),且每次比较都必须移动记录三次来达到交换记录位置。在这种情况下,比较和移动次数均达到最大值:

冒泡排序的最坏时间复杂度为:O(n^2) 。
综上所述:冒泡排序总的平均时间复杂度为:O(n^2) 。

public class BubbleSort {
	
	public static void main(String[] args) {
		int[] arr = {2, 1, 8, 5, 6,3};
		System.out.print("排序之前: ");
		System.out.println(Arrays.toString(arr));
		
		int length = arr.length;
		
		for(int i = 0; i < length - 1; i++) {
			for(int j = 0; j < length - i - 1; j++) {
				if(arr[j] > arr[j + 1]) {
					int tmp = arr[j];
					arr[j] = arr[j + 1];
					arr[j + 1] = tmp;
				}
			}
		}
		System.out.print("排序后: ");
		System.out.println(Arrays.toString(arr));
	}
}

鸡尾酒排序

鸡尾酒排序的概念:鸡尾酒排序又叫定向冒泡排序,鸡尾酒搅拌排序,搅拌排序(也可以视作选择排序的一种变形),涟漪排序,来回排序或快乐小时排序,是冒泡排序的一种变形。此算法与冒泡排序的不同处在于排序时是以双向在序列中进行排序。

鸡尾酒排序的算法描述如下:
先对数组从左到右进行升序的冒泡排序;
再对数组进行从右到左的降序的冒泡排序;
以此类推,持续的、依次的改变冒泡的方向,并不断缩小没有排序的数组范围;

鸡尾酒排序复杂度:
时间复杂度 O(n^2)
最优时间复杂度 O(n)
平均时间复杂度 O(n^2)


public class CocktailSort {

	public static void main(String[] args) {
		int[] arr = { 2, 1, 8, 5, 6, 3 };
		System.out.print("排序之前: ");
		System.out.println(Arrays.toString(arr));

		int length = arr.length;
		int left = 0;
		int right = length - 1;
		int i, tmp;

		while (left < right) {
			for (i = left; i < right; i++) {
				if (arr[i] > arr[i + 1]) {
					tmp = arr[i];
					arr[i] = arr[i + 1];
					arr[i + 1] = tmp;
				}
			}
			right--;
			for(i = right; i > left; i--) {
				if(arr[i] > arr[i + 1]) {
					tmp = arr[i];
					arr[i] = arr[i + 1];
					arr[i + 1] = tmp;
				}
			}
			left++;
		}
		System.out.print("排序后: ");
		System.out.println(Arrays.toString(arr));
	}
}

选择排序

原理:每一趟从待排序的记录中选出最小的元素,顺序放在已排好序的序列最后,直到全部记录排序完毕。也就是:每一趟在n-i+1(i=1,2,…n-1)个记录中选取关键字最小的记录作为有序序列中第i个记录。
效率:对于长度为N的数组,选择排序需要大约N²/2次比较和N次交换。也即最好、最差、平均时间效率均为O(n²),只需要一个辅助变量帮助交换元素。

public class SelectionSort {

	public static void main(String[] args) {
		int[] arr = { 2, 1, 8, 5, 6, 3 };
		System.out.print("排序之前: ");
		System.out.println(Arrays.toString(arr));

		int length = arr.length;

		for (int i = 0; i < length; i++) {
			int k = i;
			for (int j = k + 1; j < length; j++) {
				if(arr[j] < arr[k]) {
					k = j;
				}
			}
			
			if(i != k) {
				int tmp = arr[i];
				arr[i] = arr[k];
				arr[k] = tmp;
			}
			System.out.println();
			for(int a : arr) {
				System.out.print(a);
			}
		}
		System.out.print("排序后: ");
		System.out.println(Arrays.toString(arr));
	}

}

插入排序

插入排序类似整理扑克牌,将每一张牌插到其他已经有序的牌中适当的位置。
插入排序由N-1趟排序组成,对于P=1到N-1趟,插入排序保证从位置0到位置P上的元素为已排序状态。
简单的说,就是插入排序总共需要排序N-1趟,从index为1开始,讲该位置上的元素与之前的元素比较,放入合适的位置,这样循环下来之后,即为有序数组。

效率:如果目标是把n个元素的序列升序排列,那么采用插入排序存在最好情况和最坏情况。最好情况就是,序列已经是升序排列了,在这种情况下,需要进行的比较操作需(n-1)次即可。最坏情况就是,序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。插入排序的赋值操作是比较操作的次数加上 (n-1)次。平均来说插入排序算法的时间复杂度为O(n^2)

public class InsertSort {

	public static void main(String[] args) {
		int[] arr = { 2, 1, 8, 5, 6, 3 };
		System.out.print("排序之前: ");
		System.out.println(Arrays.toString(arr));
		
		int length = arr.length;
		for(int i = 0; i < length - 1; i++) {
			for(int j = i; j >= 0 && arr[j] > arr[j + 1];j--) {
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
		System.out.print("排序后: ");
		System.out.println(Arrays.toString(arr));
	}

}

二分插入排序

二分插入排序的基本思想和插入排序一致;都是将某个元素插入到已经有序的序列的正确的位置; 和直接插入排序的最大区别是,元素A[i]的位置的方法不一样;直接插入排序是从A[i-1]往前一个个比较,从而找到正确的位置;而二分插入排序,利用前i-1个元素已经是有序的特点结合二分查找的特点,找到正确的位置,从而将A[i]插入,并保持新的序列依旧有序;

public class BinaryInsertSort {

	public static void main(String[] args) {
		int[] arr = { 2, 1, 8, 5, 6, 3 };
		System.out.print("排序之前: ");
		System.out.println(Arrays.toString(arr));

		int length = arr.length;
		int tmp;
		for (int i = 1; i < length; i++) {
			int left = 0;
			int right = i - 1;
			int mid;
			
			while(left <= right) {
				mid = (left + right) / 2;
				if(arr[i] > arr[mid]) {
					left = mid + 1;
				}else {
					right = mid - 1;
				}
			}
			tmp = arr[i];
			for(int j = i - 1; j > left -1; j--) {
				
				arr[j + 1] = arr[j];
				arr[j] = tmp;
				
			}
		}
		System.out.print("排序后: ");
		System.out.println(Arrays.toString(arr));

	}

}

希尔排序

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
基本步骤,在此我们选择增量gap=length/2,缩小增量继续以gap = gap/2的方式,这种增量选择我们可以用一个序列来表示,{n/2,(n/2)/2…1},称为增量序列。

增量序列{n/2,(n/2)/2…1}(希尔增量),其最坏时间复杂度依然为O(n2),一些经过优化的增量序列如Hibbard经过复杂证明可使得最坏时间复杂度为O(n3/2)。

public class ShellSort {
	
	public static void main(String[] args) {
		int[] arr = { 2, 1, 8, 5, 6, 3 };
		System.out.print("排序之前: ");
		System.out.println(Arrays.toString(arr));
		
		int length = arr.length;
		
		for(int gap = length / 2; gap > 0; gap /= 2) {
			for(int i = gap; i < length; i++ ) {
				int j = i;
				while(j - gap >= 0 && arr[j] < arr[j - gap]) {
					int tmp = arr[j];
					arr[j] = arr[j - gap];
					arr[j - gap] = tmp;
					j -= gap;
				}
			}
		}
		
		
		System.out.print("排序后: ");
		System.out.println(Arrays.toString(arr));
	}

}

归并排序

归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。

分阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。

public class MergeSort {

	public static void main(String[] args) {
		int[] arr = { 2, 1, 8, 5, 6, 3 };
		System.out.print("排序之前: ");
		System.out.println(Arrays.toString(arr));
		
		sort(arr);

		System.out.print("排序后: ");
		System.out.println(Arrays.toString(arr));
	}

	public static void sort(int[] arr) {
		int [] tmp = new int[arr.length];
		sort(arr, 0, arr.length - 1, tmp);
	}

	public static void sort(int[] arr, int left, int right, int[] tmp) {
		if(left < right) {
			int mid = (left + right) / 2;
			sort(arr, left, mid, tmp);
			sort(arr, mid + 1, right, tmp);
			merge(arr, left, mid, right, tmp);
		}
		
	}
	
	public static void merge(int[] arr, int left, int mid, int right, int[] tmp){
		int i = left;
		int j = mid + 1;
		int t = 0;
		while(i <= mid && j <= right) {
			if(arr[i] < arr[j]) {
				tmp[t++] = arr[i++];
			}else {
				tmp[t++] = arr[j++];
			}
		}
		
		while(i <= mid) {
			tmp[t++] = arr[i++];
		}
		
		while(j <= right) {
			tmp[t++] = arr[j++];
		}
		
		t = 0;
		
		while(left <= right) {
			arr[left++] = tmp[t++];
		}
	}

}

堆排序

堆排序是把数组看作堆,第i个结点的孩子结点为第2i+1和2i+2个结点(不超出数组长度前提下),堆排序的第一步是建堆,然后是取堆顶元素然后调整堆。
建堆的过程是自底向上不断调整达成的,这样当调整某个结点时,其左节点和右结点已经是满足条件的,此时如果两个子结点不需要动,则整个子树不需要动,如果调整,则父结点交换到子结点位置,再以此结点继续调整。 使用的大顶堆,建立好堆后堆顶元素为最大值,此时取堆顶元素即使堆顶元素和最后一个元素交换,最大的元素处于数组最后,此时调整小了一个长度的堆,然后再取堆顶和倒数第二个元素交换,依次类推,完成数据的非递减排序。 堆排序的主要时间花在初始建堆期间,建好堆后,堆这种数据结构以及它奇妙的特征,使得找到数列中最大的数字这样的操作只需要O(1)的时间复杂度,维护需要logn的时间复杂度。


快速排序

快速排序是冒泡排序的改进版,也是最好的一种内排序,在很多面试题中都会出现,也是作为程序员必须掌握的一种排序方法。
思想:1.在待排序的元素任取一个元素作为基准(通常选第一个元素,但最的选择方法是从待排序元素中随机选取一个作为基准),称为基准元素;
2.将待排序的元素进行分区,比基准元素大的元素放在它的右边,比其小的放在它的左边;
3.对左右两个分区重复以上步骤直到所有元素都是有序的。
所以我是把快速排序联想成东拆西补或西拆东补,一边拆一边补,直到所有元素达到有序状态。

算法分析:
1.当分区选取的基准元素为待排序元素中的最大或最小值时,为最坏的情况,时间复杂度和直接插入排序的一样,移动次数达到最大值 Cmax = 1+2+…+(n-1) = n*(n-1)/2 = O(n2) 此时最好时间复杂为O(n2)
2.当分区选取的基准元素为待排序元素中的"中值",为最好的情况,时间复杂度为O(nlog2n)。
3.快速排序的空间复杂度为O(log2n).
4.当待排序元素类似[6,1,3,7,3]且基准元素为6时,经过分区,形成[1,3,3,6,7],两个3的相对位置发生了改变,所是快速排序是一种不稳定排序。

public class FastSort {
	
	public static void main(String[] args) {
		int[] arr = { 2, 1, 8, 5, 6, 3 };
		System.out.print("排序之前: ");
		System.out.println(Arrays.toString(arr));
		
		int left = 0;
		int right = arr.length - 1;
		fast(arr, left, right);
		
		System.out.print("排序后: ");
		System.out.println(Arrays.toString(arr));
	}

	public static void swap(int[] arr, int i, int j) {
		int tmp = arr[j];
		arr[j] = arr[i];
		arr[i] = tmp;
	}
	
	public static void fast(int[] arr,int left,int right) {
		int i = left;
		int j = right;
		int tmp = 0;
		if(left <= right) {
			tmp = arr[left];
			while(left != right) {
				while(left < right && arr[right] >= tmp) {
					right--;
				}
				arr[left] = arr[right];
				while(left < right && arr[left] <= tmp) {
					left++;
				}
				arr[right] = arr[left];
			}
			arr[right] = tmp;
			fast(arr, i, left - 1);
			fast(arr, right + 1, j);
		}
	}
}

比较

排序方法 平均情况 最好情况 最坏情况 稳定性
冒泡排序 O(n^2) O(n) O(n^2) 稳定
鸡尾酒排序 O(n^2) O(n) O(n^2)
选择排序 O(n^2) O(n^2) O(n^2) 不稳定
插入排序 O(n^2) O(n) O(n^2) 稳定
二分插入排序
希尔排序 O(nlogn)/O(n^2) O(n^1.3) O(n^2) 不稳定
归并排序 O(nlogn) O(nlogn) O(nlogn) 稳定
堆排序 O(nlogn) O(nlogn) O(nlogn) 不稳定

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值