七种经典排序算法最全攻略

经典排序算法在面试中占有很大的比重,也是基础。包括冒泡排序,插入排序,选择排序,希尔排序,归并排序,快速排序,堆排序。希望能帮助到有需要的同学。全部程序采用JAVA实现。

本篇博客所有排序实现均默认从小到大

一、冒泡排序
BubbleSort

介绍:

冒泡排序的原理非常简单,它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。

步骤:

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对第0个到第n-1个数据做同样的工作。这时,最大的数就“浮”到了数组最后的位置上。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
/**
	 * 冒泡排序算法
	 * 原理是临近的数字两两进行比较,按照从小到大或者从大到小的顺序进行交换,这样一趟过去后,最大或最小的数字被交换到了最后一位,然后再从头开始进行两两比较交换,直到倒数第二位时结束
	 * @param nums 被排序的数组
	 * @param n 数组长度
	 * 最坏情况:O(n2)  如果未优化的冒泡排序,最好情况也是O(n2),优化后最好情况是O(n)
	 */
	public void BubbleSort(int[] nums,int len){
		for(int i=0;i<len-1;i++){ //一趟比较
			for(int j=0;j<<strong>len-1-i</strong>;j++){ //一次比较,每次把最大值放在最后面,所以下一次比较的时候不用对后i个数比较了
				int temp = nums[j];
				if(nums[j] > nums[j+1]){
					nums[j] = nums[j+1];
					nums[j+1] = temp;
				}
			}
		}
		System.out.println("冒泡排序:");
		for (int i : nums) {
			System.out.print(i+" ");
		}
	}

不过针对上述代码还有两种优化方案。

优化1:某一趟遍历如果没有数据交换,则说明已经排好序了,因此不用再进行迭代了。用一个标记记录这个状态即可(程序如下)。

优化2:记录某次遍历时最后发生数据交换的位置,这个位置之后的数据显然已经有序,不用再排序了。因此通过记录最后发生数据交换的位置就可以确定下次循环的范围了(上面程序中" j<=len-1-i" 实现)。

public void betterBubbleSort(int[] nums,int len){
		boolean flag;
		flag = true;
		for(int i=0;i<len-1 && flag;i++){ //一趟比较 如果上趟比较一次也没有交换,则说明后面都排好序了,不用再比较了
			flag = false;
			for(int j=0;j<len-1-i;j++){ //一次比较,每次把最大值放在最后面,所以下一次比较的时候不用对后i个数比较了
				int temp = nums[j];
				if(nums[j] > nums[j+1]){
					nums[j] = nums[j+1];
					nums[j+1] = temp;
					flag = true;
				}
			}
		}
		System.out.println("优化后的冒泡排序:");
		for (int i : nums) {
			System.out.print(i+" ");
		}
	}

二、选择排序
SelectionSort

介绍:

选择排序无疑是最简单直观的排序。它的工作原理如下。

步骤:

  1. 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
  2. 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
  3. 以此类推,直到所有元素均排序完毕。

<span style="font-size:14px;font-weight: normal;">/**
	 * 选择排序算法
	 * 基本思想是:每一趟从待排序的记录中选出关键字最小的记录,顺序放在已排好序的子序列前面,直到全部记录排序完毕。
	 * @param nums 被排序的数组
	 * @param n 数组长度
	 * 最坏情况:O(n2) ,最好情况:O(n2)
	 */
	public void SelectSort(int[] nums,int len){
		for(int i=0;i<len;i++){
			int min = nums[i]; //先把每一趟最小值暂定为nums[i]
			int index = i; //记录最小值所在的索引值
			for(int j=i+1;j<len;j++){ //开始一趟比较:从i+1位置到数组末尾比较,找出最小值并记录位置
				if(nums[j] < min){
					min = nums[j];
					index = j;
				}
			}
			//将nums[i]和最小值交换
			nums[index] = nums[i];
			nums[i] = min;
		}
		System.out.println("选择排序:");
		for (int i : nums) {
			System.out.print(i+" ");
		}
	}</span>

三、插入排序
InsertionSort

介绍:

