十大经典排序算法java版本(动画演示)

算法概述

算法分类

十种常见排序算法可以分为两大类:

  • 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
  • 非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。

在这里插入图片描述

算法复杂度

在这里插入图片描述
在这里插入图片描述

相关概念

  • 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
  • 不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
  • 时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
  • 空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。

1.冒泡排序(Bubble Sort)

冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

1.1算法描述

  • 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
  • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
  • 针对所有的元素重复以上的步骤,除了最后一个;
  • 重复步骤1~3,直到排序完成。

1.2动图演示
在这里插入图片描述在这里插入图片描述
1.3代码实现

package sort;
import java.util.Arrays;
public class test1
{
	public static void main(String[] args)
	{
		int arr[] =
		{
				3, 9, -1, 8, 5
		};

		//测试冒泡排序
		//		System.out.println("普通版本");
		//		bubbleSort(arr);

		System.out.println("升级版本");
		optimizeBubbleSort(arr);
	}

	//普通版冒泡排序   时间复杂度 O(n^2)
	public static void bubbleSort(int[] arr)
	{
		int temp = 0; // 临时变量
		for (int i = 0; i < arr.length - 1; i++)
		{

			for (int j = 0; j < arr.length - 1 - i; j++)
			{
				// 如果前面的数比后面的数大,则交换
				if (arr[j] > arr[j + 1])
				{
					temp = arr[j];
					arr[j] = arr[j + 1];
					arr[j + 1] = temp;
				}
			}
			System.out.println("第" + (i + 1) + "趟排序后的数组");
			System.out.println(Arrays.toString(arr));
		}
	}

	//优化版冒泡排序
	public static void optimizeBubbleSort(int[] arr)
	{
		// 冒泡排序 的时间复杂度 O(n^2), 自己写出
		int temp = 0; // 临时变量
		boolean flag = false; // 标识变量,表示是否进行过交换
		for (int i = 0; i < arr.length - 1; i++)
		{

			for (int j = 0; j < arr.length - 1 - i; j++)
			{
				// 如果前面的数比后面的数大,则交换
				if (arr[j] > arr[j + 1])
				{
					flag = true;
					temp = arr[j];
					arr[j] = arr[j + 1];
					arr[j + 1] = temp;
				}
			}
			System.out.println("第" + (i + 1) + "趟排序后的数组");
			System.out.println(Arrays.toString(arr));

			if (!flag)
			{ // flag为false,说明在一趟排序中没有进入交换环节 --->已经有序
				break;
			}
			else
			{
				flag = false; // 重置flag!!!, 进行下次判断
			}
		}
	}

}

1.4结果分析
普通版本:对于每一个无序数组都需要进行n-1趟排序
优化版本:当中途某一趟没有进行任何交换,说明数组已经有序了,排序完成
在这里插入图片描述


2.选择排序(Selection Sort)

选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

