最近发现了一个挺厉害的人工智能学习网站,内容通俗易懂,风趣幽默,感兴趣的可以点击此链接进行查看:床长人工智能教程
废话不多说,请看正文!
排序的基本概念
排序的稳定性
假设Ki = Kj(1<= i <= n, 1<=j<=n, i != j ),且在排序前的序列中Ri 领先于 Rj (即 i < j)
若在排序后的序列中 Ri 仍然领先于 Rj ,则称所用的排序方法是稳定的
若可能使排序后的序列中 Rj 领先于 Ri, 则称所用排序方法使不稳定的。
内部排序:指的是待排序记录全部存放在计算机内存中进行排序的过程。
外部排序:指的是待排序记录的数量很大,以致内存一次不能容纳全部记录,在排序过程中尚需对外存进访问的排序过程。
内部排序分类
- 插入类:将无序子序列中的一个或几个记录“插入”到有序序列中,从而增加记录的有序子序列的长度。主要包括直接插入排序,折半插入排序和希尔排序。
- 交换类:通过“交换”无序序列中的记录从而得到其中关键字最小或最大的记录,并将它加入到有序子序列中,以此方法增加记录的有序子序列的长度。主要包括冒泡排序和快速排序。
- 选择类:从记录的无序子序列中“选择”关键字最小或最大的记录,并将它加入到有序子序列中,以此方法增加记录的有序子序列的长度。主要包括简单选择排序、树型选择排序和堆排序。
- 归并类:通过“归并”两个或两个以上的记录有序子序列,逐步增加记录有序序列的长度。2-路归并排序是最为常见的归并排序方法。
- 分配类:是唯一一类不需要进行关键字之间比较的排序方法,排序时主要利用分配和收集两种基本操作来完成。基数排序是主要的分配类排序方法
十大排序算法
1、 冒泡排序
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
- 什么时候最快?
当输入的数据已经是正序时。
- 什么时候最慢?
当输入的数据是反序时。
平均复杂度 O(n²)
最好情况 O(n)
最坏情况 O(n²)
空间复杂度O(1);
稳定性 稳定
import java.util.Arrays;
public class BubbleSort {
public static void main(String[] args) {
int arr[] = {101, 34, 119, 1, -1, 90, 123};
bubbleSort(arr);
System.out.println(Arrays.toString(arr));
}
/**
* 将前面额冒泡排序算法,封装成一个方法
* @param arr
*/
public static void bubbleSort(int[] arr) {
/**
* temp 临时变量
* 冒泡排序 的时间复杂度 O(n^2)
* flag 标识变量,表示是否进行过交换
*/
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;
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
/**
* 在一趟排序中,一次交换都没有发生过
*/
if (!flag) {
break;
} else {
/**
* 重置flag!!!, 进行下次判断
*/
flag = false;
}
}
}
}
2、选择排序
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
重复第二步,直到所有元素均排序完毕。
平均复杂度 O(n²)
最好情况 O(n²)
最坏情况 O(n²)
空间复杂度O(1);
稳定性 不稳定
import java.util.Arrays;
/**
* 选择排序
*/
public class SelectSort {
public static void main(String[] args) {
int [] arr = {101, 34, 119, 1, -1, 90, 123};
selectSort(arr);
System.out.println(Arrays.toString(arr));
}
/**
* 选择排序
* @param arr
*/
public static void selectSort(int[] arr) {
/**
* 选择排序时间复杂度是 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++) {
/**
* 说明假定的最小值,并不是最小
* 重置min
* 重置minIndex
*/
if (min > arr[j]) {
min = arr[j];
minIndex = j;
}
}
/**
* 将最小值,放在arr[0], 即交换
*/
if (minIndex != i) {
arr[minIndex] = arr[i];
arr[i] = min;
}
}
}
}
3、插入排序
将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素
当成是未排序序列。从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。
(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等
元素的后面。)
平均复杂度 O(n²)
最好情况 O(n)
最坏情况 O(n²)
空间复杂度O(1);
稳定性 稳定
import java.util.Arrays;
public class InsertSort {
public static void main(String[] args) {
int[] arr = {101, 34, 119, 1, -1, 90, 123};
/**
* 调用插入排序算法
*/
insertSort(arr);
System.out.println(Arrays.toString(arr));
}
/**
* 插入排序
* @param arr
*/
public static void insertSort(int[] arr) {
for (int i = 1; i < arr.length; i++){
for(int j = i; j > 0; j--){
if(arr[j-1] > arr[j]){
int temp = arr[j-1];
arr[j-1] = arr[j];
arr[j] = temp;
}else{
break;
}
}
}
}
}
4、希尔排序
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希
尔排序是非稳定排序算法。
希尔排序按照下标增量分组,对每组使用插入排序算法,随着增量的减少,每组
包含的元素越来越多,当增量减到1时,整个元素被分成一组。
平均复杂度 O(nlogn)
最好情况 O(n log²n)
最坏情况 O(n log²n)
空间复杂度O(1);
稳定性 不稳定
import java.util.Arrays;
public class ShellSort {
public static void main(String[] args) {
int[] arr = {101, 34, 119, 1, -1, 90, 123};
sort(arr);
System.out.println(Arrays.toString(arr));
}
/**
* 对数组a中的元素进行排序
* @param arr
*/
public static void sort(int[] arr){
/**
* 1.根据数组a的长度,确定增长量h的初始值;
*/
int h = 1;
while(h < arr.length / 2){
h = 2*h+1;
}
/**
* 2.希尔排序
*/
while(h >= 1){
/**
* 找到待插入的元素
*/
for (int i = h; i<arr.length; i++){
/**
* 把待插入的元素插入到有序数列中
*/
for (int j = i;j >= h;j -= h){
/**
* 待插入的元素是a[j],比较a[j]和a[j-h]
*/
if(arr[j-h] > arr[j]){
int temp = arr[j-h];
arr[j-h] = arr[j];
arr[j] = temp;
}else {
/**
* 待插入元素已经找到了合适的位置,结束循环;
*/
break;
}
}
}
/**
* 减小h的值
*/
h= h/2;
}
}
}
5、归并排序
1、申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
2、设定两个指针,最初位置分别为两个已经排序序列的起始位置;
3、比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
4、重复步骤 3 直到某一指针达到序列尾;
5、将另一序列剩下的所有元素直接复制到合并序列尾。
平均复杂度 O(nlogn)
最好情况 O(nlogn)
最坏情况 O(nlogn)
空间复杂度O(n);
稳定性 稳定
import java.util.Arrays;
//排序代码
public class MergeSort {
/**
* 归并所需要的辅助数组
*/
private static int[] assist;
public static void main(String[] args) {
int[] arr = {101, 34, 119, 1, -1, 90, 123};
sort(arr);
System.out.println(Arrays.toString(arr));
}
/**
* 对数组a中的元素进行排序
* @param arr
*/
public static void sort(int[] arr) {
/**
* 初始化辅助数组
*/
assist = new int[arr.length];
int left = 0;
int right = arr.length - 1;
sort(arr, left, right);
}
/**
* 对数组a中从left到hi的元素进行排序
* @param arr
* @param left
* @param right
*/
private static void sort(int[] arr, int left, int right) {
if (left >= right){
return;
}
int mid = left + (right - left) / 2;
/**
* 对left到mid之间的元素进行排序;
*/
sort(arr, left, mid);
/**
* 对mid+1到hi之间的元素进行排序;
*/
sort(arr, mid+1, right);
/**
* 对left到mid这组数据和mid到hi这组数据进行归并
*/
merge(arr, left, mid, right);
}
/**
*对数组中,从left到mid为一组,从mid+1到hi为一组,对这两组数据进行归并
* @param arr
* @param left
* @param mid
* @param right
*/
private static void merge(int[] arr, int left, int mid, int right) {
/**
* left到mid这组数据和mid+1到hi这组数据归并到辅助数组assist对应的索引处
* i 定义一个指针,指向assist数组中开始填充数据的索引
* p1 定义一个指针,指向第一组数据的第一个元素
* p2 定义一个指针,指向第二组数据的第一个元素
*/
int i = left;
int p1 = left;
int p2 = mid + 1;
/**
* 比较左边小组和右边小组中的元素大小,哪个小,就把哪个数据填充到assist数组中
*/
while (p1 <= mid && p2 <= right) {
if(arr[p1] < arr[p2]){
assist[i++] = arr[p1++];
}else {
assist[i++] = arr[p2++];
}
}
/**
* 上面的循环结束后,如果退出循环的条件是p1<=mid,则证明左边小组中的数据已经归并完毕,如果退出循环的条件是p2<=hi,则证明右边小组的数据已经填充完毕;
* 所以需要把未填充完毕的数据继续填充到assist中,//下面两个循环,只会执行其中的一个
*/
while(p1<=mid){
assist[i++]=arr[p1++];
}
while(p2<=right){
assist[i++]=arr[p2++];
}
/**
* 到现在为止,assist数组中,从left到hi的元素是有序的,再把数据拷贝到a数组中对应的索引处
*/
for (int index=left;index<=right;index++){
arr[index]=assist[index];
}
}
}
6、快速排序
从数列中挑出一个元素,称为 "基准"(pivot);
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
平均复杂度 O(nlogn)
最好情况 O(nlogn)
最坏情况 O(n²)
空间复杂度O(logn);
稳定性 不稳定
package com.yxj.mianshi.algorithm;
import java.util.Arrays;
public class QuickSort3 {
public static void main(String[] args) {
int[] nums = {101, 34,3,5,3,5,2,78,1100};
quickSort(nums,0,nums.length - 1);
System.out.println(Arrays.toString(nums));
}
public static void quickSort(int[] nums, int left, int right){
if (nums == null || nums.length == 0){
return;
}
if (left < right){
int i = left;
int j = right;
int pivot = nums[left];
while (i < j){
while (i < j && nums[j] >= pivot)
{
j--;
}
swap(nums,i,j);
while (i < j && nums[i] <= pivot){
i++;
}
swap(nums,i,j);
}
quickSort(nums,left,i - 1);
quickSort(nums, i + 1,right);
}
}
public static void swap(int[] nums,int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
7、计数排序
(1)找出待排序的数组中最大和最小的元素
(2)统计数组中每个值为i的元素出现的次数,存入数组的第i项
(3)对所有的计数累加(从数组中的第一个元素开始,每一项和前一项相加)
(4)反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
平均复杂度 O(n + k)
最好情况 O(n + k)
最坏情况 O(n + k)
空间复杂度O(n);
稳定性 稳定
import java.util.Arrays;
public class CountSort {
public static void main(String[] args) {
int arr[] = {101, 34, 119, 1, 90, 123, 130};
countSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void countSort(int[] arr){
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max){
max = arr[i];
}
}
int min = arr[0];
for (int i = 1; i < arr.length; i++){
if (arr[i] < min){
min = arr[i];
}
}
/**
* 创建计数数组
*/
int[] count = new int[max - min + 1];
for(int i = 0; i < arr.length; i++){
count[arr[i]-min]++;
}
int k = 0;
for(int i = 0; i < count.length; i++){
while (count[i] > 0){
arr[k++] = i + min;
count[i]--;
}
}
}
}
8、桶排序
把数据放在桶里,桶里排好序,遍历桶即可
平均复杂度 O(n + k)
最好情况 O(n + k)
最坏情况 O(n²)
空间复杂度O(n + k);
稳定性 稳定
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
public class BucketSort {
public static void main(String[] args) {
int arr[] = {101, 34, 119, 1, 90, 123, 130};
bucketSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void bucketSort(int[] arr){
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max){
max = arr[i];
}
}
int min = arr[0];
for (int i = 1; i < arr.length; i++){
if (arr[i] < min){
min = arr[i];
}
}
/**
* 确定桶的数量
*/
ArrayList<ArrayList<Integer>> buckets = new ArrayList<>();
int count = (max - min) / arr.length + 1;
for(int i = 0; i < count; i++){
buckets.add(new ArrayList<Integer>());
}
for(int i = 0; i < arr.length; i++){
buckets.get((arr[i] - min) / arr.length).add(arr[i]);
}
for(int i = 0; i< buckets.size(); i++){
Collections.sort(buckets.get(i));
}
/***
* 遍历桶里的每个数据,输出到arr
*/
int k = 0;
for(int i = 0; i < buckets.size(); i++){
ArrayList<Integer> list = buckets.get(i);
for (int j = 0; j < list.size(); j++){
arr[k++] = list.get(j);
}
}
}
}
9、基数排序
基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的
数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日
期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。基数排序:根据键值的每位数字来分配桶;
平均复杂度 O(n * k)
最好情况 O(n * k)
最坏情况 O(n * k)
空间复杂度O(n + k);
排序方式 Out-place
稳定性 稳定
import java.util.Arrays;
public class RadixSort {
public static void main(String[] args) {
int arr[] = {101, 34, 119, 1, 90, 123,0,0,1};
radixSort(arr);
System.out.println(Arrays.toString(arr));
}
/**
* 基数排序方法
* @param arr
*/
public static void radixSort(int[] arr) {
/**
* 得到数组中最大的数的位数
* 假设第一数就是最大数
*/
int max = arr[0];
for(int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
//得到最大数是几位数
int maxLength = (max + "").length();
/**
* 定义一个二维数组,表示10个桶, 每个桶就是一个一维数组
* 二维数组包含10个一维数组
* 为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.length
*/
int[][] bucket = new int[10][arr.length];
/**
* 为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数
* m[0] , 记录的就是 bucket[0] 桶的放入数据个数
*/
int[] m = new int[10];
for(int i = 0 , n = 1; i < maxLength; i++, n *= 10) {
for(int j = 0; j < arr.length; j++) {
/**
* 取出每个元素的对应位的值
*/
int temp = arr[j] / n % 10;
/**
* 放入到对应的桶中
*/
bucket[temp][m[temp]] = arr[j];
m[temp]++;
}
/**
* 按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
*/
int index = 0;
/**
* 遍历每一桶,并将桶中是数据,放入到原数组
*/
for(int k = 0; k < m.length; k++) {
/**
* 如果桶中,有数据,我们才放入到原数组
*/
if(m[k] != 0) {
/**
* 循环该桶即第k个桶(即第k个一维数组), 放入
*/
for(int l = 0; l < m[k]; l++) {
/**
* 取出元素放入到arr
*/
arr[index++] = bucket[k][l];
}
}
/**
* 第i+1轮处理后,需要将每个 m[k] = 0 !!!!
*/
m[k] = 0;
}
}
}
}
10、堆排序
创建一个堆 H[0……n-1];
把堆首(最大值)和堆尾互换;
平均复杂度 O(nlongn)
最好情况 O(nlogn)
最坏情况 O(nlogn)
空间复杂度O(1);
稳定性 不稳定
import java.util.Arrays;
public class HeapSort {
public static void main(String[] args) {
int arr[] = {101, 34, 119, 1, -1, 90, 123};
heapSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void heapSort(int[] arr) {
// 初始化建堆
for (int i = arr.length / 2 - 1; i >= 0; i--){
heapify(arr,i,arr.length - 1);
}
for (int i = arr.length - 1; i > 0; i--){
// 交换当前节点和堆顶节点
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
heapify(arr,0,i - 1);
}
}
public static void heapify(int[] arr, int i, int last_index) {
int max = i;
if (2 * i + 1 <= last_index && arr[2 * i + 1] > arr[max]){
max = 2 * i + 1;
}
if (2 * i + 2 <= last_index && arr[2 * i + 2] > arr[max]){
max = 2 * i + 2;
}
if (max != i){
int temp = arr[i];
arr[i] = arr[max];
arr[max] = temp;
heapify(arr,max,last_index);
}
}
}