排序算法
快速排序
对冒泡法的改进
时间复杂度:O(nlogn)
基本思想:通过一趟排序将要排序的数据分割成两个独立的部分,其中一部分的所有数据都比另一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行
package sort;
import java.util.Arrays;
public class QuickSort {
public static void main(String[] args) {
int[] arr = {1,34,2,4,56,23,7,-1,20};
System.out.println(Arrays.toString(arr));
quick(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
private static void quick(int[] arr,int left,int right) {
int l = left;
int r = right;
int pivot = arr[(left+right)/2]; // middle value
int temp = 0;
// while循环的目的是让比 pivot值小的放到左边
// 比 pivot值大的放到右边
while(l<r){
// 在 pivot 的左边一直找,找到大于等于pivot的值,才退出
while(arr[l]<pivot){
l+=1;
}
// 在 pivot 的右边一直找,找到小于等于pivot的值,才退出
while(arr[r]>pivot){
r-=1;
}
// 如果 l>=r 说明pivot的左右两边的值,已经按照左边全小于等于
// pivot值;右边全大于等于pivot值
if(l>=r){
break;
}
// swrap
temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
// 交换后 如果 arr[l] == pivot,则 r--,向前移一步
if(arr[l]==pivot){
r-=1;
}
// 交换后 如果 arr[r] == pivot,则 l++,向前移一步
if(arr[r]==pivot){
l+=1;
}
}
// 如果 l==r 必须 l++ r-- 否则会出现栈溢出
if(l==r){
l+=1;
r-=1;
}
// 向左递归
if(left<r){
quick(arr,left,r);
}
// 向右递归
if(right>l){
quick(arr,l,right);
}
}
}
时间测试:80000个随机数据
before sorting 2020-06-15 10:04:28
after sorting 2020-06-15 10:04:28
归并排序
merge-sort
时间复杂度:O(nlogn)
合并方法
public static void merge
1. 传入参数:
int[] arr 传入的需要排序的数组
int left 数组左边索引
int mid 数组中间索引
int right 数组右边索引
int[] temp 存放中间结果的数组
2. 定义变量
int i 左边有序序列的初始索引,初始值为 left
int j 右边有序序列的初始索引,初始值为 mid+1
int t 指向temp数组的当前索引,初始值为0
3. 先把左右两边(有序)的数组,按照规则填充到temp数组;直到左右两边的有序序列有一方处理完毕
4. while循环 i、j在各自范围内移动
如果左边有序序列当前元素小于等于右边有序序列的当前元素,把左边当前元素填充到temp中,后移 t 和 i;
反之,将右边有序序列的当前元素,填到temp中,后移 t 和 j
5. 用while循环把有剩余数据的一方的数据依次全部填充到temp中
6. 将temp数组元素拷贝到arr中,并不是每次都拷贝所有数据
定义变量 tempLeft指向原数组索引,t指向temp索引,进行填充
分+合
public static void mergeSort
1. 传入参数
int[] arr
int left
int right
int[] temp
2. 变量
int mid = (left+right)/2 中间索引
3. 在left<right条件下,向左递归进行分解:mergeSort(arr,left,mid,temp); 向右递归进行分解:mergeSort(arr,mid+1,right,temp); 每一步之后进行合并:merge(arr,left,mid,right,temp);
代码
package sort;
import java.util.Arrays;
public class MergeSortTest {
public static void main(String[] args) {
int[] arr = {1,34,2,4,56,23,7,-1,20};
int[] temp = new int[arr.length]; // 额外的空间
System.out.println(Arrays.toString(arr));
mergeSort(arr,0,arr.length-1,temp);
System.out.println(Arrays.toString(arr));
}
// divide+merge
public static void mergeSort(int[] arr,int left,int right,int[] temp){
if(left<right){
int mid = (left+right)/2;
// 向左递归进行分解
mergeSort(arr,left,mid,temp);
// 向右递归进行分解
mergeSort(arr,mid+1,right,temp);
// 合并
merge(arr,left,mid,right,temp);
}
}
// merge
public static void merge(int[] arr,int left,int mid,int right,int[] temp){
int i = left; // 初始化i,左边有序序列的初始索引
int j = mid + 1; // 初始化j,右边有序序列的初始索引
int t = 0; // 指向temp数组的当前索引
// 1
// 先把左右两边(有序)的数组,按照规则填充到temp数组
// 直到左右两边的有序序列有一方处理完毕
while(i<=mid && j<=right){ // keep going
// 如果左边有序序列当前元素小于等于右边有序序列的当前元素
// 把左边当前元素填到temp中
// 后移 t,i
if(arr[i]<=arr[j]){
temp[t] = arr[i];
t+=1;
i+=1;
}else{ // 将右边有序序列的当前元素,填充到temp数组
temp[t] = arr[j];
t+=1;
j+=1;
}
}
// 2
// 把有剩余数据的一方的数据依次全部填充到temp中
while(i<=mid){ // 左边有序序列有剩余元素,全部填到temp中
temp[t] = arr[i];
t+=1;
i+=1;
}
while(j<=right){ // 右边有序序列有剩余元素,全部填到temp中
temp[t] = arr[j];
t+=1;
j+=1;
}
// 3
// 将 temp 数组的元素拷贝到 arr
// 并不是每次都拷贝所有数据
t = 0;
int tempLeft = left;
while(tempLeft<=right){
// 第一次合并 tempLeft=0,right=1
// 第二次合并 tempLeft=2,right=3
// 第二次合并 tempLeft=0,right=3
// 最后一次 tempLeft=0,right=7
arr[tempLeft] = temp[t];
t+=1;
tempLeft+=1;
}
}
}
时间测试:8000000个随机数排序
before sorting 2020-06-15 11:03:38
after sorting 2020-06-15 11:03:40
基数排序(桶排序)
radix sort 属于“分配式排序” distribution sort,又称为“桶子法“ bucket sort
-
通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用
-
基数排序是属于稳定性排序,效率较高
-
是桶排序的扩展
-
不支持负数
基本思想:将所有待比较数值统一为统一的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序数列
arr = [53,3,542,748,14,214]
总共有标号 0-9 十个桶
- 第一轮排序
- 将每个元素的个位数取出,然后看这个数应该放在哪个对应的桶(一个一维数组)
- 按照桶的顺序(一维数组的下标依次取出数组放入原来的数组)
- 得到:arr[542,53,3,14,214,748]
- 第二轮排序
- 将每个元素的十位数取出,然后看这个数应该放在哪个对应的桶(一个一维数组)
- 按照桶的顺序(一维数组的下标依次取出数组放入原来的数组)
- 得到:arr[3,14,214,542,748,53]
- 第三轮排序
- 将每个元素的百位数取出,然后看这个数应该放在哪个对应的桶(一个一维数组)
- 按照桶的顺序(一维数组的下标依次取出数组放入原来的数组)
- 得到:arr[3,14,53,214,542,748]
找到最大位数
定义一个二维数组,表示10个桶,每个桶是一个一维数组
1. 二维数组包含10个一维数组
2. 为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.length
3. 基数排序是使用空间换时间的经典算法
为了记录每个桶中实际存放了多少个数据,我们定义一个一维数组来记录各个桶每次放入的数据个数
按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
遍历每一个桶,并将桶中的数据放入原数组
如果桶中有数据,我们才放入到原数组中
循环该桶,即第k个桶(第k个一维数组)
取出元素放入 arr
第一轮处理后,需要将每个 bucketElementCounts[k]=0!
轮数:最大位数
取模
清空每次的桶
代码
package sort;
import java.util.Arrays;
public class RadixSort2 {
public static void main(String[] args) {
int[] arr = {1,34,2,4,56,23,7,20};
System.out.println(Arrays.toString(arr));
radix(arr);
System.out.println(Arrays.toString(arr));
}
public static void radix(int[] arr){
// 1 先得到数组中最大的数的位数
int max = arr[0];
for (int i = 0; i < arr.length; i++) {
if(arr[i]>max){
max = arr[i];
}
}
// how many digits?
int maxLength = (max+"").length();
for (int j = 0,n=1; j < maxLength; j++,n*=10) {
// 定义一个二维数组,表示10个桶,每个桶是一个一维数组
// 1. 二维数组包含10个一维数组
// 2. 为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.length
// 3. 基数排序是使用空间换时间的经典算法
int[][] bucket = new int[10][arr.length];
// 为了记录每个桶中实际存放了多少个数据,我们定义一个一维数组来记录各个桶
// 每次放入的数据个数
int[] bucketElementCounts = new int[10];
for (int i = 0; i < arr.length; i++) {
// 取出每个元素的个位的值
int digitOfElement = arr[i] / n % 10;
// 放入到对应的桶中
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[i];
bucketElementCounts[digitOfElement]++;
}
// 按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
int index = 0;
// 遍历每一个桶,并将桶中的数据放入原数组
for(int k = 0; k < bucketElementCounts.length; k++){
// 如果桶中有数据,我们才放入到原数组中
if(bucketElementCounts[k]!=0){
// 循环该桶,即第k个桶(第k个一维数组)
for(int l = 0; l < bucketElementCounts[k]; l++){
// 取出元素放入 arr
arr[index++] = bucket[k][l];
}
}
}
}
}
}
// 测试时间 8000000数据
before sorting 2020-06-15 12:37:47
after sorting 2020-06-15 12:37:48