编程语言
首页 > 编程语言> > 十大经典排序算法解析与java实现(未完待续)

十大经典排序算法解析与java实现(未完待续)

作者:互联网

文章目录

参考资料

  1. 十大经典排序算法动画与解析,看我就够了!(配代码完全版)
  2. https://www.bilibili.com/video/BV1Kb411W75N
  3. 归并排序
  4. 图解排序算法(四)之归并排序

0. 基本概念

  1. 排序:是计算机程序设计中的一项重要操作,其功能是指一个数据元素集合或序列重新排列成一个按数据元素某个数据项值有序的序列。
  2. 排序码(关键码):排序依据的数据项。
  3. 稳定排序:排序前与排序后相同关键码元素间的位置关系,保持一致的排序方法。
  4. 不稳定排序:排序前与排序后相同关键码元素间的相对位置发生改变的排序方法
  5. 排序分为两类:
    1. 内排序:指待排序列完全存放在内存中所进行的排序。内排序大致可分为五类:插入排序、交换排序、选择排序、归并排序和分配排序。
    2. 外排序:指排序过程中还需访问外存储器的排序。

10大经典排序算法比较

在这里插入图片描述

1. 插入排序

1.1 直接插入排序

1.1.1 基本思想

n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含1个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
在这里插入图片描述

package pers.chh3213.sort;

import java.util.Arrays;

/**
* DirectInsertSort.java
* @Description 直接插入法
* @author chh3213
* @version
* @date 2021年12月26日上午11:19:07
 */
public class DirectInsertSort {
	public static void main(String[] args) {
		DirectInsertSort insertSort = new DirectInsertSort();
		int[] arr = {9, -16, 310, 23, -30, -49, 25, 21, 30};
		insertSort.directInsertSort(arr);
		System.out.println(Arrays.toString(arr));
	}
	public void directInsertSort(int[] arr) {
		// 法一
		for (int i = 1; i < arr.length; i++) {
				int temp = arr[i];           // 记录要插入的数据
				int j=i;
				for ( ; j>0&&arr[j-1]>temp; j--) {// 从已经排序的序列最右边的开始比较,找到比其小的数
					arr[j]=arr[j-1];
				}
				if(j!=i)arr[j]=temp;	
		}
		//法二
//		for (int i = 1; i < arr.length; i++) {
//			for(int j=i;j>0;j--) {
//				if(arr[j]<arr[j-1])swap(arr, j, j-1);
//			}
//		}
	}
	public void swap(int[] arr, int i, int j) {
		int temp =arr[i];
		arr[i]=arr[j];
		arr[j]=temp;
	}


}

1.1.2 直接插入排序效率分析

1.2 希尔排序(缩小增量排序)

1.2.1 基本思想

先将整个待排元素序列分割成若干个子序列(由相隔某个增量的元素组成的)分别进行直接插入排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况) ,效率是很高的,因此希尔排序在时间效率上有较大提高。

在这里插入图片描述

d1=8/2=4;
d2=4/2=2;
d3=2/2=1;

在这里插入图片描述

package pers.chh3213.sort;

import java.util.Arrays;

public class ShellSort {
	public static void main(String[] args) {
		ShellSort shell = new ShellSort();
		int[] arr = {9, -16, 310, 23, -30, -49, 25, 21, 30};
		shell.shellSort(arr);
		System.out.println(Arrays.toString(arr));
	}
	public void shellSort(int[] arr) {
		//第一次步长为数组长度/2,后面依次步长/2
		for (int step = arr.length /2; step >0; step /= 2) {
			//直接插入排序
			for (int i = step; i < arr.length; i++) {
				int temp = arr[i];//右侧待排序区第一个数
				int j=i-step;//左侧已排序区域第一个数索引
				for( ;j>=0&&arr[j]>temp;j-=step) {
					arr[j+step]=arr[j];//满足条件则把已排序数字往后挪一个位置
//					swap(arr, j, j+step);
				}
				//插入待排序数字到指定位置
				arr[j+step]=temp;
			}


		}
	}
	public void swap(int[] arr, int i, int j) {
		int temp =arr[i];
		arr[i]=arr[j];
		arr[j]=temp;
	}
}

1.2.2 希尔排序的效率分析

2. 交换排序

2.1 冒泡排序(Bubble Sort)

2.1.1 基本思想

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

在这里插入图片描述