2.1算法描述
n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下:

  • 初始状态:无序区为R[1…n],有序区为空;
  • 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R(i…n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1…i]和R[i+1…n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
  • n-1趟结束,数组有序化了。

2.2动图演示
在这里插入图片描述在这里插入图片描述

2.3代码实现

//选择排序
	public static void selectSort(int[] arr)
	{
		//在推导的过程,我们发现了规律,因此,可以使用for来解决
		//选择排序时间复杂度是 O(n^2)
		for (int i = 0; i < arr.length - 1; i++)
		{
			int minIndex = i;
			int min = arr[i];
			for (int j = i + 1; j < arr.length; j++)
			{
				if (min > arr[j])//注意如果要求从大到小排序,只修改min < arr[j]
				{ // 说明假定的最小值,并不是最小
					min = arr[j]; // 重置min
					minIndex = j; // 重置minIndex
				}
			}
			// 将最小值,放在arr[0], 即交换
			if (minIndex != i)
			{
				arr[minIndex] = arr[i];
				arr[i] = min;
			}
			System.out.println("第"+(i+1)+"轮后~~");
			System.out.println(Arrays.toString(arr));// 1, 34, 119, 101
		}
	}

2.4算法分析
表现最稳定的排序算法之一,因为无论什么数据进去都是O(n2)的时间复杂度,所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。理论上讲,选择排序可能也是平时排序一般人想到的最多的排序方法了吧。
在这里插入图片描述


3.插入排序(Insertion Sort)

插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
3.1算法描述
一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:

  • 从第一个元素开始,该元素可以认为已经被排序;
  • 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  • 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  • 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  • 将新元素插入到该位置后;
  • 重复步骤2~5。
    3.2动图演示
    在这里插入图片描述在这里插入图片描述
    3.3代码实现
	//插入排序
	public static void insertSort(int[] arr)
	{
		int insertVal = 0;
		int insertIndex = 0;
		//使用for循环来把代码简化
		for (int i = 1; i < arr.length; i++)
		{
			//定义待插入的数
			insertVal = arr[i];
			insertIndex = i - 1; // 即arr[1]的前面这个数的下标

			// 给insertVal 找到插入的位置
			// 说明
			// 1. insertIndex >= 0 保证在给insertVal 找插入位置,不越界
			// 2. insertVal < arr[insertIndex] 待插入的数,还没有找到插入位置
			// 3. 就需要将 arr[insertIndex] 后移
			while (insertIndex >= 0 && insertVal < arr[insertIndex])
			{
				arr[insertIndex + 1] = arr[insertIndex];// arr[insertIndex]
				insertIndex--;
			}
			// 当退出while循环时,说明插入的位置找到, insertIndex + 1

			arr[insertIndex + 1] = insertVal;

			System.out.println("第" + i + "轮插入");
			System.out.println(Arrays.toString(arr));
		}
	}

3.4 算法分析
插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
在这里插入图片描述


4.希尔排序(Shell Sort)

1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。
4.1算法描述
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:

  • 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  • 按增量序列个数k,对序列进行k 趟排序;
  • 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
    4.2.1动图演示
    在这里插入图片描述在这里插入图片描述
    4.2.2过程图示
    在这里插入图片描述

4.3代码实现
1.交换法:

	// 希尔排序时, 对有序序列在插入时采用交换法, 
	// 思路(算法) ===> 代码
	public static void shellSort(int[] arr) {
		
		int temp = 0;
		int count = 0;
		// 根据前面的逐步分析,使用循环处理
		for (int gap = arr.length / 2; gap > 0; gap /= 2) {
			for (int i = gap; i < arr.length; i++) {
				// 遍历各组中所有的元素(共gap组,每组有个元素), 步长gap
				for (int j = i - gap; j >= 0; j -= gap) {
					// 如果当前元素大于加上步长后的那个元素,说明交换
					if (arr[j] > arr[j + gap]) {
						temp = arr[j];
						arr[j] = arr[j + gap];
						arr[j + gap] = temp;
					}
				}
			}
			System.out.println("希尔排序第" + (++count) + "轮 =" + Arrays.toString(arr));
		}
//以下是每一轮循环的过程分析
		/*
		
		// 希尔排序的第1轮排序
		// 因为第1轮排序,是将10个数据分成了 5组
		for (int i = 5; i < arr.length; i++) {
			// 遍历各组中所有的元素(共5组,每组有2个元素), 步长5
			for (int j = i - 5; j >= 0; j -= 5) {
				// 如果当前元素大于加上步长后的那个元素,说明交换
				if (arr[j] > arr[j + 5]) {
					temp = arr[j];
					arr[j] = arr[j + 5];
					arr[j + 5] = temp;
				}
			}
		}
		
		System.out.println("希尔排序1轮后=" + Arrays.toString(arr));//
		
		
		// 希尔排序的第2轮排序
		// 因为第2轮排序,是将10个数据分成了 5/2 = 2组
		for (int i = 2; i < arr.length; i++) {
			// 遍历各组中所有的元素(共5组,每组有2个元素), 步长5
			for (int j = i - 2; j >= 0; j -= 2) {
				// 如果当前元素大于加上步长后的那个元素,说明交换
				if (arr[j] > arr[j + 2]) {
					temp = arr[j];
					arr[j] = arr[j + 2];
					arr[j + 2] = temp;
				}
			}
		}

		System.out.println("希尔排序2轮后=" + Arrays.toString(arr));//

		// 希尔排序的第3轮排序
		// 因为第3轮排序,是将10个数据分成了 2/2 = 1组
		for (int i = 1; i < arr.length; i++) {
			// 遍历各组中所有的元素(共5组,每组有2个元素), 步长5
			for (int j = i - 1; j >= 0; j -= 1) {
				// 如果当前元素大于加上步长后的那个元素,说明交换
				if (arr[j] > arr[j + 1]) {
					temp = arr[j];
					arr[j] = arr[j + 1];
					arr[j + 1] = temp;
				}
			}
		}

		System.out.println("希尔排序3轮后=" + Arrays.toString(arr));//
		*/
	}

2.移位法(效率更高,但是难理解)

//对交换式的希尔排序进行优化->移位法
	public static void shellSort2(int[] arr) {
		
		// 增量gap, 并逐步的缩小增量
		for (int gap = arr.length / 2; gap > 0; gap /= 2) {
			// 从第gap个元素,逐个对其所在的组进行直接插入排序
			for (int i = gap; i < arr.length; i++) {
				int j = i;
				int temp = arr[j];
				if (arr[j] < arr[j - gap]) {
					while (j - gap >= 0 && temp < arr[j - gap]) {
						//移动
						arr[j] = arr[j-gap];
						j -= gap;
					}
					//当退出while后,就给temp找到插入的位置
					arr[j] = temp;
				}

			}
		}
	}

4.4 算法分析
希尔排序的核心在于间隔序列的设定。既可以提前设定好间隔序列,也可以动态的定义间隔序列。动态定义间隔序列的算法是《算法(第4版)》的合著者Robert Sedgewick提出的。 
在这里插入图片描述

5.归并排序(Merge Sort)

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
5.1算法描述

  • 把长度为n的输入序列分成两个长度为n/2的子序列;
  • 对这两个子序列分别采用归并排序;
  • 将两个排序好的子序列合并成一个最终的排序序列

5.2.1动图演示
在这里插入图片描述在这里插入图片描述
5.2.2过程图示
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述在这里插入图片描述

5.3代码实现

package sort;
import java.util.Arrays;

public class MergeSort
{

	public static void main(String[] args)
	{
		int arr[] =
		{
				8, 4, 5, 7, 1, 3, 6, 2
		};
		int temp[] = new int[arr.length];//归并排序需要一个额外空间
		mergeSort(arr, 0, arr.length - 1, temp);
		System.out.println("归并排序后=" + Arrays.toString(arr));
	}

	//分+合方法
	public static void mergeSort(int[] arr, int left, int right, int[] temp)
	{
		if (left < right)
		{
			int mid = (left + right) / 2; //中间索引
			//向左递归进行分解
			mergeSort(arr, left, mid, temp);
			//向右递归进行分解
			mergeSort(arr, mid + 1, right, temp);
			//合并
			merge(arr, left, mid, right, temp);

		}
	}

	//合并的方法
	/**
	 * 
	 * @param arr   排序的原始数组
	 * @param left  左边有序序列的初始索引
	 * @param mid   中间索引
	 * @param right 右边索引
	 * @param temp  做中转的数组
	 */
	public static void merge(int[] arr, int left, int mid, int right,
			int[] temp)
	{

		int i = left; // 初始化i, 左边有序序列的初始索引
		int j = mid + 1; //初始化j, 右边有序序列的初始索引
		int t = 0; // 指向temp数组的当前索引

		//(一)
		//先把左右两边(有序)的数据按照规则填充到temp数组
		//直到左右两边的有序序列,有一边处理完毕为止
		while (i <= mid && j <= right)
		{//继续
			//如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
			//即将左边的当前元素,填充到 temp数组 
			//然后 t++, i++
			if (arr[i] <= arr[j])
			{
				temp[t] = arr[i];
				t += 1;
				i += 1;
			}
			else
			{ //反之,将右边有序序列的当前元素,填充到temp数组
				temp[t] = arr[j];
				t += 1;
				j += 1;
			}
		}

		//(二)
		//把有剩余数据的一边的数据依次全部填充到temp
		while (i <= mid)
		{ //左边的有序序列还有剩余的元素,就全部填充到temp
			temp[t] = arr[i];
			t += 1;
			i += 1;
		}

		while (j <= right)
		{ //右边的有序序列还有剩余的元素,就全部填充到temp
			temp[t] = arr[j];
			t += 1;
			j += 1;
		}

		//(三)
		//将temp数组的元素拷贝到arr
		//注意,并不是每次都拷贝所有
		t = 0;
		int tempLeft = left; // 
		//第一次合并 tempLeft = 0 , right = 1 //  tempLeft = 2  right = 3 // tL=0 ri=3
		//最后一次 tempLeft = 0  right = 7
		System.out.println("templeft=" + tempLeft + " right=" + right);//此语句为了方便看出排序过程,可删掉
		while (tempLeft <= right)
		{
			arr[tempLeft] = temp[t];
			t += 1;
			tempLeft += 1;
		}

	}

}

5.4 算法分析
在这里插入图片描述
归并排序是一种稳定的排序方法。和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(nlogn)的时间复杂度。代价是需要额外的内存空间。

6.快速排序(Quick Sort)

快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
6.1算法描述
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:

  • 从数列中挑出一个元素,称为 “基准”(pivot);
  • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  • 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

6.2.1动图演示
在这里插入图片描述在这里插入图片描述
6.2.2过程图示
在这里插入图片描述在这里插入图片描述
6.3代码实现
代码一:

package sort;

import java.util.Arrays;

public class Test6
{

	public static void main(String[] args)
	{
		int[]arr= {-9,78,30,0,23,-567,70};
		quickSort(arr, 0, arr.length-1);
		System.out.println("arr="+Arrays.toString(arr));
	}
	public static void quickSort(int[] arr, int left, int right)
	{
		int l = left; //左下标
		int r = right; //右下标
		//pivot 中轴值
		int pivot = arr[(left + right) / 2];
		int temp = 0; //临时变量,作为交换时使用
		//while循环的目的是让比pivot 值小放到左边
		//比pivot 值大放到右边
		while (l < r)
		{
			//在pivot的左边一直找,找到大于等于pivot值,才退出
			while (arr[l] < pivot)
			{
				l += 1;
			}
			//在pivot的右边一直找,找到小于等于pivot值,才退出
			while (arr[r] > pivot)
			{
				r -= 1;
			}
			//如果l >= r说明pivot 的左右两的值,已经按照左边全部是
			//小于等于pivot值,右边全部是大于等于pivot值
			if (l >= r)
			{
				break;
			}

			//交换
			temp = arr[l];
			arr[l] = arr[r];
			arr[r] = temp;

			//如果交换完后,发现这个arr[l] == pivot值 相等 r--, 前移
			if (arr[l] == pivot)
			{
				r -= 1;
			}
			//如果交换完后,发现这个arr[r] == pivot值 相等 l++, 后移
			if (arr[r] == pivot)
			{
				l += 1;
			}
		}

		// 如果 l == r, 必须l++, r--, 否则为出现栈溢出
		if (l == r)
		{
			l += 1;
			r -= 1;
		}
		//向左递归
		if (left < r)
		{
			quickSort(arr, left, r);
		}
		//向右递归
		if (right > l)
		{
			quickSort(arr, l, right);
		}
	}
}

代码二(与动画对应):

package sort;

import java.util.Arrays;

public class Test6_1
{
	public static void main(String[] args)
	{
		//给出无序数组
		int arr[] =
		{
				72, 6, 57, 88, 60, 42, 83, 73, 48, 85
		};

		//输出无序数组
		System.out.println(Arrays.toString(arr));
		//快速排序
		quickSort(arr);
		//partition(arr,0,arr.length-1);
		//输出有序数组
		System.out.println(Arrays.toString(arr));
	}

	public static void quickSort(int[] arr)
	{
		int low = 0;
		int high = arr.length - 1;
		quickSort(arr, low, high);
	}

	private static int partition(int[] arr, int low, int high)
	{
		//指定左指针i和右指针j
		int i = low;
		int j = high;

		//将第一个数作为基准值。挖坑
		int x = arr[low];

		//使用循环实现分区操作
		while (i < j)
		{//5  8
			//1.从右向左移动j,找到第一个小于基准值的值 arr[j]
			while (arr[j] >= x && i < j)
			{
				j--;
			}
			//2.将右侧找到小于基准数的值加入到左边的(坑)位置, 左指针想中间移动一个位置i++
			if (i < j)
			{
				arr[i] = arr[j];
				i++;
			}
			//3.从左向右移动i,找到第一个大于等于基准值的值 arr[i]
			while (arr[i] < x && i < j)
			{
				i++;
			}
			//4.将左侧找到的打印等于基准值的值加入到右边的坑中,右指针向中间移动一个位置 j--
			if (i < j)
			{
				arr[j] = arr[i];
				j--;
			}
		}

		//使用基准值填坑,这就是基准值的最终位置
		arr[i] = x;//arr[j] = y;
		//返回基准值的位置索引
		return i; //return j;
	}

	private static void quickSort(int[] arr, int low, int high)
	{//???递归何时结束
		if (low < high)
		{
			//分区操作,将一个数组分成两个分区,返回分区界限索引
			int index = partition(arr, low, high);
			//对左分区进行快排
			quickSort(arr, low, index - 1);
			//对右分区进行快排
			quickSort(arr, index + 1, high);
		}

	}

}

发布了88 篇原创文章 · 获赞 27 · 访问量 5908

猜你喜欢

转载自blog.csdn.net/weixin_43362002/article/details/104181632
今日推荐