文章目录
概述
排序算法分为内部排序和外部排序,内部排序把数据记录放在内存中进行排序,而外部排序因为排序的数据量大,内存不能一次容纳全部的排序记录,所以在排序过程中需要访问外存。
经常提及的八大排序算法指的就是内部排序的八种算法,分别是冒泡排序,快速排序,直接插入排序,希尔排序,堆排序,归并排序和基数排序。
各种排序算法的对比
冒泡排序
冒泡排序的基本思想
冒泡排序是一种简单的排序算法,它重复访问要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。访问数列的工作是重复的进行直到没有再需要交换的数据,也就是说该数列已经完成排序。这个算法的名字由来是因为越小的元素会经由交换慢慢“上浮”到数列的顶端,像水中的气泡从水底浮到水面。
package com.zj.KSort;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/8/28 0028 11:34
冒泡排序
冒泡排序是稳定的排序算法,最容易实现的排序, 最坏的情况是每次都需要交换,
共需遍历并交换将近n²/2次, 时间复杂度为O(n²).
最佳的情况是内循环遍历一次后发现排序是对的,
因此退出循环, 时间复杂度为O(n). 平均来讲, 时间复杂度为O(n²).
由于冒泡排序中只有缓存的temp变量需要内存空间, 因此空间复杂度为常量O(1)。
*/
public class BubbleSort {
public static void sort(int[] array){
// 校验
if(array==null||array.length==0){
return;
}
int length = array.length;
// 外层,需要lengtrh-1次循环比较
for(int i=0;i<length-1;i++){
// 内层,每次循环需要两两比较的次数,
// 每次比较厚,都会将当前最大的数放到最后位置
for(int j=0;j<length-1-i;j++) {
if (array[j] > array[j + 1]) {
// 交换数组array的j和j+1位置的数据
swap(array,j,j+1);
}
}
}
}
/**
* 交换数组arraty的i和j位置的数据
* @param array
* @param i
* @param j
*/
public static void swap(int[] array,int i,int j){
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
快速排序
快速排序(
QuickSort
)是对冒泡排序的一种改进,借用了分治
的思想,它的基本思想是:通过一趟排序将要排序的数据分隔成独立的两步分,其中一部分的所有数据都比另外一部分的所有数据要小,然后在按照此方法对着两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
算法描述
快速排序使用分治策略来吧一个序列(list)分为两个子序列(sub-lists),步骤为
- 从数列中挑出一个元素,称为基准
- 重新排列数列,所有比基准小的元素都摆放在基准前面,所有比基准大的元素都摆在基准的后面(相同的数可以放到任一边)。在这个分区结束之后,该基准就处于数列的中间位置,(这个称谓分区操作)
- 递归把小于基准元素的子数列和大于基准值元素的自数列排序
采用左右指针进行
- low=L;high=R;选取a[low]作为关键字记录key
- high–;由后向前找比他小的数
- low++,由前向后找比他大的数
- 交换(2)(3)步找到的数
- 重复(2)(3)一直往后找,直到left和right相遇,这时key和a[low]交换位置
package com.zj.KSort;
import java.util.Arrays;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/8/28 0028 12:02
快速排序
*/
public class QuickSort {
/**
*快速排序(左右指针法)
* @param arr 待排序数组
* @param low 左边界
* @param high 右边界
*/
public static void sort(int[] arr,int low,int high){
if(arr==null||arr.length<=0){
return;
}
// 不用排序已经是顺序的了
if(low>=high) return;
int left = low;
int right = high;
// 基准
int key = arr[left];
while (left<right){
// 注意必须是先从右往左找比器小的值
//从右往左找比key小的数
while (left<right&&arr[right]>=key) right--;
// 从左往右找到比key大的数
while (left<right&&arr[left]<=key) left++;
//将从左往右找到比基准大的值与
// 从右往左找到比基准小的值交换
if(left<right) swap(arr,left,right);
}
// 将基准出的元素与left元素进行交换
// 说明为基准出的元素找到一个合适的值了
swap(arr,low,left);
System.out.println(Arrays.toString(arr));
// 递归调用
sort(arr,low,left-1);
sort(arr,left+1,high);
}
/**
* 交换数组arraty的i和j位置的数据
* @param array
* @param i
* @param j
*/
public static void swap(int[] array,int i,int j){
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
public static void main(String[] args) {
int[] arr = {12,243,1,4,214,6,73,34,352,8,2};
System.out.println(Arrays.toString(arr));
QuickSort.sort(arr,0,arr.length-1);
}
}
直接插入排序
直接插入排序的基本思想是:将数组中的所有元素依次根前面已经排好的元素相比较,如果选择的元素比已排序的元素小,则交换,直到全部元素都比较过为止
算法描述
一般来说,插入排序都采用in-place在数组上实现,具体算法描述如下;
- 从第一个元素开始,该元素可以认为已经被排序
- 取出下一个元素,在已经排序的元素序列中从后向前扫描
- 如果该元素(已排序)大于新元素,将该元素移动到下一位置
- 重复步骤③,直到找到已排序的元素小于或者等于新元素的位置
- 将新元素插入到该位置后
package com.zj.KSort;
import java.util.Arrays;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/8/28 0028 12:44
插入排序
直接插入排序是稳定的排序算法。
*/
public class InsertSort {
public static void sort(int[] arr) {
if (arr == null || arr.length == 0) return;
for (int i = 0; i < arr.length; i++) {
int j = i - 1;
// 先取出带插入数据保存
// 因为向后移位过程中会把覆盖掉待插入数据
int temp = arr[i];
while (j >= 0 && arr[j] > temp) {//如果当前元素比待插入数据大,则后移动
arr[j + 1] = arr[j];
j--;
}
// 找到比待插入数据小的位置,将待插入数据插入
arr[j + 1] = temp;
}
}
public static void main(String[] args) {
int[] arr = {12,243,1,4,214,6,73,34,352,8,2};
InsertSort.sort(arr);
System.out.println(Arrays.toString(arr));
}
}
希尔排序
希尔排序,也称为递减增量排序算法,是插入排序的一种高速而稳定的改进版本。
希尔排序是先将整个待排序的记录序列分隔成若干子序列分别进行直接插入排序,待整个序列中的序列基本有序,再对全体巨鹿进行依次直接插入排序。
基本思想
将待排序数组按照步长
gap
进行分组,然后将分组的元素利用直接插入排序的方法进行排序,每次在将gap折半减小,循环上述操作;当gap=1时,利用直接插入,完成排序
可以看到步长的选择是希尔排序的重要组成部分,只要最终步长为1任何步长序列都可以工作。一般来说最简单的步长取值是初次取数组长度的一半为增量,之后再次减半,直到增量为1,。
算法描述
- 选择一个增量序列t1,t2,…tk;其中ti>tj,tk=1(一般初次取数组半长,之后每次再减半,直到增量为1)
- 按增量序列个数k,对序列进行ktang排序
- 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
package com.zj.KSort;
import java.util.Arrays;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/8/28 0028 13:02
* 希尔排序 不稳定
* 时间复杂度情况如下:(n指待排序序列长度)
1) 最好情况:序列是正序排列,在这种情况下,需要进行的比较操作需(n-1)次。后移赋值操作为0次。即O(n)
2) 最坏情况:O(nlog2n)。
3) 渐进时间复杂度(平均时间复杂度):O(nlog2n)
希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,
所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小,插入排序对于有序的序列效率很高。
所以,希尔排序的时间复杂度会比O(n²)好一些。
希尔算法的性能与所选取的增量(分组长度)序列有很大关系。只对特定的待排序记录序列,
可以准确地估算比较次数和移动次数。想要弄清比较次数和记录移动次数与增量选择之间的关系,
、并给出完整的数学分析,至今仍然是数学难题。
希尔算法在最坏的情况下和平均情况下执行效率相差不是很多,与此同时快速排序在最坏的情况下执行的效率会非常差。
希尔排序没有快速排序算法快,因此中等大小规模表现良好,对规模非常大的数据排序不是最优选择。
(注:专家们提倡,几乎任何排序工作在开始时都可以用希尔排序,若在实际使用中证明它不够快,再改成快速排序这样更高级的排序算法。)。
*/
public class ShellSort {
public static void sort(int[] arr){
int gap = arr.length/2;
// 每次步长对半,直到步长为1结束
for(;gap>0;gap=gap/2){
// 对每个子序列进行希尔排序
// a[0] a[gap] a[2*gap]
// a[1] a[gap+1] a[gap+gap+1]
for(int i=0;i<arr.length;i=i+gap){
// 对每个序列中的元素选择合适的位置,进行插入排序
int temp = arr[i];
// 在子序列里面进行查找
int j = i-gap;
while (j>=0&&arr[j]>temp){
arr[j+gap] = arr[j];
//接着再子序列里面进行查找
j = j-gap;
}
// 找到合适的插入位置
arr[j+gap] = temp;
}
}
}
public static void main(String[] args) {
int[] arr = {12,243,1,4,214,6,73,34,352,8,2};
ShellSort.sort(arr);
System.out.println(Arrays.toString(arr));
}
}
选择排序
在未排序序列中找到最小(大)元素,存放到未排序序列的起始位置。在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。
算法描述
- 从待排序序列中,找到关键字最小的元素
- 如果最小元素不是待排序序列的第一个元素,将其和第一个元素互换;
- 从余下的 N - 1 个元素中,找出关键字最小的元素,重复①、②步,直到排序结束。
package com.zj.KSort;
import java.util.Arrays;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/8/28 0028 15:08
选择排序
*/
public class SelectSort {
/**
*
* @param arr
*/
public static void sort(int[] arr){
for(int i=0;i<arr.length-1;i++){
int minIndex = i;
// 从其之后选择一个最小的元素
for(int j=i+1;j<arr.length;j++){
if(arr[minIndex]>arr[j]){
minIndex=j;
}
}
// 若最下元素的下标发生改变
if(minIndex!=i){
swap(arr,minIndex,i);
}
}
}
public static void swap(int[] array,int i,int j){
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
public static void main(String[] args) {
int[] arr = {12,243,1,4,214,6,73,34,352,8,2};
SelectSort.sort(arr);
System.out.println(Arrays.toString(arr));
}
}
堆排序
参考
归并排序
归并排序是建立在归并操作上的一种有效的排序算法,1945年由约翰·冯·诺伊曼首次提出。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行。
基本思想
归并排序算法是将两个(或两个以上)有序表合成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的,然后再把有序子序列合并为整体有序序列
package com.zj.KSort;
import java.util.Arrays;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/8/28 0028 15:21
归并排序
*/
public class MergeSort {
/**
* 采用递归的方法
* @param a
*/
public static int[] sort(int[] a){
// 数组长度小于1则直接返回
if(a.length<=1) return a;
// 直接将数组的长度对半分为两个小的数组进行合并
// 直到
int num = a.length>>1;
int[] left = Arrays.copyOfRange(a,0,num);
int[] right = Arrays.copyOfRange(a,num,a.length);
// 递归的进行排序
return mergeTwoArray(sort(left),sort(right));
}
/**
* 将两个有序数组进行合并
* @param a
* @param b
* @return
*/
public static int[] mergeTwoArray(int[] a,int[] b){
// 申请额外空间保存归并之后的数据
int[] result = new int[a.length+b.length];
int aIndex = 0;
int bIndex = 0;
int resultIndex = 0;
// 选取两个序列中的较小的值放入新的数组
while (aIndex<a.length&&bIndex<b.length){
// 假如a数组的位置小于b数组位置的元素则
if(a[aIndex]<=b[bIndex]){
result[resultIndex] = a[aIndex];
aIndex++;
}else{
result[resultIndex] = b[bIndex];
bIndex++;
}
resultIndex++;
}
// 两个数组中肯定有一个数组中的元素没有取完则将
while (aIndex<a.length){// 序列a中有多余的元素移入到新的数组
result[resultIndex++] = a[aIndex++];
}
while (bIndex<b.length){// 序列b中有多余的元素移入到新的数组
result[resultIndex++] = b[bIndex++];
}
return result;
}
public static void main(String[] args) {
int[] arr = {12,243,1,4,214,6,73,34,352,8,2};
System.out.println(Arrays.toString(MergeSort.sort(arr)));
System.out.println(Arrays.toString(arr));
}
}
基数排序
基数排序(Radix sort)是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
算法描述
(1)取得数组中的最大数,并取得位数
(2)arr为原始数组,从最低位开始取每个位组成的radix数组,对radix进行计数排序(利用计数排序适用于小范围的特点)
代码实现
- 基数排序:通过序列中各个元素的值,对排序的N个元素进行若干汤的“分配”与“收集”来实现排序
- 分配:我们将L[I]中的元素取出,首先确定其个位上的数字,根据该数字分配到与之序号相同的桶中
- 收集:当序列中所有的元素都分配到对应的桶中,再按照顺序依次将桶中的元素收集形成新的一个待排序列L[],对新形成的序列L[]重复执行分配和收集元素中的十位,拜位直到分配完该序列中的最高位,则排序结束
package com.zj.KSort;
import java.util.Arrays;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/8/28 0028 15:46
基排序(桶排序)
*/
public class RadixSort {
public static void sort(int[] a){
if(a==null||a.length<=0) return;
//下面这一部分是获取数组中的最大值所对应的位数
int max = a[0];
for(int i=1;i<a.length;i++){
max = Math.max(max,a[i]);
}
int maxDigit = 0;
while (max!=0){
max = max/10;
maxDigit++;
}
///
// 位1桶数组,10代表1,,,,,,10
// a.length表示每位极端情况下吧a数组所有的元素全部算进去
int[][] buckets = new int[10][a.length];
int base = 10;
// 把数组a按照位数进行maxDigit循环
for(int i=0;i<maxDigit;i++){
// 存储在各个桶中元素的数量
// 10个桶,每个桶里存储几个元素,这个值得借鉴
int[] bucketlen = new int[10];
// 遍历数组中的每个元素将其存放到对应的同种
for(int j=0;j<a.length;j++){
// 这个计算的很关键
int whichBucket = (a[j]%base)/(base/10);
// 将元素存入到同种,并将对应桶中的元素进行更新
buckets[whichBucket][bucketlen[whichBucket]++] = a[j];
}
// 从铜中将元素依次从头往后捞取出来再次存入到数组a中
int k =0;// 用来计算从铜中捞出元素的总个数方便存入到aa中
for(int j=0;j<buckets.length;j++){
for(int x=0;x<bucketlen[j];x++){
a[k++] = buckets[j][x];
}
}
// 对技术进行更新
base = base*10;
}
}
public static void main(String[] args) {
int[] arr = {12,243,1,4,214,6,73,34,352,8,2};
RadixSort.sort(arr);
System.out.println(Arrays.toString(arr));
}
}