震惊!常见的七种排序算法都在这里了(笑)

整理了常见的7种排序算法,分一下类即:

插入排序: 直接插入和希尔排序

选择排序:简单选择和堆排序

交换排序:冒泡排序和快速排序

此外,还有一种归并排序。

(一)插入排序

(1)直接插入排序

插入排序:时间复杂度是O(n^2)  稳定
思路记忆:分为有序列和无序列两个部分,前面部分是有序列(从一个元素开始),然后向其中插入

public class InsertSort {

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int[] array = new int[n];
		for (int i = 0; i < n; i++) {
			array[i] = sc.nextInt();
		}
		new InsertSort().insertSort(array);
	}

	public void insertSort(int[] array) {
		int length = array.length;
		// 将数组第一个元素驻留,作为有序列部分,从第二个元素开始作为插入元素
		for (int i = 1; i < length; i++) {
			// 有序列中最后一个元素的下标
			int j = i - 1;
			// 将要插入到元素
			int temp = array[i];
			// 将要插入的元素和有序列比较,比较时从后向前,这样比较的结果有两种,一种是找到了一个比有序列中元素
			// 大或者等于的位置,另一种是比有序列中第一个元素即最小的元素还要小。在此之前全体后移,给要插入的元素留出array【j】的位置
			for (; j >= 0 && array[j] > temp; j--) {
				array[j + 1] = array[j];
			}

			// 这里加一是因为for循环将理想位置又减了1
			array[j + 1] = temp;
		}
		for (int i : array)
			System.out.print(i + " ");
	}
//可以看到,当遇到相等的情况就会停下来,不再往前移,所以稳定。
	//for循环嵌套,所以时间复杂度是O(n)
	//没有使用辅助空间
}
(2)希尔排序

1. 希尔排序是对直接插入排序算法的优化。为什么这样说呢?是因为直接插入排序是将前面部分视作有序列,然后将后面的元素挨个插入
而希尔排序进行了分组:分为间隔为h的几队,这几组相同位置的元素构成一组,组内进行比较,也就是通过组内的直接插入排序,使其初步有序,
而且,这样需要移动的元素数目大大减少,当后期h逐步变小时,虽然移动的数目变多了,但是这时候已经基本有序了,移动的可能性就变小了。
2. 关于希尔排序的时间复杂度,这与h的选择有关,是一个未解的数学之谜。大致在O(nlogn)~O(n^2)之间
希尔排序时间复杂度的下界是n*log2n。希尔排序没有快速排序算法快 O(n(logn)),因此中等大小规模表现良好,对规模非常大的数据排序不是最优选择。
但是比O(  )复杂度的算法快得多。并且希尔排序非常容易实现,算法代码短而简单。 
此外,希尔算法在最坏的情况下和平均情况下执行效率相差不是很多,与此同时快速排序在最坏的情况下执行的效率会非常差。
专家们提倡,几乎任何排序工作在开始时都可以用希尔排序,若在实际使用中证明它不够快,再改成快速排序这样更高级的排序算法. 
3. 希尔排序是不稳定的。

public class ShellSort {

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int[] array = new int[n];
		for (int i = 0; i < n; i++) {
			array[i] = sc.nextInt();
		}
		new ShellSort().shellSort(array);
		for (int i : array)
			System.out.print(i + " ");
	}

	public void shellSort(int[] array) {
		int i, j, gap;
		int length = array.length;
		for (gap = length / 2; gap > 0; gap--) {
			//组内的直接插入排序
			for (j = gap; j < length; j++) {
				//组内,从第二个元素开始找自己合适插入的位置,如果比前面的小,那么往前移,一直移到合适位置再落地生花
				int temp = array[j];
				int k = j - gap;
				while (k >= 0 && array[k] > temp) {
					array[j] = array[k];
					k -= gap;
				}
				array[k + gap] = temp;
			}

		}
	}
	//学到的一种简洁的写法
	public void simpleShell(int[] array){
		int i,j, gap;
		for(gap = array.length/2; gap >0;gap /=2){
			//组内从第二个元素开始找自己的合适位置
			for( i = gap;i<array.length;i++)
				for(j = i-gap;j >= 0 && array[j] > array[j+gap]; j-= gap)
					Swap(array[j],array[j+gap]);
		}
	}

	private void Swap(int i, int j) {
		int temp = i;
		i = j;
		j = temp;
	}
}
(二) 选择排序

(1)简单选择排序

直接选择排序也叫简单选择排序,即每次选出最小的放到前面的序列中。
直接选择排序要进行n-1次扫描,每次扫描都要选出无序区中最小的,并将前面位置的东西换到最小值位置上。也就是说,最好情况和最坏情况下,
虽然我们人眼能看出来是有序的了,仍然要进行扫描和比较。所以最好、最坏、平均时间复杂度都是O(n)。
再者,直接选择排序是不稳定的,eg :
排序前: 2,4,4*,3
排序后: 2 3 4* 4 