package pers.chh3213.sort;
import java.util.Iterator;
import java.util.Scanner;
public class BubbleSort {
	public static void main(String[] args) {
		int[] arr = {5,4,1,966,2,3,56,89,12,0,56562};
		System.out.println("before sort:");
		for (int i : arr) {
			System.out.print(i+"\t");
		}
		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]) {
					int temp = arr[j];
					arr[j] = arr[j+1];
					arr[j+1] = temp;
				}
			}
		}
		System.out.println();
		System.out.println("after sort:");
		for (int i : arr) {
			System.out.print(i+"\t");
		}
	}
}

因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序, 因此可以在排序过程中设置一个标志swap判断元素是否进行过交换,从而减少不必要的比较。改进代码如下:

	public void bubbleSort(int[] data) {
		for (int i = 0; i < data.length-1; i++) {
			boolean swap = false;
			for (int j = 0; j < data.length-i-1; j++) {
				if(data[j]>data[j+1]) {
					int temp = data[j];
					data[j]= data[j+1];
					data[j+1] = temp;
					swap = true;
				}
			}
			if(!swap)break; //不交换时停止排序
		}
	}

2.1.2 冒泡排序的效率分析

2.2 快速排序(Quick Sort)

2.2.1 排序思想

1. 从数列中挑出一个元素,称为基准(pivot),一般取第一个元素;
2. 通过一次划分,将待排元素分为左右两个子序列,所有元素比基准值小的摆放在左序列,所有元素比基准值大的摆在右序列(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为**分区**(partition)操作;
3. 然后分别对两个子序列继续进行划分,直至每一个序列只有一个元素为止;
4. 最后得到的序列便是有序的序列。

(注:图片来源:参考资料1)

在这里插入图片描述

在这里插入图片描述

  1. low从前往后移动直到R[low].key>=R[0].key;
    在这里插入图片描述
  2. R[high]=R[low], high–;
    在这里插入图片描述
  3. goto 3;

在这里插入图片描述在这里插入图片描述在这里插入图片描述
8. 直到low==high时, R[low]=R[0] (即将作为标准的元素放到其最终位置)。
在这里插入图片描述- 示例

package pers.chh3213.sort;
public class QuickSort {
	public static void main(String[] args) {
		System.out.println("quick sort test");
		int[] arr = {9, -16, 30, 23, -30, -49, 25, 21, 30};
		System.out.println("before sort:");
		for (int i : arr) {
			System.out.print(i+"\t");
		}
		QuickSort quick = new QuickSort();
		quick.quickSort(arr, 0, arr.length-1);
		System.out.println();
		System.out.println("after sort:");
		for (int i : arr) {
			System.out.print(i+"\t");
		}

	}
	public  void quickSort(int[] arr,int start, int end) {
		if(start<end) {
			 int index = partition(arr, start, end); //将表一分为2
			 quickSort(arr, start, index-1); // 对左子序列进行快速排序
			 quickSort(arr, index+1, end); //对右子序列进行快速排序
		}

	}
//	一次划分
	public  int partition(int[] arr, int low,int high) {

		int base = arr[low]; //暂存基准元素到base
		while (low<high) {//从表的两端交替的向中间扫描
			while(low<high && arr[high]>=base)high--;//右端扫描
			if(low<high) {
				arr[low]=arr[high];//把比基准小的元素放到基准前面
				low++;
			}
			while(low<high && arr[low]< base)low++;//左端扫描
			if(low<high) {
				arr[high]=arr[low];//把比基准大的元素放到基准后面
				high--;
			}
		}
		arr[low] = base;//把基准元素放到最终位置

		return low;//返回基准元素所在的位置
	}
}

2.2.2 快速排序的递归树

2.2.3 快速排序的时间复杂度

2.2.4 快速排序的空间复杂度及稳定性

3. 选择排序

3.1 简单选择排序

3.1.1 基本过程

  1. 在一组元素R[i]R[n]中选择具有最小关键码的元素
  2. 若它不是这组元素中的第一个元素,则将它与这组元素中的第一个元素对调。
  3. 除去具有最小关键字的元素,在剩下的
    元素中重复第1、2步,直到剩余元素只有一个为止。

在这里插入图片描述

在这里插入图片描述

package pers.chh3213.sort;

import java.util.Arrays;

public class SelectSort {
	public static void main(String[] args) {
		SelectSort select = new SelectSort();
		int[] arr = {9, -16, 310, 23, -30, -49, 25, 21, 30};
		select.selectSort(arr);
		System.out.println(Arrays.toString(arr));
	}
	public void selectSort(int[] arr) {
		for (int i = 0; i < arr.length; i++) {//进行n-1趟排序
			int min = i;
			for (int j = i+1; j < arr.length; j++) {
				if(arr[min]>arr[j])min=j; // 记录目前能找到的最小值元素的下标
			}
			 // 找到最小值后,再将找到的最小值和i位置所在的值进行交换
			if(i!=min)swap(arr, i, min);
		}
	}
	public void swap(int[] arr, int i, int j) {
		int temp =arr[i];
		arr[i]=arr[j];
		arr[j]=temp;
	}
}


3.1.2 简单选择排序的效率分析

  1. 无论初始状态如何,在第i趟排序中选择最小关键码的元素,需做n-i次比较,因此总的比较次数为:
    ∑ i = 1 n − 1 n − i = n ( n − 1 ) / 2 = O ( n 2 ) \sum_{i=1}^{n-1}{n-i}=n(n-1)/2=O(n^2) ∑i=1n−1​n−i=n(n−1)/2=O(n2)(即时间复杂度)

  2. 最好情况:序列为正序时,移动次数为 0 0 0, 最坏情况:序列为反序时,每趟排序均要执行交换操作,总的移动次数取最大值 3 ( n − 1 ) 3(n-1) 3(n−1)。

  3. 由于在直接选择排序中存在着不相邻元素之间的互换,因此,直接选择排序是一种不稳定的排序方法。例如,给定排序码为3, 7, 3’, 2, 1,排序后的结果为1, 2, 3’, 3, 7

3.2 堆排序

3.2.1 堆的定义

3.2.2 堆排序的基本思想

  1. 建初始堆
    将排序码k1, k2, k, ..,kn表示成一棵完全二叉树,然后从第n/2个排序码(即树的最后一个非终端结点)开始筛选,使由该结点作根结点组成的子二叉树符合堆的定义,然后从第n/2-1个排序码重复刚才操作,直到第一个排序码止。这时候,该二叉树符合堆的定义,初始堆已经建立。

  2. 堆排序
    将堆中第一个结点(二叉树根结点)和最后一个结点的数据进行交换(k1,与kn,),再将 k 1 k_1 k1​~ k n − 1 k_{n-1} kn−1​,重新建堆,然后k1,和k_{n-2}交换,如此重复下去,每次重新建堆的元素个数不断减1,直到重新建堆的元素个数仅剩一个为止。这时堆排序已经完成,则排序码 k 1 , k 2 , k 3 , . . , k n k1, k2, k3, .., kn k1,k2,k3,..,kn已排成一个有序序列。

  3. 堆排序的两大步骤

    • 根据初始输入数据形成初始堆
    • 通过一系列的元素交换和重新调整堆进行排序。
  4. 堆排序的关键问题

    • 如何由一个无序序列建成一个堆?
    • 如何在输出堆顶元素之后,调整剩余元素,使之成为一个新的堆?

4. 二路归并排序

4.1 基本思想

在这里插入图片描述

package pers.chh3213.sort;

import java.util.Arrays;
/**
 *
* MergeSort.java
* @Description 归并排序
* @author chh3213
* @version
* @date 2021年12月26日下午4:54:53
 */
public class MergeSort {
	public static void main(String[] args) {
		MergeSort Sort = new MergeSort();
		int[] arr = {9, -16, 310, 23, -30, -49, 25, 21, 30};
		Sort.mergeSort(arr,0,arr.length-1);
		System.out.println(Arrays.toString(arr));
	}
	public void mergeSort(int[] arr,int left, int right) {
		int mid = (left+right)/2;
		if(left<right) {
			//递归
			mergeSort(arr, left, mid);//左边归并排序,使得左子序列有序
			mergeSort(arr, mid+1, right);//右边归并排序,使得右子序列有序
			//合并
			merge(arr, left, right, mid);//将两个有序子数组合并操作
		}
	}

	public void merge(int[] arr, int left,int right, int mid) {
		/*
		 * 将两个有序数组合并
		 */
		int[] temp = new int[right-left+1]; //建好一个临时数组
		int i=left; //左子序列索引(理解成指针)
		int j = mid+1;//右子序列索引(理解成指针)
		int k =0;//临时数组的索引(理解成指针)
		while(i<=mid && j<=right) {
			if(arr[i]<=arr[j]) {
				temp[k++]=arr[i++];
			}
			else {
				temp[k++]=arr[j++];
			}
		}
		while(i<=mid)temp[k++]=arr[i++];//将左子序列剩余元素填充进temp中
		while(j<=right)temp[k++]=arr[j++];//将右子序列剩余元素填充进temp中
		//将temp中的元素全部拷贝回原数组中
		for (int k2 = 0; k2 < temp.length; k2++) {
			arr[k2+left]=temp[k2];
		}
	}
}

4.2 效率分析

5. 基数排序

标签:arr,java,int,元素,未完待续,low,序列,排序
来源: https://blog.csdn.net/weixin_42301220/article/details/122141528