插入排序的工作原理是,对于每个未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

步骤:

  1. 从第一个元素开始,该元素可以认为已经被排序
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
  3. 如果被扫描的元素(已排序)大于新元素,将该元素后移一位
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  5. 将新元素插入到该位置后
  6. 重复步骤2~5

排序演示:

/**
	 * 插入排序算法
	 * @param nums 被排序的数组
	 * @param n 数组长度
	 * 最坏情况:输入数组已反向排序  O(n2); 最好情况:输入数组已排序  O(n);平均情况:O(n2)
	 * 由于插入排序其内层循环非常紧凑,对于小规模输入,插入排序是一种非常快的排序算法
	 */
	public void InsertSort(int[] nums,int len){
		int j;
		for(int i=1;i<len;i++){
			int temp = nums[i];
			for(j=i;j>0;j--){
				if(temp < nums[j-1]){
					nums[j] = nums[j-1]; //将所有在nums[i]之前的大于nums[i]的值都往后移一位
				}
				else break;
			}
			//移完所有大于nums[i]的值后,j刚好指向最靠前一个大于nums[i]的位置
			nums[j] = temp;
		}
		System.out.println("插入排序:");
		for (int i : nums) {
			System.out.print(i+" ");
		}
	}

四、希尔排序
ShellSort

介绍:

希尔排序,也称递减增量排序算法,实质是分组插入排序。由 Donald Shell 于1959年提出。希尔排序是非稳定排序算法。

希尔排序的基本思想是:将数组列在一个表中并对列分别进行插入排序,重复这过程,不过每次用更长的列(步长更长了,列数更少了)来进行。最后整个表就只有一列了。将数组转换至表是为了更好地理解这算法,算法本身还是使用数组进行排序。

例如,假设有这样一组数[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ],如果我们以步长为5开始进行排序,我们可以通过将这列表放在有5列的表中来更好地描述算法,这样他们就应该看起来是这样:

13 14 94 33 82
25 59 94 65 23
45 27 73 25 39
10

然后我们对每列进行排序:

10 14 73 25 23
13 27 94 33 39
25 59 94 65 82
45

将上述四行数字,依序接在一起时我们得到:[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ]。这时10已经移至正确位置了,然后再以3为步长进行排序:

10 14 73
25 23 13
27 94 33
39 25 59
94 65 82
45

排序之后变为:

10 14 13
25 23 33
27 25 59
39 65 73
45 94 82
94

最后以1步长进行排序(此时就是简单的插入排序了)。

/**
	 * 希尔排序算法
	 * @param nums 被排序的数组
	 * @param n 数组长度
	 * 最坏情况:O(n2) 使用Hibbard增量的最坏情况:O(n^3/2)
	 */
	public void ShellSort(int[] nums,int len){
		int increment;
		int temp,i,j;
		for(increment = len/2;increment > 0;increment /= 2){ //增量为len/2 len/4 len/8.....1
			for(i=increment;i<len;i++){ //对每个子序列进行插入排序
				temp = nums[i];
				for(j=i;j>=increment;j -= increment){
					if(temp < nums[j-increment]){
						nums[j] = nums[j-increment];
					}
					else
						break;
				}
				nums[j] = temp;
			}
		}
		System.out.println("希尔排序:");
		for (int k : nums) {
			System.out.print(k+" ");
		}
	}

五、归并排序
MergeSort

介绍:

归并排序是采用分治法的一个非常典型的应用。归并排序的思想就是先递分解数组,再并数组。

先考虑合并两个有序数组,基本思路是比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。

再考虑递归分解,基本思路是将数组分解成leftright,如果这两个数组内部数据是有序的,那么就可以用上面合并数组的方法将这两个数组合并排序。如何让这两个数组内部是有序的?可以再二分,直至分解出的小组只含有一个元素时为止,此时认为该小组内部已有序。然后合并排序相邻二个小组即可。

排序演示:

