数据结构与算法(java)—— 常用排序算法(冒泡,快排,选择,插入)

排序算法概述

[外链图片转存失败(img-w7mtuU4R-1568895939233)(media/15688179587622.jpg)]

内部排序:

将所有数据都加载到内存中排序,也就是我们常用的排序

外部排序:

因为可能数据量很大,所以无法全部加载到内存中,那么我们需要借助外部存储器来进行排序。

八大排序算法:

交换:冒泡排序快速排序(前一个优化)
插入:插入排序希尔排序(前一个优化)
选择:简单选择排序堆排序(前一个优化)
归并排序
基数排序
希尔排序相当于直接插入排序的优化,它们同属于插入排序类,堆排序相当于简单选择排序的优化,它们同属于选择排序类。而快速排序其实就是冒泡排序的升级,它们都属于交换排序类

1. 冒泡排序

基本思想:

相邻俩俩比较,交换,最大值置后。
相邻的比较,让最小的或者最大的浮动到最顶端。
每次循环会查到一个最值,然后循环继续第二次查到第二大值,直到全部查完。
时间复杂度为两层O(n^2 )

代码实现:

package algrithm;

//冒泡排序
public class BubbleSort {
    public static void bubbleSort(int[] arr) {
        //双层循环:i表示比较趟数,j表示俩俩数进行比较
        for (int i = 0; i < arr.length - 1; i++) {//一共多少趟,一共需要N-1趟
            //每一趟相邻两个数比较,大数置后,每一趟比完最值放到边上
            for (int j = 0; j < arr.length - 1 - i; j++) {//一共需要N-i比完的个数
                //如果前一个数比后一个数大就交换
                if (arr[j] > arr[j + 1]) {
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
    }
}

算法效率:

冒泡排序是稳定的排序算法,最容易实现的排序, 最坏的情况是每次都需要交换, 共需遍历并交换将近n²/2次, 时间因为是两层循环:复杂度为O(n²). 最佳的情况是内循环遍历一次后发现排序是对的, 因此退出循环, 时间复杂度为O(n). 平均来讲, 时间复杂度为O(n²). 由于冒泡排序中只有缓存的temp变量需要内存空间, 因此空间复杂度为常量O(1)。

2. 快速排序

基本思想:

分治,中间值置位,交换。
快速排序(Quicksort)是对冒泡排序的一种改进,借用了分治的思想,通过一趟排序将要排序的数据分割成独立的两部分其中一部分的所有数据都比另外一部分的所有数据都要小 ,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行。

实现步骤

找个基准数第一个数比如为6

  • 用指针从后找到小于6的数,再从前面找到大于 6 的数。交换他俩。让小的到左边,大的到右边。
  • 依次循环找,当两个指针相遇时,则把基准数6放到这个指针位置,也就是把6放中间。这时左边都小于6,右边都大于6。
    递归左右两边的操作。

左右指针法
用伪代码描述如下:
(1)判断左边若大于右边则退出
(2)定义基准数为第一个元素,并定义左右下标
(3)while循环,当左下标小于右时
(4)while循环当右大于基准数则继续找,while当左小于基准数则继续找
(5)当l<r则满足条件交换arr[l]和arr[r]
6)循环结束把该位置和第一个基准数交换
7)递归

图文详解
https://tryenough.com/arithmetic-quitsort
https://blog.csdn.net/Advance_/article/details/81880509

代码实现

//快速排序
public class QuickSort {
    //left为左边下标,right为右下标
    public static void quickSort(int arr[], int first, int last) {
        //如果输入了左边大于右边则有问题直接退出
        if (first > last)
            return;

        int piviot = arr[first];//第一个数作为基准数
        int l = first; //左下标
        int r = last;   //右下标

        //进行循环,因为每次left和right都移动一个,所以终究会碰头
        while (l < r) {
            //先找右边
            //当右边的数大于中间数,则继续移动找,直到找到小的
            while ((arr[r] >= piviot) && (l < r)) {
                r--;
            }
            //当左边有数比基数大则停止,小则继续移动
            while ((arr[l] <= piviot) && (l < r)) {
                l++;
            }
            //上面两个循环之后就找到了下标,进行交换
            //这时查找到的数左边大右边小,则交换元素,让小的去左边,大的去右边
            if (l < r) {//如果l<r则满足条件,交换
                int t = arr[l];
                arr[l] = arr[r];
                arr[r] = t;
            }
            //交换完了进行下一次循环继续找
        }

        //循环结束之后,交换基准数和第一个数
        arr[first] = arr[l];
        arr[l] = piviot;

        //进行递归处理
        quickSort(arr, first, l - 1);//继续处理左边的,这里是一个递归的过程
        quickSort(arr, l + 1, last);//继续处理右边的 ,这里是一个递归的过程
    }
}

算法效率

快速排序并不稳定,快速排序每次交换的元素都有可能不是相邻的, 因此它有可能打破原来值为相同的元素之间的顺序。

3. 选择排序

在这里插入图片描述

插入排序于冒泡排序相似,不同点是,冒泡排序每次比较相邻元素,而选择排序是每次用一个和后面都元素分别比较。最值放置到最前面

算法效率

不稳定排序算法,选择排序的简单和直观名副其实,这也造就了它出了名的慢性子,无论是哪种情况,哪怕原数组已排序完成,它也将花费将近n²/2次遍历来确认一遍。 唯一值得高兴的是,它并不耗费额外的内存空间。
时间复杂度也是O(n^2 )

package algrithm;

//选择排序
public class SelectionSort {
    public static void selectionSort(int[] arr) {
        //所有数遍历,每个i跟内层的j也就是后面的每个数进行比较
        for (int i = 0; i < arr.length - 1; i++) {//所有数遍历
            //从前往后,第一个和第二个比,第一个再和第三个比
            for (int j = i + 1; j < arr.length - 1; j++) {//从i后一个开始比
                //如果第一个大于第三个,则交换,小的放前
                if (arr[i] > arr[j]) {
                    int temp = arr[j];
                    arr[j] = arr[i];
                    arr[i] = temp;
                }
                //排好后再从第二个数开始,分别和后面的比较
            }
        }
    }
}

选择排序优化:减少交换次数。


public class SelectionBetter {
    public static void selectionSort(int[] arr) {
        //所有数遍历,每个i跟内层的j也就是后面的每个数进行比较
        for (int i = 0; i < arr.length - 1; i++) {//所有数遍历
            int min = arr[i];
            int minIndex = i;
            //从前往后,第一个和第二个比,第一个再和第三个比
            for (int j = i + 1; j < arr.length - 1; j++) {//从i后一个开始比
                //每次存储最小数
                if (arr[i] > arr[j]) {
                    min = arr[j];
                    minIndex = j;
                }
                //排好后再从第二个数开始,分别和后面的比较
            }
            //最小数下标交换
            if(minIndex != i){
                arr[minIndex] = arr[i];
                arr[i] = min;
            }
        }
    }
}

4. 插入排序

基本思想:

分为有序表和无序表,每次把无序表中第一个元素拿出来,依次和有序表中进行比较,插入到有序表中适当位置。
步骤:
1)遍历从第二个数开始到数组长度
2)定义待插入值和已排序数组下标
3)循环和前面已排好数组比较,直到找到比他小的


//插入排序
public class InsertSort {

    public static void insertSort(int[] arr) {
        for (int i = 1; i < arr.length; i++) {//从第二个数开始
            //待插入的值
            int insertVal = arr[i];
            int j = i - 1;//j为已排序数组下标
            
            //保证下标>=0且待插入值小于前面排序好的数组值
            while (j >= 0 && insertVal < arr[j]) {
                arr[j + 1] = arr[j];//数组往后移一位
                j--;//然后继续往前比较
            }
            arr[j + 1] = insertVal;//比较完之后,将待插入值插到当前角标位置后面一个
            //这时序列都往后移了一位,前面都就排好了,继续下次循环
        }
        //应该比冒泡排序执行次数少,因为如果该数大,则前面都不需要移动。

    }
}
发布了103 篇原创文章 · 获赞 94 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/chongbin007/article/details/101035132