按照是否借助外部存储
按照排序的思想(以下均为内部排序)
所谓交换排序,顾名思义,它的思想在于每次通过两个元素的交换,最终达到排序的目的。经典的交换排序分为 冒泡排序 和快速排序。
算法思想:从左到右,将数组(或一组记录)的每两个数依次进行比较,如果前一个数大于后一个数,则将两数进行位置交换。这样,每一趟后,较大的数就慢慢往后移,较小的数慢慢往前移,就向冒水泡一样,所以称为冒泡排序。直观来看,通过每一次排序,都会将一个最大的数通过两两交换的方式移动到数组的尾部。
伪代码
BubbleSort(Arr)
1. for i ⬅1 to length(Arr)
2. do for int j⬅length(Arr) down to i+1
3. do if Arr[j]
/**
* 冒泡排序
* @param arr 非有序数组
*/
public static void sort(int[] arr)
{
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+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
}
这里,第二层for循环j 当然,为了算法的严谨性,我们也可以额外考虑数组为空的情况。在这里,可以,但没必要,哈哈。很容易看出来冒泡排序具有以下特征: 选择排序的主要思想是每一趟选择一个最大/最小的值移动到数组的头部或尾部,经过length(arr)-1趟,整个数组就变为有序数组了。选择排序分为 简单选择排序 和 堆排序。 冒泡排序和选择排序很像,选择排序性能更高,因为选择排序每次比较后不会立即进行交换, 插入排序的思想在于将数组看作一组有序记录和一组非有序记录,每次从非有序数组中取出一个插入到有序记录的合适位置。 插入排序分为 直接插入排序 和 希尔排序(缩小增量排序)。 插入排序其实不需要辅助空间,这里我用了辅助空间是为了便于理解。 直接插入排序的缺点 希尔排序思想 动态展示 希尔排序在对有序序列进行插入时,分为交换法和移动法。 算法思想 算法步骤 代码实现 算法思想 算法步骤 参考文献 [1] https://www.toutiao.com/a6593273307280179715/?iid=6593273307280179715/**
* 优化的冒泡排序,如果某趟排序没有进行交换,则直接break
* @param arr 非有序数组
*/
public static void refinedSort(int[] arr)
{
boolean flag = true;
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+1];
arr[j+1] = arr[j];
arr[j] = temp;
flag = false;
}
}
if(flag)
{
break;
}else {
flag = true;
}
}
}
1. 时间复杂度:
最好: 数组已经有序 O(1) 【对于优化后的冒牌排序】
最坏: 数组逆序 O(n^2)
平均:O(n^2)
2. 空间复杂度:O(1) ,是基于原地排序的,没有辅助空间的消耗
3. 稳定排序:即对于原始数组中两个相同元素,他们的先后位置再排序前后不会发生改变。
快速排序
/**
* 快速排序的递归解法
* 每次以中轴的值作为基准来分成左右两堆
* @param arr 原始数组
* @param left 左下表索引
* @param right 右下标索引
*/
public static void sort(int[] arr, int left,int right)
{
int l = left;
int r = right;
int pivot = arr[(left + right)/2];
while(l<r)
{
while(arr[l]<pivot)
{
l ++;
}
while(arr[r]>pivot)
{
r --;
}
// 已经分成两组完毕
if(l >= r)
{
break;
}
// 交换操作
int temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
// 判断交换的两个值中是否有值和pivot指向的基准值相同 例如 5 5 5这种情况,会死循环
// 此时可以理解为改变了以下pivot为一个和他相同的值,相当于中间值的位置改变了
if(arr[l] >= pivot)
{
r--;
}
if(arr[r] == pivot)
{
l++;
}
}
// 开始递归操作
// 如果l==r,必须r++,l--,否则会出现栈溢出
if(l == r)
{
l++;
r--;
}
// 向左递归
if(left < r)
{
sort(arr,left,r);
}
// 向右递归
if(right > r)
{
sort(arr, l, right);
}
}
选择排序
简单选择排序
选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。第一次:从arr[0] - arr[n-1] 中选择最小数,与arr[0]交换
第二次: 从arr[1] - arr[n-1] 中选择最小数, 与arr【1】交换
....
第n-1次: 从arr[n-2] - arr[n-1] 中选择最小数, 与arr【n-2】交换.
算法结束。
要找到最小的那个才会进行交换操作。/**
* 选择排序,每次选择一个最小的元素与数组前面的元素交换
* @param arr 非有序数组
*/
public static void sort(int[] arr)
{
for(int i=0;i<arr.length-1;i++)
{
int minTemp = arr[i];
int minIndex = i;
for(int j=i;j<arr.length;j++)
{
if(arr[j] < minTemp)
{
minTemp = arr[j];
minIndex = j;
}
}
// 交换最小值和当前的i
if(minIndex !=i)
{
int temp = arr[i];
arr[i] = minTemp;
arr[minIndex] = temp;
}
}
}
堆排序
插入排序
直接插入排序
初始状态:有序记录:arr[0] 无序记录:剩下的n-1个元素
每一次,从剩下的元素中取一个,再有序记录中找一个合适的位置插入该元素,就形成了一个新的有序记录。
1. 取出下一个元素,在已经排序的元素序列中从后向前扫描;
2. 如果该元素(已排序)大于新元素,将该元素移到下一位置;
3. 重复步骤2,直到找到已排序的元素小于或者等于新元素的位置;
4. 将新元素插入到该位置后;
5. 重复步骤1-4直到数组有序
public class MyInsertSorter {
/**
* 插入排序 有序数组+无序数组结合
* @param arr 原始数组
*/
public static int[] sort(int[] arr)
{
int[] helper = new int[arr.length];
for(int i=0;i<arr.length;i++)
{
if(i == 0)
{
helper[0] = arr[0];
}else {
int j ;
// 查找插入位置
for(j=0;j<i;j++)
{
if(arr[i]<helper[j])
{
break;
}
}
// 从插入位置到i的所有元素后移
for(int m=j;m<i;m++)
{
helper[m+1] = helper[m];
}
// 插入当前元素
helper[j] = arr[i];
}
}
return helper;
}
}
更正宗的插入排序应该是下面这样的/**
* 直接插入排序
* @param arr
*/
public static void insertSort(int[] arr)
{
for(int i=1;i<arr.length;i++)
{
int index = i-1;
int temp = arr[i];
while(index>0 && arr[index]>temp)
{
arr[index+1] = arr[index];
index --;
}
arr[index+1]= temp;
}
}
希尔排序
对于数组 2 3 4 5 6 1 (最差情况: 逆序)。当插入的数是较小数比如1时,后移的次数会明显增多,影响了效率。因此,引入希尔排序(缩小增量排序)。
希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序,同时该算法是冲破O(n2)的第一批算法之一。 希尔排序把一组记录按下标的一定增量分组,对每组采用直接插入排序进行排序;随着增量逐渐减小,每组包含的关键词越来越多,当增量减小到1时,整个记录恰好被分为一组,算法终止。它与插入排序的不同之处在于,它会优先比较距离较远的元素。
初始增量:gap = length /2 = 5 分为5组 【8 3】【9 5】【1 4】【7 6】【2 0】
直接插入排序:3 4 1 6 0 8 9 4 7 2
第二次增量: 5/2 = 2 分为两组【3 1 0 9 7】【4 6 8 4 2】
直接插入排序:0 2 1 4 3 5 7 6 9 8
第三次增量: 2/2 = 1 分为1组【0 2 1 4 3 5 7 6 9 8】
直接插入排序,即完成了整个记录的排序
这里,颜色相同的为一组。
第一次:gap = 10/2 = 5 组,对每组分别进行直接插入排序
第二次:gap = 5/2 = 2组,对每组分别进行直接插入排序
第三次:gap = 2/2 = 1组,对每组分别进行直接插入排序
这样,经过三轮直接插入排序,算法终止。 我们可以看出希尔排序的优点在于 每经过一轮缩小增量的直接插入排序,整个数组会变得相对越来越有序,下一次直接插入排序真正进行的元素后移动作会不断减少。
public class MyShellSorter {
/**
* 希尔排序,采用 依次交换法
* 反而会比简单插入排序慢,因为交换次数太多了
* @param arr
*/
public static void sort(int[] arr)
{
// 初始化增量为len/2
int gap = arr.length/2;
while(gap >=1 )
{
for(int i=gap;i<arr.length;i++)
{
for(int j=i-gap;j>=0;j-=gap)
{
if(arr[j] > arr[j+gap])
{
// 交换
int temp = arr[j];
arr[j] = arr[j+gap];
arr[j+gap] = temp;
}
}
}
gap /= 2;
}
}
/**
* 希尔排序 采用优化的移动法 效率更高
* @param arr
*/
public static void RefinedShellSort(int[] arr)
{
int gap = arr.length/2;
while(gap >=1)
{
for(int i=gap;i<arr.length;i++)
{
int temp = arr[i];
int index = i - gap;
while(index>=0 && temp<arr[index])
{
arr[index+gap] = arr[index];
index -= gap;
}
arr[index+gap] = temp;
}
gap /= 2;
}
}
}
归并排序
归并排序是分治法的一种典型应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。相当于每次都是将两个有序序列合并成一个新的有序序列。
1)把长度为n的输入序列分成两个长度为n/2的子序列;
2)对这两个子序列分别采用归并排序;
3)将两个排序好的子序列合并成一个最终的排序序列。
根据两个有序数组合并成一个新的有序数组,
public class MyMergeSort {
public static void main(String[] args) {
int[] arr = {
4,5,7,8,1,2,3,6};
int[] temp = new int[arr.length]; // 归并排序需要额外的空间开销
sort(arr,0, arr.length-1, temp);
}
public static void sort(int[] arr,int left,int right,int[] temp)
{
if(left<right)
{
int mid = (left+right)/2;
// 左半部分递归分解
sort(arr, left, mid, temp);
// 右半部分递归分解
sort(arr, mid+1, right, temp);
// 合并
merge(arr, left, right, mid, temp);
}
}
/**
*
* @param arr 原始数组
* @param left 左边部分的指针索引
* @param right 右边部分的指针索引
* @param mid 中间元素的索引
* @param temp 临时数组
*/
public static void merge(int[] arr,int left, int right, int mid,int[] temp)
{
// 左边部分数组的指针索引
int i = left;
// 右边部分数组的指针索引
int j = mid+1;
// 临时数组的填充下表索引
int t = 0;
// 1. 左右两边的数组依次按照大小顺序填充到temp数组中,使temp是一个有序数组
while(i <=mid && j<=right)
{
if(arr[i]<arr[j])
{
temp[t] = arr[i];
i++;
t++;
}else {
temp[t] = arr[j];
j++;
t++;
}
}
// 2. 对于左右两边,如果有一边还有剩余元素,直接依次填充到temp数组即可
while(i<=mid)
{
temp[t] = arr[i];
i++;
t++;
}
while(j<=right)
{
temp[t] = arr[j];
j++;
t++;
}
// 3. 将temp数组拷贝到原数组
// Note:并不是每次都拷贝所有元素到arr
t = 0;
int tempLeft = left;
// 第一次合并 TL=0,right = 1 // 第二次 TL=2 right=3 // TL = 0, right = 3// TL = 0,right=7
while(tempLeft<=right)
{
arr[tempLeft] = temp[t];
tempLeft ++;
t++;
}
}
}
public class MyMergeSort2 {
public static void main(String[] args) {
int[] arr = {
4,5,7,8,1,2,3,6,10,11};
sort(arr, 0, arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static void sort(int[] arr,int left,int right)
{
int mid = (left+right)/2;
if(left<right)
{
sort(arr, left, mid);
sort(arr, mid+1, right);
merge(arr, left, right);
}
}
public static void merge(int[] arr,int left,int right)
{
int i = left;
int[] temp = new int[right-left+1];
int t = 0;
int mid = (left+right)/2;
int j = mid+1;
while(i<=mid && j<=right)
{
if(arr[i]<arr[j])
{
temp[t++] = arr[i++];
}else {
temp[t++] = arr[j++];
}
}
while(i<=mid)
{
temp[t++] = arr[i++];
}
while(j<=right)
{
temp[t++] = arr[j++];
}
int tempLeft = left;
t = 0;
while(tempLeft<=right)
{
arr[tempLeft++] = temp[t++];
}
}
}
基数排序
1)通过键值的各个位上的值,将要排序的元素分配至某些桶中,达到排序的作用。建立0-9共是10个桶,从个位开始每次把所有数放到对应的桶中,每一轮放置完毕后依次从桶中取出即可。
2)基数排序是一种较高效率的稳定性排序
3)基数排序是桶排序的扩展取得数组中的最大数,并取得位数;
arr为原始数组,从最低位开始取每个位组成radix数组;
对radix进行计数排序(利用计数排序适用于小范围数的特点);
package com.like.java.data_structure.sort;
import java.util.Arrays;
public class MyRadixSort {
public static void main(String[] args) {
int[] arr = {
53,3,542,748,14,214};
sort(arr);
System.out.println(Arrays.toString(arr));
}
/**
* 获取最大数的位数
* @param arr
* @return
*/
private static int getMaxDigit(int[] arr)
{
int max = arr[0];
for(int i=0;i<arr.length;i++)
{
if(arr[i]>max)
{
max = arr[i];
}
}
return (max+"").length();
}
public static void sort(int[] arr)
{
int maxDigit = getMaxDigit(arr);
// 最外层:位数循环
for(int n=0;n<maxDigit;n++)
{
// 0-9共10个桶
int[][] bucket = new int[10][arr.length];
// 每个桶中存储的个数
int[] bucketsSize = new int[10];
// 值循环-对应位数值放入对应桶
for(int k=0;k<arr.length;k++)
{
int val = (n==0)? arr[k] % 10 : arr[k]/(10*n) % 10;
bucket[val][bucketsSize[val]] = arr[k];
bucketsSize[val] = bucketsSize[val] +1;
}
// 依次取出桶中元素
int t = 0;
for(int i = 0;i<10;i++)
{
if(bucketsSize[i] != 0)
{
for(int j=0;j<bucketsSize[i];j++)
{
arr[t++] = bucket[i][j];
}
}
}
System.out.println(Arrays.toString(arr));
}
}
}
计数排序
桶排序
[2] INTRODUCTION TO ALGORITHM