十大经典排序算法——原理+动图+代码(下) 目录
十大经典排序算法——原理+动图+代码(上)
6、快速排序(Quick Sort)
6.1 算法描述
6.2 动图演示
6.3 代码实现
6.4 算法分析
7、堆排序(Heap Sort)
7.1 算法描述
7.2 动图演示
7.3 代码实现
7.4 算法分析
8、计数排序(Counting Sort)
8.1 算法描述
8.2 动图演示
8.3 代码实现
8.4 算法分析
9、桶排序(Bucket Sort)
9.1 算法描述
9.2 动图演示
9.3 代码实现
9.4 算法分析
10、基数排序(Radix Sort)
10.1 算法描述
10.2 动图演示
10.3 代码实现
10.4 算法分析
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
步骤:
- 快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。
- 从数列中挑出一个元素,称为 “基准”(pivot)(动图中浅黄色的数字);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
- 重复步骤1~4。
package xmcc.datajpa_test.算法;
import java.util.Arrays;
public class Quick_Sort {
/**
* 快速排序算法
* @param arr
* @return
*/
public static int[] quick(int[]arr,int start,int end){
if (arr.length < 1 || start < 0 ||
end >= arr.length || start > end)
return null;
int smallIndex = partition(arr,start,end);
if (smallIndex > start)
quick(arr,start ,smallIndex-1 );
if (smallIndex < end)
quick(arr,smallIndex+1 ,end );
return arr;
}
/**
* 算法——partition
* @param arr
* @param start
* @param end
* @return
*/
private static int partition(int[] arr, int start, int end) {
int pivot = (int) (start + Math.random() * (end - start + 1));
int smallIndex =start - 1;
myswap(arr,pivot,end);
for (int i = start; i <= end; i++) {
if (arr[i] <= arr[end]){
smallIndex++;
if (i > smallIndex)
myswap(arr,i,smallIndex);
}
}
return smallIndex;
}
/**
* 交换数组内两个元素
* @param arr
* @param i
* @param j
*/
private static void myswap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] =temp;
}
//测试
public static void main(String[] args) {
int[]arr=new int[]{10,9,5,11,2,6,3};
int[] result = quick(arr,0,0);
System.out.println(Arrays.toString(result));
}
}
最佳情况:T(n) = O(nlogn) 最差情况:T(n) = O(n2) 平均情况:T(n) = O(nlogn)
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
步骤:
- 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
- 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
- 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
package xmcc.datajpa_test.算法;
import java.util.Arrays;
public class Heap_Sort {
//声明全局变量,用于记录数组arr的长度
static int len;
/**
* 堆排序算法
* @param arr
* @return
*/
public static int[] heap(int[] arr){
len = arr.length;
if (len < 1) return arr;
//1、构建一个最大堆
buildMaxHeap(arr);
//2、循环将堆首位(最大值)与末位交换,然后再重新调整最大堆
while(len > 0){
myswap(arr,0,len - 1);
len--;
adjustHeap(arr,0);
}
return arr;
}
/**
* 建立最大堆
* @param arr
*/
private static void buildMaxHeap(int[] arr) {
//从最后一个非叶子节点开始向上构造最大堆
for (int i = (len - 1) / 2; i >= 0; i--) {
adjustHeap(arr,i );
}
}
/**
* 调整使之成为最大堆
* @param arr
* @param i
*/
private static void adjustHeap(int[] arr, int i) {
int maxIndex = i;
//如果有左子树,且左子树大于父节点,则将最大指针指向左子树
if (i * 2 < len && arr[i * 2] > arr[maxIndex])
maxIndex = i * 2;
//如果有右子树,且右子树大于父节点,则将最大指针指向右子树
if (i * 2 +1 < len && arr[i * 2 +1] > arr[maxIndex])
maxIndex = i * 2 + 1;
//如果父节点不是最大值,则将父节点与最大值交换,并且递归调整与父节点交换的位置
if (maxIndex != i){
myswap(arr,maxIndex ,i );
adjustHeap(arr,maxIndex );
}
}
/**
* 交换数组内两个元素
* @param arr
* @param i
* @param j
*/
private static void myswap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] =temp;
}
//测试
public static void main(String[] args) {
int[]arr=new int[]{10,9,5,11,2,6,3};
int[] result = heap(arr);
System.out.println(Arrays.toString(result));
}
}
最佳情况:T(n) = O(nlogn) 最差情况:T(n) = O(nlogn) 平均情况:T(n) = O(nlogn)
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
计数排序(Counting sort)是一种稳定的排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。它只能对整数进行排序。
步骤:
- 找出待排序的数组中最大和最小的元素;
- 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
- 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
- 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
package xmcc.datajpa_test.算法;
import java.util.Arrays;
public class Count_Sort {
/**
* 计数排序
*/
private static int[] count(int[] arr) {
if (arr.length ==0 ) return arr;
int bias,min = arr[0],max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max)
max = arr[i];
if (arr[i] < min)
min = arr[i];
}
bias = 0 - min;
int[] bucket = new int[max - min + 1];
Arrays.fill(bucket,0 );
for (int i = 0; i < arr.length; i++) {
bucket[arr[i] + bias]++;
}
int index = 0, i = 0;
while (index < arr.length){
if (bucket[i] != 0){
arr[index] = i - bias;
bucket[i]--;
index++;
}else
i++;
}
return arr;
}
//测试
public static void main(String[] args) {
int[]arr=new int[]{10,9,5,11,2,6,3};
int[] result = count(arr);
System.out.println(Arrays.toString(result));
}
}
当输入的元素是n 个0到k之间的整数时,它的运行时间是 O(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。
最佳情况:T(n) = O(n+k) 最差情况:T(n) = O(n+k) 平均情况:T(n) = O(n+k)
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。
桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序。
步骤:
- 人为设置一个BucketSize,作为每个桶所能放置多少个不同数值(例如当BucketSize==5时,该桶可以存放{1,2,3,4,5}这几种数字,但是容量不限,即可以存放100个3);
- 遍历输入数据,并且把数据一个一个放到对应的桶里去;
- 对每个不是空的桶进行排序,可以使用其它排序方法,也可以递归使用桶排序;
- 从不是空的桶里把排好序的数据拼接起来。
- 注意,如果递归使用桶排序为各个桶排序,则当桶数量为1时要手动减小BucketSize增加下一循环桶的数量,否则会陷入死循环,导致内存溢出。
package xmcc.datajpa_test.算法;
import java.util.ArrayList;
import java.util.Arrays;
public class Bucket_Sort {
/**
* 桶排序
* @param arr
* @param bucketSize
* @return
*/
private static ArrayList bucket(ArrayList arr,int bucketSize) {
if (arr == null || arr.size() < 2)
return arr;
int max = arr.get(0) , min = arr.get(0);
//找到最大值 最小值
for (int i = 0; i < arr.size(); i++) {
if (arr.get(i) > max)
max = arr.get(i);
if (arr.get(i) < min)
min = arr.get(i);
}
int bucketCount = (max -min) / bucketSize + 1;
ArrayList> bucketArr = new ArrayList<>(bucketCount);
ArrayList resultArr = new ArrayList<>();
for (int i = 0; i < bucketCount; i++) {
bucketArr.add(new ArrayList());
}
for (int i = 0; i < arr.size(); i++) {
bucketArr.get((arr.get(i) - min) / bucketSize).add(arr.get(i));
}
for (int i = 0; i < bucketCount; i++) {
if (bucketCount == 1)
bucketSize--;
ArrayList temp = bucket(bucketArr.get(i), bucketSize);
for (int j = 0; j < temp.size(); j++)
resultArr.add(temp.get(j));
}
return resultArr;
}
public static void main(String[] args) {
int[]arr=new int[]{10,9,5,11,2,6,3};
ArrayList arrayList = new ArrayList<>();
for (int i = 0; i < arr.length; i++) {
arrayList.add(arr[i]);
}
ArrayList bucket = bucket(arrayList, 4);
bucket.stream().forEach(
integer -> System.out.print(integer+" ")
);
}
}
桶排序最好情况下使用线性时间O(n),桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大。
最佳情况:T(n) = O(n+k) 最差情况:T(n) = O(n+k) 平均情况:T(n) = O(n2)
基数排序也是非比较的排序算法,对每一位进行排序,从最低位开始排序,复杂度为O(kn),n为数组长度,k为数组中的数的最大的位数;
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。
步骤:
- 取得数组中的最大数,并取得位数;
- arr为原始数组,从最低位开始取每个位组成radix数组;
- 对radix进行计数排序(利用计数排序适用于小范围数的特点);
package xmcc.datajpa_test.算法;
import java.util.ArrayList;
import java.util.Arrays;
public class Radix_Sort {
/**
* 基数排序
* @param arr
* @return
*/
public static int[] radix(int[]arr){
if (arr == null || arr.length < 2)
return arr;
//1、先算出最大数的位数
int max = arr[0];
for (int i = 0; i < arr.length; i++) {
max = Math.max(max,arr[i] );
}
int maxDigit = 0;
while (max != 0){
max /= 10;
maxDigit++;
}
int mod = 10,div = 1;
ArrayList> buceketList = new ArrayList<>();
for (int i = 0; i < 10; i++)
buceketList.add(new ArrayList());
for (int i = 0; i < maxDigit; i++,mod *= 10,div *= 10) {
for (int j = 0; j < arr.length; j++) {
int num = (arr[j] % mod) / div;
buceketList.get(num).add(arr[j]);
}
int index = 0;
for (int j = 0; j < buceketList.size(); j++) {
for (int k = 0; k < buceketList.get(j).size(); k++)
arr[index++] = buceketList.get(j).get(k);
buceketList.get(j).clear();
}
}
return arr;
}
//测试
public static void main(String[] args) {
int[]arr=new int[]{10,9,5,11,2,6,3};
radix(arr);
System.out.println(Arrays.toString(arr));
}
}
最佳情况:T(n) = O(n * k) 最差情况:T(n) = O(n * k) 平均情况:T(n) = O(n * k)
基数排序有两种方法:
MSD 从高位开始进行排序 LSD 从低位开始进行排序
基数排序 vs 计数排序 vs 桶排序
这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
- 基数排序:根据键值的每位数字来分配桶
- 计数排序:每个桶只存储单一键值
- 桶排序:每个桶存储一定范围的数值