/**
	 * 归并排序算法
	 * 将待排序序列R[0...n-1]看成是n个长度为1的有序序列,将相邻的有序表成对归并,得到n/2个长度为2的有序表;将这些有序序列再次归并,
	 * 得到n/4个长度为4的有序序列;如此反复进行下去,最后得到一个长度为n的有序序列。
	 * 综上可知:归并排序其实要做两件事:(1)“分解”——将序列每次折半划分。(2)“合并”——将划分后的序列段两两合并后排序。
	 * @param nums
	 * @param len
	 * O(nlogn)
	 */
	public void MergeSort(int[] nums,int len){
		sort(nums, 0, len-1);
		System.out.println("归并排序:");
		for (int k : nums) {
			System.out.print(k+" ");
		}
	}
	//分段 将一个数组分成2个数组
	public void sort(int[] nums,int low,int high){
		int mid = (high+low)/2;
		if(low < high){
			sort(nums, low, mid);
			sort(nums, mid+1, high);
			merge(nums, low, mid, high);
		}
	}
	// 分段排序 再合起来
	public void merge(int[] array,int low,int mid,int high){
		int i=low; //第一段序列下标
		int j=mid+1; //第二段序列下标
		int k=0; //第三段序列下标
		int[] array2 = new int[high - low + 1]; //新序列
		while(i<=mid && j<=high){ //把两个子序列的头元素比较,取较小者进入新序列,然后在旧序列中跳过这个较小值,开始下一次比较
			if(array[i] <= array[j]){
				array2[k++] = array[i++];
			}
			else{
				array2[k++] = array[j++];
			}
		}
		if(i <= mid){ //说明上面是j溢出退出循环,即序列1还未比较完
			while(i<=mid){ //若第一段序列还没扫描完,将其全部复制到合并序列
				array2[k++] = array[i++];
			}
		}
		else if(j <= high){
			while(j<=high){ //若第二段序列还没扫描完,将其全部复制到合并序列
				array2[k++] = array[j++];
			}
		}
		for(k=0;k<array2.length;k++){
			array[k+low] = array2[k];//将分段排序的新数组复制到之前数组
		}
	}

六、快速排序
QuickSort

介绍:

快速排序通常明显比同为Ο(n log n)的其他算法更快,因此常被采用,而且快排采用了分治法的思想,所以在很多笔试面试中能经常看到快排的影子。可见掌握快排的重要性。

步骤:

  1. 从数列中挑出一个元素作为基准数。
  2. 分区过程,将比基准数大的放到右边,小于或等于它的数都放到左边。
  3. 再对左右区间递归执行第二步,直至各区间只有一个数。

排序演示:

/**
	 * 快速排序算法
	 * 快速排序采用的思想是分治思想。快速排序是找出一个元素(理论上可以随便找一个)作为基准(pivot),
	 * 然后对数组进行分区操作,使基准左边元素的值都不大于基准值,基准右边的元素值 都不小于基准值,如此作为基准的元素调整到排序后的正确位置。
	 * 递归快速排序,将其他n-1个元素也调整到排序后的正确位置。最后每个元素都是在排序后的正 确位置,排序完成。
	 * 所以快速排序算法的核心算法是分区操作,即如何调整基准的位置以及调整返回基准的最终位置以便分治递归。
	 * @param nums
	 * @param len
	 * O(NlogN)
	 */
	public void QuickSort(int[] nums,int len){
		Qsort(nums,0,len-1);
		System.out.println("快速排序:");
		for (int k : nums) {
			System.out.print(k+" ");
		}
	}
	public void Qsort(int[] nums, int left, int right) {
		if(left>right) return;
		int pivot = nums[left];
		int i = left;
		int j = right;
		while(i < j){
			while(nums[j] >= pivot && i<j){ //从右往左找比nums[i]小的数
				j--;
			}
			nums[i] = nums[j]; //找到之后交换
			while(nums[i] <= pivot && i<j){ //从左往右找比nums[j]大的数
				i++;
			}
			nums[j]  = nums[i];	 //找到之后交换
		}
		//while执行完后,必然有 i==j
		nums[i] = pivot; //将基准数归位
		Qsort(nums, left, i-1); //递归处理左边
		Qsort(nums, i+1, right); //递归处理右边
	}

七、堆排序
HeapSort