public class SelectSort {

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int[] array = new int[n];
		for (int i = 0; i < n; i++) {
			array[i] = sc.nextInt();
		}
		new SelectSort().selectSort(array);
	}

	public void selectSort(int[] array) {
		int length = array.length;
		//遍历n-1次
		for(int i = 0;i<length-1;i++ ){
			int min = i;
			//从无序队中,找到最小元素的下标,存在min中
			for(int j = i+1;j<length;j++){
				if(array[j] < array[min])
					min = j;
			}
			//交换当前位置和最小值位置的元素,即将后面无序区中的最小值加到前面的有序列中
			int temp = array[i];
			array[i] = array[min];
			array[min] = temp; 
		}
		for(int i : array)
			System.out.print( i + " ");
	}
}
(2)堆排序

 堆排序是利用大根堆或者小根堆的建立与调整进行排序,每次将root 与堆的最后一个元素交换,实现一个元素就位,进而调整剩下堆的元素
 堆排序是不稳定的排序算法,其平均时间复杂度是O(nlog n )

public class HeapSort {

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int[] array = new int[n];
		for (int i = 0; i < n; i++) {
			array[i] = sc.nextInt();
		}
		new HeapSort().heapSort(array);

	
	}
	//往下的方向
	public void maxHeap(int[] array, int length,int i){
			int left = 2*i +1;
			int right = 2* i+2;
			//找到根节点及其左右孩子中最大的保存在largest中
			int largest =  i;
			if(left < length && largest < left)
				largest = left;
			if(right < length && largest < right)
				largest = right;
			//如果根节点不是最大的,即需要调整成最大堆,就将根节点和孩子交换
			if(largest != i){
				int temp = i;
				i = largest;
				largest = temp;
			}
		maxHeap(array, length, largest);
	}
	//从下往上的方向
	public void buildMaxHeap(int[] array){
		int half  =  array.length/2;
		for(int i = half; i>=0;i--){
			//以每一个非叶子节点为根节点的子树都要建成一个大根堆
			maxHeap(array,array.length,i);
		}
	}
	public void heapSort(int[] array){
			buildMaxHeap(array);
			//array[0] 是大根堆的堆顶,每次要将堆顶换到后面去,再把剩下的调整为大根堆,重复此过程,直到堆里还剩下一个元素	
			for(int i = array.length-1; i >=1;i--){
				int temp = array[0];
				array[0] = array[i];
				array[i] = temp;
				maxHeap(array, i, 0);
			}
	}
}
(三)交换排序

(1)冒泡排序

冒泡排序是对数组进行n次扫描,每次都将最大的一个放到最右边
最好情况即本来就有序,这样扫描n次但无移动,所以时间复杂度是O(n)
最坏情况即反序,需要进行n次扫描,每次扫描都要进行n-i次移动,时间复杂度是O(n^2)
所以平均时间复杂度是O(n^2)
当遇到前后两个元素相等时,我们是不会去交换他们的,所以冒泡排序是稳定的

public class BubbleSort {

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int[] array = new int[n];
		for (int i = 0; i < n; i++) {
			array[i] = sc.nextInt();
		}
		new BubbleSort().bubbleSort(array);
	}

	public void bubbleSort(int[] array) {
		int length = array.length;
		// 进行 n 次遍历
		for (int i = 0; i < length; i++) {
			// 除了已经到位的右边的元素,其他的再从头遍历比较 其中界限 length-1-i 是关键点
			for (int j = 0; j < length - 1 - i; j++) {
				// 如果前面的元素比后面的大,就交换两者
				if (array[j] > array[j + 1]) {
					int temp = array[j];
					array[j] = array[j + 1];
					array[j + 1] = temp;
				}
			}
		}
		for (int i : array)
			System.out.print(i + " ");
	}
}
(2)快速排序

 1.快速排序是对冒泡排序的优化,冒泡排序每一次扫描进行的比较只能保证一个元素就位,但是快速排序用了分治的思想,能让
 一堆元素初始就位。
 2.快速排序的思路是: 选出一个中间元素(一般选数列中第一个元素即可),然后通过从后向前查找和从前向后查找两种方式,使比中间元素小的元素在中间元素的左边
  比中间元素大的元素在其右边。接下来再将左侧部分和右侧部分用相同的方法进行分治。
 3.快速排序的时间复杂度:每次将待排序数组分为两部分,理想状态下,每一次都是划分为等长的两部分,
 因此进行log2 n 次划分,每次两部分共进行n次比较,因此最好时间复杂度是O(nlog2 n)
 最坏情况是数组已经有序或者基本有序的情况,每次划分只能减少一个元素,快速排序退化为冒泡排序,时间复杂度是O(n^2)
 由此,我们也可以看出来,对于快速排序,原有数列越乱越好。
 4.快速排序的空间复杂度:O(1)  但要注意在递归栈上需要花费最少logn 最多n的空间

