(1)时间复杂度:主要是分析关键字的比较次数和记录的移动次数。
(2)空间复杂度:分析排序算法中需要多少辅助内存
(3)稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。
(1)时间复杂度
(2)空间复杂度
(3)稳定性的意义
在线动画演示各种排序算法过程 - aTool在线工具
java的几种经典排序算法
Java程序员必须掌握的8大排序算法
核心思想:对n个数据要进行 n-1 趟比较,每趟比较 a[i] 与 a[i-1]~~a[0],即是a[i] 与 i 前面的数据逐个比较 ,目的就是将一个待排序的数据,插入到前面已经排好序的有序序列中去,直到插完所有元素为止。
/**
* 直接插入排序
*/
private static void insertSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 1; i < arr.length; i++) {
for (int j = i - 1; j >= 0; j--) {
//j=i-1开始,j--,直至j=-1 或 arr[j + 1] >= arr[j]
if (arr[j + 1] <= arr[j]) {
//arr[j+1] 与 j 以及前面的数据逐个比较, 将小的数插入到前一位
swap(arr, j, j + 1);
}
}
}
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main0() {
int[] a = {49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 1};
Log.i("TAG", "排序之前:");
for (int anA : a) {
Log.i("TAG", anA + " ");
}
Log.i("TAG", "排序之后:");
insertSort(a);
for (int anA : a) {
Log.i("TAG", anA + " ");
}
}
(1)最坏情况
9, 8, 7, 6, 5, 4, 3, 2, 1(逆序)
//所以时间复杂度是:O(N^2)
(2)最好情况
1, 2, 3, 4, 5, 6, 7, 8, 9(正序)
//所以时间复杂度是:O(N)
具体操作为:在将一个新元素temp插入已排好序的数组的过程中,寻找插入点时, 将待插入区域的首元素设置为a[low],末元素设置为a[high],将temp与a[mid]比较,其中mid=(low+high)/2相比较;
(1)如果比参考元素小,则选择a[low]到a[m-1]为新的插入区域(即high=m-1);否则选择a[m+1]到a[high]为新的插入区域(即low=m+1);
(2)如此直至low<=high不成立,即将此位置之后所有元素后移一位,并将新元素插入a[low]。
package com.dn.sort;
public class BinaryInsertSort {
private static int[] binaryInsertSort(int[] a) {
for (int i = 0; i < a.length; i++) {
int temp = a[i];//待插入元素
int low = 0;
int high = i - 1;
int mid = 0;
// 确定要插入的位置
while (low <= high) {
// 找出low和high中间的索引
mid = (low + high) / 2;
if (temp < a[mid]) {
// 如果值比中间值小,限制在小于mid的那一半搜索,让high左移到"中间下标-1"
high = mid - 1;
} else {
// 如果值比中间值大,限制在大于mid的那一半搜索,让low右移到"中间下标+1"
low = mid + 1;
}
}
//将low到i处的所有元素后移一位
for (int j = i - 1; j >= low; j--) {
a[j + 1] = a[j];
}
//元素位置变化时
if (low != i) {
// 元素位置变化时插入新元素temp(因为插入都在mid的右边mid+1,即是low)
a[low] = temp;
}
}
return a;
}
public static void main(String[] args) {
int[] a = { 49, 38, 65, 97, 176, 213, 227, 49, 78, 34, 12, 164, 11, 18,
1 };
System.out.println("排序之前:");
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
// 二分插入排序
a = binaryInsertSort(a);
System.out.println();
System.out.println("排序之后:");
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
}
}
核心思想:算法先将要排序的一组数按某个增量d(n/2,n为要排序数的个数)分成若干组,每组中记录的下标相差d.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。当增量减到1时,进行直接插入排序后,排序完成。
package com.dn.sort;
//不稳定
public class HeerSort {
private static int[] heerSort(int[] a) {
System.out.println();
int d = a.length / 2;//默认增量
while (true) {
for (int i = 0; i < d; i++) {
for (int j = i; j + d < a.length; j += d) {//i开始,d为增量分组比较(eg:d=3; i=0; j=0、3,3、6,6、9,9、12,12、15)
int temp;
if (a[j] > a[j + d]) {//互换位置
temp = a[j];
a[j] = a[j + d];
a[j + d] = temp;
}
}
}
if (d == 1) {
break;
}
d--;//增量-1继续比较
}
return a;
}
/**
* 增强希尔排序&冒泡排序
*/
private static int[] heerBubbleSort(int[] a) {
System.out.println();
int d = a.length / 2;
while (true) {
d = d/2;
for (int i = 0; i < d; i++) {
for (int j = i; j + d < a.length; j += d) {//i开始,d为增量分组比较并组内排序(eg:d=3; i=0; j=0、3、6、9、12、15)
for (int n = i; n + d < a.length; n += d) {//冒泡排序(组内排序)
int temp;
if (a[n] > a[n + d]) {//互换位置
temp = a[n];
a[n] = a[n + d];
a[n + d] = temp;
}
}
}
}
if (d == 1) {
break;
}
d--;//增量-1继续比较
}
return a;
}
public static void main(String[] args) {
int[] a = { 49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 1, 33, 85,29 };
System.out.println("排序之前:");
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
a = heerBubbleSort(a);
System.out.println();
System.out.println("排序之后:");
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
}
}
核心思想:在待排序的数据元素中,选出最小(或最大)的一个数与第一个位置的数交换;然后在剩下的数当中再找最小(或最大)的与第二个位置的数交换,直到所有元素排完为止,简单选择排序是不稳定排序。
/**
* 选择排序
*
* @param arr
*/
public static void selectSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 0; i < arr.length; i++) {
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
//找到最小数的下标
minIndex = arr[j] < arr[minIndex] ? j : minIndex;
}
swap(arr, i, minIndex);
}
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main2() {
int[] array = new int[]{57,68,59,52};
selectSort(array);
Log.i("TAG", "选择排序:");
for (int num : array) {
Log.i("TAG", num + " ");
}
}
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
堆排序是一种树形选择排序,是对直接选择排序的有效改进。堆排序的关键在于:①建堆(大顶堆或小顶堆);②拿堆的根节点和最后一个节点交换。堆排序的核心思想:
(1)将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
(2)将堆顶元素与末尾元素交换,将最大元素”沉”到数组末端;
(3)重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
(1)构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)
(2)index = 2;值为8,不符合情况
(3)index = 1;值为6,符合情况 —>largest=index=4
从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。
(4)index = 4;值为6,不符合情况
(5)index = 0;值为4,符合情况 —>largest=index=1
找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换:
(6)index = 1;值为4,符合情况
交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6:
(7)就将一个无需序列构造成了一个大顶堆
(8)将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素,如此反复进行交换、重建、交换。
(9)将堆顶元素9和末尾元素4进行交换
(10)重新调整结构,使其继续满足堆定义
(11)再将堆顶元素8与末尾元素5进行交换,得到第二大元素8
(12)后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序
图解排序算法(三)之堆排序
package com.dn.sort;
public class HeapSort {
public void heapSort(int[] array) {
if (array == null || array.length <= 1) {
return;
}
// 建立最大堆
buildMaxHeap(array);
// 排序
for (int i = array.length - 1; i >= 1; i--) {
// 最大的在0位置,那么开始沉降,这样每交换一次最大的值就丢到最后了
exchangeElements(array, 0, i);
// 调整大堆
maxHeap(array, i, 0);//i--,不遍历最后的值
}
}
/**
* 建立最大堆
* @param array
*/
private void buildMaxHeap(int[] array) {
if (array == null || array.length <= 1) {
return;
}
int half = (array.length - 1) / 2;//遍历一半(即可得到另一半,最深一层),假设有5个
for (int i = half; i >= 0; i--) {//从中间序号8开始
//调整大堆,只需遍历i=2、1、0
maxHeap(array, array.length, i);
}
}
/**
* 调整大堆️
* @param array 堆数组
* @param length 表示用于构造大堆的数组长度元素数量
* @param index 从哪位置开始
*/
private void maxHeap(int[] array, int length, int index) {
int left = index * 2 + 1;//左节点
int right = index * 2 + 2;//右节点
int largest = index;//目标序号
//left < length安全范围内
if (left < length && array[left] > array[index]) {
largest = left;//左节点大于根节点,将左序号需要赋值为目标序号
}
if (right < length && array[right] > array[largest]) {
largest = right;//右节点大于根节点或左节点中一个,将右序号需要赋值为目标序号
}
if (index != largest) {//largest位置元素不是最大值
//数据交换
exchangeElements(array, index, largest);
//继续调整大堆
maxHeap(array, length, largest);//largest==变化的值
}
}
/**
* 数据交换
*/
public void exchangeElements(int[] array, int index1, int index2) {
int temp = array[index1];
array[index1] = array[index2];
array[index2] = temp;
}
/**
* 打印
*/
public void printArray(int[] array) {
System.out.print("{");
for (int i = 0; i < array.length; i++) {
System.out.print(array[i]);
if (i < array.length - 1) {
System.out.print(", ");
}
}
System.out.println("}");
}
public static void main(String[] args) {
HeapSort heapSort = new HeapSort();
int[] array = { 19, 8, 27, 6, 35, 14, 3, 12, 1, 0, 9, 10, 7 };
System.out.println("Before heap:");
heapSort.printArray(array);
heapSort.heapSort(array);
System.out.println("After heap sort:");
heapSort.printArray(array);
}
}
基本思想:进行 n-1 趟比较并交换,对相邻的元素进行两两比较,大小不相等则进行交换,每一趟会将最小或最大的元素“冒”到末端,最终达到完全有序。
/**
* 冒泡排序
*
* @param arr
*/
public static void bubbleSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int end = arr.length - 1; end > 0; end--) {//N-1趟排序
for (int i = 0; i < end; i++) {//N-1次关键字的比较
if (arr[i] > arr[i + 1]) {
swap(arr, i, i + 1);
}
}
}
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main3() {
int arr[] = {9, 3, 1, 4, 2, 7, 8, 6, 5};
Log.i("TAG", "排序前:");
for (int data : arr) {
Log.i("TAG", data + " ");
}
bubbleSort(arr);
Log.i("TAG", "排序后:");
for (int data : arr) {
Log.i("TAG", data + " ");
}
}
(1)最坏情况
//第1趟,将9交换到arr.length-1的位置 --->N-1次比较 (0~~arr.length-1)
//第2趟,将3交换到arr.length-2的位置 --->N-1
//第3趟,将9交换到arr.length-3的位置 --->N-2
......
//第N-1趟,将a交换到arr.length-N的位置 --->1次比较 (0~~1)
//计算公式是:(N-1+1)*(N-1)/2=N*(N-1)/2=(N^2+N)/2
//所以时间复杂度是:O(N^2)
(2)最好情况
1, 2, 3, 4, 5, 6, 7, 8, 9(已经排好序)
//所以时间复杂度是:O(N)
快速排序死对冒泡排序的一种改进。
基本思想是:选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素。此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。
package com.dn.sort;
public class quickSort {
public static int getMiddle(int[] list, int low, int high) {
int temp = list[low]; // 数组的第一个作为中轴
while (low < high) {
while (low < high && list[high] >= temp) {
high--;
}
list[low] = list[high]; // 比中轴小的记录移到低端
while (low < high && list[low] <= temp) {
low++;
}
list[high] = list[low]; // 比中轴大的记录移到高端
}
list[low] = temp; // 中轴记录到
return low; // 返回中轴的位置
}
public static void quickSort(int[] list, int low, int high) {
if (low < high) {
int middle = getMiddle(list, low, high); // 将list数组进行一分为二
quickSort(list, low, middle - 1); // 对低字段进行递归排序
quickSort(list, middle + 1, high); // 对高字段进行递归排序
}
}
public static void main(String[] args) {
int a[] = { 49, 99, 56, 67, 18, 23, 34, 15, 35, 25, 73, 51 };
if (a.length > 0) { // 查看数组是否为空
quickSort(a, 0, a.length - 1);
}
for (int i = 0; i < a.length; i++)
System.out.print(a[i] + " ");
}
}
基本思想是:归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案”修补”在一起,即分而治之)。
细化来说,归并排序现将长度为n的无序序列看成是n个长度为1的有序子序列,首先做两两合并,得到n/2个长度为2的有序子序列,再做两两合并…不断重复这个过程,最终可以得到一个长度为n的有序序列。
//第1轮:[10,4] --> [4,10] ; [6,3] --> [3,6]
//得到:4 10 3 6
//位置:0 1 2 3
//标注:left middle rightStart right
//第2轮
//left=0,rightStart=2,4>3 --->a,temp=0,tempArray[0]=3
//left=0,rightStart=3,4<6 --->b,temp=1,tempArray[1]=4
//left=1,rightStart=3,10>6 --->a,temp=2,tempArray[2]=6
//left=1,rightStart=4, --->c,temp=3,tempArray[3]=10
package com.dn.sort;
public class MergeSort {
public void mergeSort(int[] a, int left, int right) {
if (left < right) {
int middle = (left + right) / 2; //找出中间索引
mergeSort(a, left, middle); //对左边数组进行递归
mergeSort(a, middle + 1, right); //对右边数组进行递归
merge(a, left, middle, right); //合并
}
}
private void merge(int[] a, int left, int middle, int right) {
//缓存数组
int[] tempArray = new int[a.length];
//右起第一个索引
int rightStart = middle + 1;
//记录左边第0个索引
int leftPos = left;
//temp记录缓存数组的索引
int temp = left;
//0 1 2 3
//比较两个小数组相应下标位置的数组大小,小的先放进缓存数组//left middle rightStart right
while (left <= middle && rightStart <= right) { //4 10 3 6
if (a[left] <= a[rightStart]) { //4>3,a ---> 4<6,b ---> 10>6,a ---> c
tempArray[temp++] = a[left++];//b //3 4 6 10
} else {
tempArray[temp++] = a[rightStart++];//a
}
}
//如果左边还有数据需要拷贝,把左边数组剩下的拷贝到缓存数组
while (left <= middle) {
tempArray[temp++] = a[left++];//c
}
//如果右边还有数据......
while (rightStart <= right) {
tempArray[temp++] = a[rightStart++];
}
//将缓存数组中的内容复制回原数组
while (leftPos <= right) {
a[leftPos] = tempArray[leftPos++];
}
}
public static void main(String[] args){
MergeSort mergeSort = new MergeSort();
int [] a = new int[]{10,4,6,3,8,2,5,7};
mergeSort.mergeSort(a, 0, a.length-1);
for(int n:a){
System.out.print(" "+n);
}
}
}
基本思想是:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
(1)个位—把个位的数值排序好:
(2)十位—把十位的数值排序好:
(3)百位—把百位的数值排序好:
package com.dn.sort;
public class BasicSort {
public void basicSort(int[] array) {
int max = 0;// 获取最大值
int digit = 10;//0-9
int times = 0;// 获取最大值位数
for (int num : array) {
if (max < num) {
max = num;
}
}
while (max > 0) {
max = max / 10;
times++;
}
//建立10个集合(0-9)
List baseList = new ArrayList();// 多维数组
for (int i = 0; i < digit; i++) {
ArrayList list1 = new ArrayList<>();
baseList.add(list1);
}
//进行times次分配和收集;
for (int i = 0; i < times; i++) {
//分配数组元素
for (int j = 0; j < array.length; j++) {
// 获取对应的位的值(pow是平方,i为0是个位,i为1是10位,i为2是百位)
int x = array[j] % (int) Math.pow(10, i + 1) / (int) Math.pow(10, i);//取余、取整
ArrayList list2 = baseList.get(x);
list2.add(array[j]);// 把元素添加进对应下标数组
}
int count = 0;//元素计数器;
//收集队列元素;
for (int j = 0; j < digit; j++) {//0-9
while (baseList.get(j).size() > 0) {
ArrayList list3 = baseList.get(j);// 拿到每一个集合
array[count] = list3.get(0);//获取集合第一个,每次都是新的数据
list3.remove(0);//删除分配给集合的数据
count++;
}
}
}
}
public static void main(String[] args) {
BasicSort basicSort = new BasicSort();
int[] a = { 136, 2, 6, 8, 9, 2, 8, 11, 23, 56, 34, 90, 89, 29, 145,209, 320, 78, 3 };
basicSort.basicSort(a);
for (int n : a) {
System.out.print(" " + n);
}
}
}
给阿里 2 万多名员工按年龄排序应该选择哪个算法?阿里员工特征,2万人,规模较小;处于18-99,尤其是23-40占了大部分,使用桶排序适合。
基本思想是:桶排序(Bucket Sort)的原理很简单,它是将数组分到有限数量的桶子里。
过程:假设待排序的数组a中共有N个整数,并且已知数组a中数据的范围[0, MAX)。在桶排序时,创建容量为MAX的桶数组r,并将桶数组元素都初始化为0;将容量为MAX的桶数组中的每一个单元都看作一个”桶”。在排序时,逐个遍历数组a,将数组a的值,作为”桶数组r”的下标。当a中数据被读取时,就将桶的值加1。例如,读取到数组a[3]=5,则将r[5]的值+1。
特征:(1)桶排序是稳定的; (2)桶排序是常见排序算法中最快的一种,大多数情况下比快排和归并排序还要快 (3)桶排序非常快但是也非常消耗空间,典型的以空间换时间,基本上是最耗内存的一种排序算法。
bucketSort(a, n, max)是作用是对数组a进行桶排序,n是数组a的长度,max是数组中最大元素所属的范围[0,max)。假设a={8,2,3,4,3,6,6,3,9}, max=10。此时,将数组a的所有数据都放到需要为0-9的桶中。如下图:
public class BucketSort {
/*
* 桶排序
* 参数说明:
* a -- 待排序数组
* max -- 数组a中最大值的范围
*/
public static void bucketSort(int[] a, int max) {
int[] buckets;
if (a==null || max<1)
return ;
// 创建一个容量为max的数组buckets,并且将buckets中的所有数据都初始化为0。
buckets = new int[max];
// 1. 计数
for(int i = 0; i < a.length; i++)
buckets[a[i]]++;
// 2. 排序
for (int i = 0, j = 0; i < max; i++) {
while( (buckets[i]--) >0 ) {
a[j++] = i;
}
}
buckets = null;
}
public static void main(String[] args) {
int i;
int a[] = {8,2,3,4,3,6,6,3,9};
System.out.printf("before sort:");
for (i=0; iout.printf("%d ", a[i]);
bucketSort(a, 10); // 桶排序
System.out.printf("after sort:");
for (i=0; iout.printf("%d ", a[i]);
}
}
before sort:8 2 3 4 3 6 6 3 9
after sort:2 3 3 3 4 6 6 8 9
桶排序
[排序算法]–桶排序的Java实现
Java实现桶排序
(1)稳定:冒泡排序、插入排序、归并排序和基数排序。
(2)不稳定:选择排序、快速排序、希尔排序、堆排序。
(3)参考链接:排序算法的稳定性及其意义
(1)O(n^2):直接插入排序,简单选择排序,冒泡排序。
(2)在数据规模较小时(9W内):直接插入排序,简单选择排序差不多。当数据较大时:冒泡排序算法的时间代价最高。性能为O(n^2)的算法基本上是相邻元素进行比较,基本上都是稳定的。
(3)O(nlogn):快速排序,归并排序,希尔排序,堆排序。快排是最好的, 其次是归并和希尔,堆排序在数据量很大时效果明显。
(1)直接插入排序、冒泡排序:待排序列基本有序的情况下,对稳定性有要求;
(2)直接选择排序:待排序列无序,对稳定性不作要求;
(1)归并排序:序列本身基本有序,对稳定性有要求,空间允许下。
(2)快速排序:序列本身无序。完全可以用内存空间,对稳定性没有要求,此时要付出log(n)的额外空间。
(1)归并排序、堆排序。
(1)直接插入排序、冒泡排序。