介绍:

堆排序在 top K 问题中使用比较频繁。堆排序是采用二叉堆的数据结构来实现的,虽然实质上还是一维数组。二叉堆是一个近似完全二叉树 。

二叉堆具有以下性质:

  1. 父节点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
  2. 每个节点的左右子树都是一个二叉堆(都是最大堆或最小堆)。

步骤:

  1. 构造最大堆(Build_Max_Heap):若数组下标范围为0~n,考虑到单独一个元素是大根堆,则从下标n/2开始的元素均为大根堆。于是只要从n/2-1开始,向前依次构造大根堆,这样就能保证,构造到某个节点时,它的左右子树都已经是大根堆。
  2. 堆排序(HeapSort):由于堆是用数组模拟的。得到一个大根堆后,数组内部并不是有序的。因此需要将堆化数组有序化。思想是移除根节点,并做最大堆调整的递归运算。第一次将heap[0]heap[n-1]交换,再对heap[0...n-2]做最大堆调整。第二次将heap[0]heap[n-2]交换,再对heap[0...n-3]做最大堆调整。重复该操作直至heap[0]heap[1]交换。由于每次都是将最大的数并入到后面的有序区间,故操作完后整个数组就是有序的了。
  3. 最大堆调整(Max_Heapify):该方法是提供给上述两个过程调用的。目的是将堆的末端子节点作调整,使得子节点永远小于父节点 。

排序演示:

/**
	 * 堆排序算法(max堆)
	 * @param nums 被排序的数组
	 * @param n 数组长度
	 * 时间复杂度 O(nlogn)
	 * 要先构建最大堆,然后循环删除堆的根节点上的最大值,并将它移到堆末尾,并将堆长度减一,再开始下一次的删除根节点
	 */
	public void HeapSort(int[] nums,int len){
		//构建最大堆
		for(int i=len/2;i>=0;i--){
			percDown(nums,i,len);
		}
		//循环,每次把根节点和最后一个节点调换位置
		for(int i=len-1;i>=1;i--){
			//Swap(nums[0],nums[i])交换nums[0]和nums[i]
			int temp = nums[0];
			nums[0] = nums[i];
			nums[i] = temp;
			percDown(nums,0,i);
		}

		System.out.println("堆排序:");
		for (int k : nums) {
			System.out.print(k+" ");
		}
	}
		/**
		 * 堆调整,使其生成最大堆
		 * @param nums
		 * @param i
		 * @param size
		 */
		public void percDown(int[] nums,int i,int size){
			int left = 2*i+1;
			int right = 2*i+2;
			int max = i;
			if(left < size && nums[left] > nums[max]){
				max = left;
			}
			if(right < size && nums[right] > nums[max]){
				max = right;
			}
			if(max != i){
				int temp = nums[i];
				nums[i] = nums[max];
				nums[max] = temp;
				percDown(nums,max,size);
			}

		}

总结

下面为七种经典排序算法指标对比情况:

时间: 2024-10-24 02:49:48

七种经典排序算法最全攻略的相关文章

七种常用排序算法

七种常用排序算法 一.常见排序算法一览: 时间复杂度: 是一个函数,它定量描述了该算法的运行时间. 空间复杂度:一个算法在运行过程中临时占用存储空间大小的量度. 稳定性:保证排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同就稳定,反之不稳定. 视觉直观感受 7 种常用的排序算法 二.算法C#实现: 1. 直接插入排序: using System; using System.Collections.Generic; using System.Linq; using Sys

Windows Socket五种I/O模型——代码全攻略(转)

Winsock 的I/O操作: 1. 两种I/O模式 阻塞模式:执行I/O操作完成前会一直进行等待,不会将控制权交给程序.套接字 默认为阻塞模式.可以通过多线程技术进行处理. 非阻塞模式:执行I/O操作时,Winsock函数会返回并交出控制权.这种模式使用 起来比较复杂,因为函数在没有运行完成就进行返回,会不断地返回 WSAEWOULDBLOCK错误.但功能强大.为了解决这个问题,提出了进行I/O操作的一些I/O模型,下面介绍最常见的三种: Windows Socket五种I/O模型——代码全攻

