快速排序(Quicksort)是对冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
1千万数据量,耗时1秒,比希尔排序还快,希尔1千万数据要3秒
public class QuickSort {
public static void main(String[] args) {
//创建要给80000个的随机的数组
int[] arr = new int[10000000];
for(int i =0; i < 10000000;i++) {
arr[i] = (int)(Math.random() * 1000000); //生成一个[0, 8000000) 数
}
Date data1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(data1);
System.out.println("排序前的时间是=" + date1Str);
// int[] arr={-9,78,0,23,-567,70};
quickSort(arr,0,arr.length-1);
// System.out.println("第一轮排序:"+ Arrays.toString(arr));
Date data2 = new Date();
String date2Str = simpleDateFormat.format(data2);
System.out.println("排序后的时间是="+date2Str);
// 排序前的时间是=2020-08-02 18:09:06
// 排序后的时间是=2020-08-02 18:09:07
}
//第一轮排序:{-9,78,0,23,-567,70} 把小于0的都放到0的左边,大于0的都放到0的右边
public static void quickSort(int[] arr,int left,int right){
//左下标
int l = left;
//右下标
int r = right;
//pivot 中轴值
int pivot = arr[(left + right) / 2];
//临时变量,作为交换时使用
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;
}
//交换
temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
//如果交换完后,发现这个arr[l] == pivot值 相等r--, 前移
//因为arr[l]不小于privot,所以l不能前移,只能原地不动
//arr[r]交换后必然大于privot,会不断往前移动,如果在不断的前移过程中,arr[r]始终大于pivot,
//那么,迟早r移动到轴值处,arr[r] == pivot,此时l
//所以需要下面的代码,让arr[l] == pivot时,r继续前移,直到l==r,然后退出循环
if(arr[l] == pivot) {
r -= 1;
}
//如果交换完后,发现这个arr[r] == pivot值 相等 l++, 后移
if(arr[r] == pivot) {
l += 1;
}
}
if (l==r){
l++;
r--;
}
//向左递归
if (left<r){
quickSort(arr,left,r);
}
//向右递归
if (l<right){
quickSort(arr,l,right);
}
}
}
https://blog.csdn.net/shujuelin/article/details/82423852
/**
* 快排编写思路:
* 1、递归出口 left>right
* 2、设置数组第一个元素为轴值privot=arr[left]
* 3、满足l
public class QuickSort2 {
public static void main(String[] args) {
int[] arr={
-9,78,0,23,-567,70};
sort(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static void sort(int arr[],int left,int right){
//当只剩下两个元素的时候,一个为轴值,无论是左边递归还是右边递归
//都是left>right
if(left>right){
return;
}
//左下标
int l=left;
//右下标
int r=right;
//轴值
int pivot=arr[left];
//辅助变量
int temp;
//当l与r还没碰头
while (l<r){
//如果左边的先走,那么每一轮最后一个必然是比基数大的数,然后右边的指针移动到和左边的指针同一个位置的时候,
//跳出外层循环,轴值和碰头处的元素交换,而这个元素是大于轴值的,顺序就乱了
//如果右边的先走,那么每一轮最后一个必然是比基数小的数,当两个指针碰头时,这个数和基数互换是没问题的,
//因为这个数小于基数
//判断右下标遇到的数是否小于轴值
//大于轴值,r前移
//小于轴值,跳出循环,准备交换
//l不能大于r,顶多重合
while(arr[r]>=pivot && l<r){
//右下标遇到的数大于轴值,右下标前移
r--;
}
//判断左下标遇到的数是否大于轴值
//小于轴值,l前移
//大于轴值,跳出循环,准备交换
while(arr[l]<=pivot && l<r)
{
//左下标遇到的数小于轴值,左下标前移
l++;
}
if (l<r){
//交换
temp = arr[r];
arr[r] = arr[l];
arr[l] = temp;
}
}
//说明l与r碰头了,轴值和l交换
arr[left]=arr[l];
arr[l]=pivot;
//轴值左边递归
sort(arr,left,l-1);
//轴值右边递归
sort(arr,r+1,right);
}
}
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
来看看治阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤
性能和快排差不多
public class MergetSort {
public static void main(String[] args) {
// int arr[] = { 8, 4, 5, 7, 1, 3, 6, 2 };
//测试快排的执行速度
// 创建要给80000个的随机的数组
int[] arr = new int[8000000];
for (int i = 0; i < 8000000; i++) {
arr[i] = (int) (Math.random() * 8000000); // 生成一个[0, 8000000) 数
}
System.out.println("排序前");
Date data1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(data1);
System.out.println("排序前的时间是=" + date1Str);
int temp[] = new int[arr.length]; //归并排序需要一个额外空间
mergeSort(arr, 0, arr.length - 1, temp);
Date data2 = new Date();
String date2Str = simpleDateFormat.format(data2);
System.out.println("排序前的时间是=" + date2Str);
// System.out.println("归并排序后=" + Arrays.toString(arr));
}
//分+合方法
public static void mergeSort(int[] arr, int left, int right, int[] temp) {
if(left < right) {
int mid = (left + right) / 2; //中间索引
//向左递归进行分解
//当分解到最后一次时,mid(传进去就是right)=left=0,递归结束,合并
//然后返回上一层递归,继续走下面的右递归,此时右递归的入参,left=mid+1,right=上一次递归传的mid
//然后右递归继续左分解,右分解,各自分解到不能分解为止,合并,再回到之前的递归,进行合并
mergeSort(arr, left, mid, temp);
//向右递归进行分解
//当分解到最后一次时,mid=0,right=1,传进去,递归结束
mergeSort(arr, mid + 1, right, temp);
//合并
//第一次合并 min=0,left=0,right=1
merge(arr, left, mid, right, temp);
}
}
/**
* 合并的方法
* @param arr 排序的原始数组
* @param left 左边有序序列的初始索引
* @param mid 中间索引
* @param right 右边索引
* @param temp 做中转的数组
*/
public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
// 初始化i, 左边有序序列的初始索引
int i = left;
//初始化j, 右边有序序列的初始索引
int j = mid + 1;
// 指向temp数组的当前索引
int t = 0;
//(一)
//先把左右两边(有序)的数据按照规则填充到temp数组
//直到左右两边的有序序列,有一边处理完毕为止
while (i <= mid && j <= right) {
//继续
//如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
//即将左边的当前元素,填充到 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;
}
}
//(二)
//把有剩余数据的一边的数据依次全部填充到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;
}
//(三)
//将temp数组的元素拷贝到arr
//注意,并不是每次都拷贝所有
t = 0;
//这个left,如果left=0,则arr从头开始复制
//如果不等于0,则是追加,右分解的时候,left=mid+1的
int tempLeft = left;
while(tempLeft <= right) {
arr[tempLeft] = temp[t];
t += 1;
tempLeft += 1;
}
}
}
将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
这样说明,比较难理解,下面我们看一个图文解释,理解基数排序的步骤
public class RadixSort {
public static void main(String[] args) {
int arr[]={
53, 3, 542, 748, 14, 214};
radixSort(arr);
}
public static void radixSort(int[] arr){
//定义一个二维数组,表示10个桶, 每个桶就是一个一维数组
//说明
//1. 二维数组包含10个一维数组
//2. 为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.length
//3. 很明显,基数排序是使用空间换时间的经典算法
int[][] bucket=new int[10][arr.length];
//为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数
//可以这里理解
//比如:bucketElementCounts[0] , 记录的就是 bucket[0] 桶的放入数据个数
int[] bucketElementCounts=new int[10];
//推导过程
//第一轮排序(针对每个元素的个位进行排序处理)
for (int i = 0; i < arr.length; i++) {
//取出每个元素的个位的值
int diffOfElement = arr[i] % 10;
int bucketElementCount = bucketElementCounts[diffOfElement];
//放入对应的桶中
bucket[diffOfElement][bucketElementCount]=arr[i];
bucketElementCounts[diffOfElement]++;
}
//按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
int index=0;
//遍历每一桶,就将桶中是数据,放入到原数组
for (int i = 0; i < bucket.length; i++) {
//如果桶中,有数据,我们才放入到原数组
if (bucketElementCounts[i]!=0){
//循环该桶即第i个桶(即第i个一维数组), 放入
for (int i1 = 0; i1 < bucketElementCounts[i]; i1++) {
//取出元素放入到arr
arr[index]=bucket[i][i1];
index++;
}
}
//第一轮处理后,需要将每个bucketElementCounts【i】=0
bucketElementCounts[i]=0;
}
System.out.println("第一轮:"+ Arrays.toString(arr));
//第二轮(针对每个元素的个位进行排序处理)
for (int i = 0; i < arr.length; i++) {
//取出每个元素的十位的值
int diffOfElement = arr[i]/10% 10;
int bucketElementCount = bucketElementCounts[diffOfElement];
//放入对应的桶中
bucket[diffOfElement][bucketElementCount]=arr[i];
bucketElementCounts[diffOfElement]++;
}
//按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
index=0;
//遍历每一桶,就将桶中是数据,放入到原数组
for (int i = 0; i < bucket.length; i++) {
//如果桶中,有数据,我们才放入到原数组
if (bucketElementCounts[i]!=0){
//循环该桶即第i个桶(即第i个一维数组), 放入
for (int i1 = 0; i1 < bucketElementCounts[i]; i1++) {
//取出元素放入到arr
arr[index]=bucket[i][i1];
index++;
}
}
//第二轮处理后,需要将每个bucketElementCounts【i】=0
bucketElementCounts[i]=0;
}
System.out.println("第二轮:"+ Arrays.toString(arr));
//第三轮(针对每个元素的个位进行排序处理)
for (int i = 0; i < arr.length; i++) {
//取出每个元素的百位的值
int diffOfElement = arr[i]/100 % 10;
int bucketElementCount = bucketElementCounts[diffOfElement];
//放入对应的桶中
bucket[diffOfElement][bucketElementCount]=arr[i];
bucketElementCounts[diffOfElement]++;
}
//按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
index=0;
//遍历每一桶,就将桶中是数据,放入到原数组
for (int i = 0; i < bucket.length; i++) {
//如果桶中,有数据,我们才放入到原数组
if (bucketElementCounts[i]!=0){
//循环该桶即第i个桶(即第i个一维数组), 放入
for (int i1 = 0; i1 < bucketElementCounts[i]; i1++) {
//取出元素放入到arr
arr[index]=bucket[i][i1];
index++;
}
}
//第二轮处理后,需要将每个bucketElementCounts【i】=0
bucketElementCounts[i]=0;
}
System.out.println("第三轮:"+ Arrays.toString(arr));
}
}
/**
* 基数排序
*/
public class RadixSort {
public static void main(String[] args) {
int arr[]={
53, 3, 542, 748, 14, 214};
radixSort(arr);
}
public static void radixSort(int[] arr){
//找出最大值
int max=arr[0];
for (int i=0;i<arr.length;i++){
if (arr[i]>max){
max=arr[i];
}
}
//获取最大数是几位数
int maxLength = (max + "").length();
//定义一个二维数组,表示10个桶, 每个桶就是一个一维数组
//说明
//1. 二维数组包含10个一维数组
//2. 为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.length
//3. 很明显,基数排序是使用空间换时间的经典算法
int[][] bucket=new int[10][arr.length];
//为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数
//可以这里理解
//比如:bucketElementCounts[0] , 记录的就是 bucket[0] 桶的放入数据个数
int[] bucketElementCounts=new int[10];
for (int k = 0,n=1; k < maxLength; k++,n*=10) {
//第k轮排序(针对每个元素的对应的位进行排序处理),比如:第一轮排序,对应个位
for (int i = 0; i < arr.length; i++) {
//取出每个元素的个位的值
int diffOfElement = arr[i] /n% 10;
int bucketElementCount = bucketElementCounts[diffOfElement];
//放入对应的桶中
bucket[diffOfElement][bucketElementCount]=arr[i];
bucketElementCounts[diffOfElement]++;
}
//按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
int index=0;
//遍历每一桶,就将桶中是数据,放入到原数组
for (int i = 0; i < bucket.length; i++) {
//如果桶中,有数据,我们才放入到原数组
if (bucketElementCounts[i]!=0){
//循环该桶即第i个桶(即第i个一维数组), 放入
for (int i1 = 0; i1 < bucketElementCounts[i]; i1++) {
//取出元素放入到arr
arr[index]=bucket[i][i1];
index++;
}
}
//第i+1轮处理后,需要将每个bucketElementCounts【i】=0
bucketElementCounts[i]=0;
}
System.out.println("第"+(k+1)+"轮:"+ Arrays.toString(arr));
}
}
}
性能测试:比快排还快
public static void main(String[] args) {
//测试快排的执行速度
// 创建要给80000个的随机的数组
int[] arr = new int[8000000];
for (int i = 0; i < 8000000; i++) {
arr[i] = (int) (Math.random() * 8000000); // 生成一个[0, 8000000) 数
}
System.out.println("排序前");
Date data1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(data1);
System.out.println("排序前的时间是=" + date1Str);
// int arr[]={53, 3, 542, 748, 14, 214};
radixSort(arr);
Date data2 = new Date();
String date2Str = simpleDateFormat.format(data2);
System.out.println("排序前的时间是=" + date2Str);
}
要花费的内存空间:
一个int,4字节;8000000个数据,11个数组
8000000*11*4/1024=xxxk
8000000*11*4/1024/1024=xxxM
8000000*11*4/1024/1024/1024=0.3278255462646484G
注意:数组中有负数的,就不要使用基数排序
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
内排序:所有排序操作都在内存中完成;
外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
时间复杂度: 一个算法执行所耗费的时间。
空间复杂度:运行完一个程序所需内存的大小。
n: 数据规模
k: “桶”的个数
In-place: 不占用额外内存
Out-place: 占用额外内存