排序算法
简介
时间复杂度
时间频度可以理解为代码执行的次数
常见的时间复杂度
这里可以理解为2的十次方为1024,循环十次就可以了,理解为跳着跑
一般++的,时间复杂度就是n,然后*的就是log,很好区分
算法的平均时间复杂度
算法的空间复杂度
冒泡排序
图解:
public class BubbleSort {
public static void main(String[] args) {
int[] arr={3,9,-1,10,-2};
int temp=0;
for (int i=0;i<arr.length-1;i++) {
//比较五个数,实际上只用比较四次就可以了,第五次不用比较
for (int j=0;j<arr.length-i-1;j++){
if (arr[j]>arr[j+1]){
temp=arr[j+1];
arr[j+1]=arr[j];
arr[j]=temp;
}
}
}
for (int index : arr) {
System.out.printf("%d\t",index);
}
}
}
这里是代码,主要就是一个思路,排序一次就选出一个最大的,然后下一次就少排一个即可,时间复杂度为n2
下面是升级版本,省略掉没有交换过的步骤:
public class BubbleSort {
public static void main(String[] args) {
int[] arr = {3, 9, -1, 10, -2};
int temp = 0;
boolean flag = true;
for (int i = 0; i < arr.length - 1; i++) {
//比较五个数,实际上只用比较四次就可以了,第五次不用比较
for (int j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
flag = false;
temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
if (flag) {
break;
}
//这里的意思就是说,没遍历完一次大的循环就重制一次标志位
flag=true;
}
for (int index : arr) {
System.out.printf("%d\t", index);
}
}
}
这里的思路就是每一次循环只要交换过了就设置一个标志位进行改变,如果没有改变过就可以结束程序了
冒泡排序用八万个数据进行统计花费了15-20多秒,效率较低
选择排序
排序思想
思路分析图
图解
public class SelectSort {
public static void main(String[] args) {
int[] arr = {3, 9, -1, 10, -2};
int minIndex=0;
int min=arr[0];
boolean flag=false;
for (int i=0;i<arr.length-1;i++){
//这里一定要搞清楚,是从i+1开始进行排序的
for (int j=i+1;j<arr.length;j++){
if (arr[j]<min){
flag=true;
min=arr[j];
minIndex=j;
}
}
//设置标志位是为了跳过没有改变的时候
if (flag) {
arr[minIndex] = arr[i];
arr[i]=min;
}
minIndex=i+1;
min=arr[i+1];
flag=false;
}
for (int data : arr) {
System.out.println(data);
}
}
}
对选择排序的速度进行测试八万数据 都是在5s以内
选择排序和冒泡排序都是一个交换的思想,都是把最小的数据进行一个交换,而下面要讲到的插入排序其实是一个插入的思想,是把数据插入到一个合适的位置,并且移动其它的数据。
插入排序
思想
图解
public class InsertSort {
public static void main(String[] args) {
int[] arr = {3, 9, -1, 10, -2};
for (int i = 1; i < arr.length; i++) {
int insertValue = arr[i];
int insertIndex = i - 1;
while (insertIndex>=0&&insertValue<arr[insertIndex]){
arr[insertIndex+1]=arr[insertIndex];
insertIndex--;
}
arr[insertIndex+1]=insertValue;
}
for (int data : arr) {
System.out.println(data);
}
}
}
执行时间测试也还是八万数据 大概也是3s以内
插入排序存在的问题:
如果数组是从小到大,然后最后一个数据是最小的,那么就要全部遍历一遍,很花费时间
希尔排序
介绍:
图解:
交换方式实现希尔排序
public class ShellSort {
public static void main(String[] args) {
int[] arr = {3, 9, 1, 10, 2, 4, 5, 6, 7, 8, 0};
int temp = 0;
// 这里是统计一共循环的次数
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
//每次把步长的第一个数与组内前面的所有数进行冒泡比较
for (int i = gap; i < arr.length; i++) {
//这里就是为了防止是奇数的情况下,无法判断最后一个,所以这里用j-=gap
for (int j = i - gap; j >= 0;j-=gap ){
if (arr[j]>arr[j+gap]){
temp=arr[j];
arr[j]=arr[j+gap];
arr[j+gap]=temp;
}
}
}
}
for (int data : arr) {
System.out.printf("%d\t",data);
}
}
}
这里的思想我说一下,其实就是把每个数放到每个组里面进行冒泡,之前写的冒泡排序因为步长是1,所以直接统计次数之后,每次进行缩小范围的冒泡就可以了;而这里的思想稍微有一点不同,这里是把每一个数首先放到每个组里面去,然后直接与这个组里面前面的所有数进行比较,相对来说,这一种或许还要更慢一些?因为时间复杂度比较高。
但是此时的希尔排序还是不完成版,因为速率太慢了~
所以引入了插入方式实现的希尔排序
//用插入的方式来实现
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i <arr.length; i++) {
int j = i;
//把每次比较的值先保存下来
int temp = arr[j];
//对每一组进行插入排序
while (j-gap>=0&&temp<arr[j-gap]){
arr[j]=arr[j-gap];
j-=gap;
}
arr[j]=temp;
}
}
for (int data : arr) {
System.out.printf("%d\t",
data);
}
插入的方式其实跟插入排序差不多,每次就分完组之后也是从步长的第一个index地方开始,然后在每一个组内进行插入排序就可以了
插入式的希尔排序速度超快 八万数据稳定1s内
快速排序
思想:
示意图:
public class QuickSort {
public static void main(String[] args) {
int[] arr = {3, 4, 6, 7, 2, 7, 2, 8, 0};
quickSort(arr, 0, arr.length - 1);
for (int data : arr) {
System.out.printf("%d\t", data);
}
}
//这里的思想就是定义两个指针,然后分别的从右往左开始和从左往右开始遍历,
private static void quickSort(int[] arr, int start, int end) {
//这里判断出龟的条件就是start<end就可以了
if (start < end) {
int temp = arr[start];
int l = start;
int r = end;
while (l < r) {
//这里的l<r也要加,因为循环里面也要进行指针位移操作
while (l < r && temp <= arr[r]) {
r--;
}
arr[l] = arr[r];
while (l < r && arr[l] <= temp) {
l++;
}
arr[r] = arr[l];
}
arr[r] = temp;
//继续递归小的
quickSort(arr, start, l);
//继续递归大的
quickSort(arr, l + 1, end);
}
}
}
快排的速度比希尔排序更快一些
归并排序
思想:
public class MergetSort {
public static void main(String[] args) {
int[] arr = {8, 4, 5, 7, 1, 3, 6, 2};
int[] temp = new int[arr.length];
mergeSort(arr,0,arr.length-1,temp);
System.out.println(Arrays.toString(arr));
}
public static void mergeSort(int[] arr,int left,int right,int[] temp){
//这里就是出龟条件,当只有1个数的时候,left可能等于right就会出龟
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);
}
}
public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
int i = left;
int j = mid + 1;
int t = 0;
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[t++] = arr[i++];
} else {
temp[t++] = arr[j++];
}
}
//当右边的数据都过去了,左边的数据还存在的时候
while (i <= mid) {
temp[t++] = arr[i++];
}
//当左边的数据都过去了,右边的数据还存在的时候
while (j <= right) {
temp[t++] = arr[j++];
}
//这里首先要把temp的值全部赋给arr,所以要首先置为0
t = 0;
//每次的right不一定是arr的长度,栈顶的长度是2,所以只需要判断left<right就可以了
while (left <= right) {
arr[left++] = temp[t++];
}
}
}
这里的思想也用到了递归,跟快排区分一下,快排的递归就是不断跟一个数进行比较,然后小的放左,大的放右;归并的递归就是先全部分开,然后在递归的时候进行比较,重新放到一个数组,然后排好序再还回来!
归并比快速更快!
基数排序(桶排序)
介绍:
思想:
图解:
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[][] bucket = new int[10][arr.length];
//这个地方首先初始化,数组内部所有元素都为0
int[] bucketElementCounts = new int[10];
int max = arr[0];
for (int data : arr) {
if (data >= max) {
max = data;
}
}
int maxLength = (max + "").length();
//进行大的循环
for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
//将数组里面的数都存入桶中
for (int j = 0; j < arr.length; j++) {
int temp = arr[j]/n%10;
//把数组的每个数据对应的值(个十百千万)放到对应的桶内
bucket[temp][bucketElementCounts[temp]++]=arr[j];
}
//把桶内的数组都压回数组
int index=0;
for (int j = 0; j < bucketElementCounts.length; j++) {
if (bucketElementCounts[j]!=0){
for (int k = 0; k < bucketElementCounts[j]; k++) {
arr[index++]=bucket[j][k];
}
}
bucketElementCounts[j]=0;
}
System.out.println("第"+(i+1)+"次桶排序结果为:"+Arrays.toString(arr));
}
}
}
代码比较多,但是思路比较简单,没有用到递归和移动,就是简单的交换,从桶到数组之间的来回交换,记住规律就好!
基数排序很耗费内存,但是速度是最快的,而且是最稳定的,这里的稳定的意思我给大家阐述一下就是,加入数组arr【1,2,1,2】排序过后【1,1,2,2】的索引值对应的是原来的1->1,3->2也就是说就算数字相同,但是索引值小的依然实在索引值大的前面,这样就称之为稳定排序,而我们的快排是不稳定的,速度也很快!
总结
快排和归并排序的时候都用到了递归,而且很明显就是拆分成了两块儿,所以时间复杂度就是nlogn,而基数排序是nk(k取决于数组中最大的数的长度是多少),因为归并排序(分治排序)和基数排序(桶排序)都用到了另外的数组来辅助,所以是外排序(额外耗费了内存空间),关于稳定性,冒泡稳定,选择因为每次选的只与大小有关与index无关,所以不稳定,插入稳定,希尔不稳定,因为每次要分组,分组分组就乱了index,快排不稳定,因为只看值,不看index,归并稳定,基数稳定!