Jerry 2017年的五一小长假:8种经典排序算法的ABAP实现

2017年4月29日~5月1日,国际劳动节, 三天的小长假. 在国内,小长假往往是这样的: 然而我当时在戏称为"德村"(德国农村)的Walldorf出差并且住在Wiesloch, 这里的五一小长假能听见鸟叫,虫鸣,和风吹过的声音,除此之外再无其他. 街道上别说行人了,连行驶的汽车都很少. 如果一个在成都习惯了热闹生活的人,到了这种乡下地方来估计会觉得百无聊赖.当时国内有同事建议我小长假去德国其他地方转转,然而作为一个30年资深宅男,一个人出去转不是我的风格. 五一放假之前,坐我对面的一

九种经典排序算法汇总

/*********************************************************** 总结各种排序算法包括但不限于: 1. 插入排序类 1.1 直接插入排序 1.2 二分插入排序 1.3 希尔排序 2. 交换排序类 2.1 冒泡排序 2.2 快速排序 3. 选择排序 3.1 直接选择排序 3.2 堆排序 4. 归并排序 5. 基数排序 以上所有排序算法的实现均为将整形数组data递增排序 ************************************

九种经典排序算法详解(冒泡排序,插入排序,选择排序,快速排序,归并排序,堆排序,计数排序,桶排序,基数排序)

综述 最近复习了各种排序算法,记录了一下学习总结和心得,希望对大家能有所帮助.本文介绍了冒泡排序.插入排序.选择排序.快速排序.归并排序.堆排序.计数排序.桶排序.基数排序9种经典的排序算法.针对每种排序算法分析了算法的主要思路,每个算法都附上了伪代码和C++实现. 算法分类 原地排序(in-place):没有使用辅助数据结构来存储中间结果的排序**算法. 非原地排序(not-in-place / out-of-place):使用了辅助数据结构来存储中间结果的排序算法 稳定排序:数列值(key)

java的几种经典排序算法

排序算法大致有直接插入排序.折半插入排序.Shell排序.归并排序.直接选择排序.堆排序.冒泡排序.快速排序.桶式排序.基数排序等这些种,各个算法都有其优异性,大家不妨自己看看.下面贴上每个算法的简单讲解和实现: 1.直接选择排序(DirectSelectSort):其关键就是对n个数据要进行n-1趟比较,每趟比较的目的就是选择出本趟比较中最小的数据,并将选择出的数据放在本趟中的第一位.实现如下: [java] view plaincopy <span style="font-size:1

几种经典排序算法的R语言描述

1.数据准备 # 测试数组 vector = c(5,34,65,36,67,3,6,43,69,59,25,785,10,11,14) vector ## [1] 5 34 65 36 67 3 6 43 69 59 25 785 10 11 14 2.R语言内置排序函数 在R中和排序相关的函数主要有三个:sort(),rank(),order(). sort(x)是对向量x进行排序,返回值排序后的数值向量; rank()是求秩的函数,它的返回值是这个向量中对应元素的“排名”; order()

8种经典排序算法总结

1.二分查找 代码: int binarySearch(int arr[],int l,int r,int x) { while(l <= r) { int m = l + (r-1)/2;//为了防止(l+r溢出) if(arr[m] == x) return m; if(arr[m] < x) l = m + 1; else r = m - 1; } return -1; } 问题扩展1:给定一个有序的数组,用最少的比较次数找出给定的元素. 代码: int binarySearch(int

(转载)Windows Socket五种I/O模型——代码全攻略

如果你想在Windows平台上构建服务器应用,那么I/O模型是你必须考虑的.Windows操作系统提供了选择(Select).异步选择(WSAAsyncSelect).事件选择(WSAEventSelect).重叠I/O(Overlapped I/O)和完成端口(Completion Port)共五种I/O模型.每一种模型均适用于一种特定的应用场景.程序员应该对自己的应用需求非常明确,而且综合考虑到程序的扩展性和可移植性等因素,作出自己的选择. 我会以一个回应反射式服务器(与<Windows网络