public class QuickSort {

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int[] array = new int[n];
		for (int i = 0; i < n; i++) {
			array[i] = sc.nextInt();
		}
		new QuickSort().quickSort(array, 0, array.length - 1);
	}

	public int Split(int[] array, int low, int high) {
		//split作为中间分割元素
		int split = array[0];
		//双while结构
		while (low < high) {
			//从后往前找到第一个比分割元素小的元素下标
			while (low < high && array[high] >= split)
				high--;
			//将此元素换到分割元素	前,low指针往后移,此时先不必让high处的元素固定下来,后面还会更改
			if (low < high)
				array[low++] = array[high];
			//从前往后找到第一个比分割元素大的元素下标
			while (low < high && array[low] <= split)
				low++;
			//将此元素换到分割元素	后,high指针往前移,此时先不必让low处的元素固定下来,后面还会更改
			if (low < high)
				array[high--] = array[low];
		}
		//最后将分割元素归位
		array[low] = split;
		return low;

	}

	public void quickSort(int[] array, int low, int high) {
		int partition;
		if (low < high) {
			//分治法
			partition = Split(array, low, high);
			//快排两侧部分
			quickSort(array, low, partition - 1);
			quickSort(array, partition + 1, high);
		}
	}
}
(四)归并排序

(1)归并排序也是使用了分治思想。这里我们可以和同样使用分治思想的快速排序进行比较可以发现,
  快速排序是先整体初步有序再细分有序,而归并排序是先细分有序,再合并到整体有序。两种有异曲同工之妙

(2)归并排序的思路是,先两两合并成有序列,得到的一个个子序列再作为一个个小整体两两合并
  具体来讲,第一步,申请辅助数组空间,和待排数组等长,用来存放排序后的
  第二步,设置头指针和尾指针,最初位置是待排序列的头和尾
  第三步,比较两个指针指向的元素。选择相对小的元素放入合并空间,并移动指针到下一位置
  重复第三步知道某一指针超出序列,将另一序列剩下的所有元素直接copy到合并序列尾部
(3)示例:如 设有数列{6,202,100,301,38,8,1}
初始状态:6,202,100,301,38,8,1
第一次归并后:{6,202},{100,301},{8,38},{1},比较次数:3;
第二次归并后:{6,100,202,301},{1,8,38},比较次数:4;
第三次归并后:{1,6,8,38,100,202,301},比较次数:4;
总的比较次数为:3+4+4=11,;

(4)时间复杂度:归并排序是仅次于快速排序的排序算法,最好,最坏和平均时间复杂度都是O(nlog n)
(5)空间复杂度:O(n)
(6)归并排序是稳定的排序算法

public class MergeSort {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int[] array = new int[n];
		for (int i = 0; i < n; i++) {
			array[i] = sc.nextInt();
		}
		new MergeSort().mergeSort(array, 0, array.length - 1);
		for (int i : array)
			System.out.print(i + " ");
	}

	public int[]  mergeSort(int[] array, int low, int high) {
			//low == high 这是递归调用退出的条件
			if(low < high){
			
			int mid = (low + high) / 2;
			//对左半部分归并排序
			mergeSort(array, low, mid);
			//对右半部分归并排序
			mergeSort(array, mid+1, high);
			//合并两者排序后的有序列
			merge(array,low,mid,high);
			}
				return array;
	}
	public void merge(int[] array,int low,int mid,int high){
		//辅助数组空间用于储存排序后的序列
		int[] temp = new int[high-low+1];
		//左边子序列的头
		int i = low;
		//右边子序列的头
		int j = mid +1;
		//辅助数组的下标初始值
		int k = 0;
		//两个子序列还没有一个完成排序时,每次两个子序列中最小的进入辅助数组中
		while(i<=mid && j<= high){
			if(array[i] < array[j])	
				temp[k++] = array[i++];
			else
				temp[k++] = array[j++];
		}
		//必然会有一个子序列先为空,这时将剩下的一个子序列剩下的元素复制进辅助数组中去
		while(i<=mid)
			temp[k++] = array[i++];
		while(j<= high)
			temp[k++] = array[j++];
		//将辅助数组的内容搬回原来的数组中去
		for( int l = 0;l<temp.length;l++)
			array[low++] = temp[l];
	}
}

附:各种排序算法时间复杂度统计表

具有稳定性的算法有: 基数排序  直接插入排序  冒泡排序  归并排序

可以快速记忆:奇数只鱼儿插入水中冒出气泡,不一会水面又归于了平静。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值