十大经典排序算法总结

1.冒泡排序(Bubble Sort)

1)算法描述

冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

(2)算法描述和实现

具体算法描述如下:

  • <1>.比较相邻的元素。如果第一个比第二个大,就交换它们两个;
  • <2>.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
  • <3>.针对所有的元素重复以上的步骤,除了最后一个;
  • <4>.重复步骤1~3,直到排序完成。
public void bubbleSort(int[] arr){
    int n = arr.length;
    for(int i = 0; i < n; i++)
        for(int j = 0; j < n - 1- i; j++){
            if(arr[j] > arr[j+1]){
                int t = arr[j+1];
                arr[j+1] = arr[j];
                arr[j] = t; 
            }
        }
    return arr;
}

改进

public static int[] bubbleSort(int[] arr){
        int k = arr.length - 1;
        while(k > 0) {
            int pos = 0;
            for (int j = 0; j < k; j++) {
                if (arr[j] > arr[j + 1]) {
                    int t = arr[j + 1];
                    arr[j + 1] = arr[j];
                    arr[j] = t;
                    pos = j;
                }
            }
            k = pos;//k的位置就以为那个最大的位置我们排好了 比如你直接k等8 说明倒数第4个都 只需要拍前面8个就行了
        }
        return arr;
    }

2.选择排序(Selection Sort)

(1)算法简介

选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

(2)算法描述和实现

n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下:

  • <1>.初始状态:无序区为R[1..n],有序区为空;
  • <2>.第i趟排序(i=1,2,3...n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
  • <3>.n-1趟结束,数组有序化了。

这个算法就是我每次交换出最小的或者最大的放在开头,第一次交换arr[0] = 0; 再次交换arr[1] = 1 .... 12345 

public static int[] bubbleSort(int[] arr){
        int len = arr.length;
        int minIndex;
        for(int i = 0; i < len - 1; i++) {
            minIndex = i;
            for (int j = i + 1; j < len; j++) {
                if (arr[j] < arr[minIndex]) {
                    minIndex = j;
                }
            }
            int t = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = t;
        }
        return arr;
    }

3.插入排序(Insertion Sort)

(1)算法简介

插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

(2)算法描述和实现

一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:

  • <1>.从第一个元素开始,该元素可以认为已经被排序;
  • <2>.取出下一个元素,在已经排序的元素序列中从后向前扫描;
  • <3>.如果该元素(已排序)大于新元素,将该元素移到下一位置;
  • <4>.重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  • <5>.将新元素插入到该位置后;
  • <6>.重复步骤2~5。
public static int[] bubbleSort(int[] arr){
        for(int i = 1; i < arr.length; i++) {
            int key = arr[i];
            int j = i - 1;
            while (j >= 0 && arr[j] > key ) {
                arr[j + 1] = arr[j];
                j--;
            }
            arr[j + 1] = key;
        }
        return arr;
    }

4.希尔排序(Shell Sort)

1959年Shell发明;
第一个突破O(n^2)的排序算法;是简单插入排序的改进版;它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序

(1)算法简介

希尔排序的核心在于间隔序列的设定。既可以提前设定好间隔序列,也可以动态的定义间隔序列。动态定义间隔序列的算法是《算法(第4版》的合著者Robert Sedgewick提出的。

(2)算法描述和实现

先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:

  • <1>. 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  • <2>.按增量序列个数k,对序列进行k 趟排序;
  • <3>.每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
public static int[] bubbleSort(int[] arr){
        int j = 0, t = 0;
        for(int r = arr.length / 2; r>= 1; r/= 2){
            for(int i = r; i < arr.length; i++){
                t=arr[i];
                j = i - r;
                while(j >= 0 && t < arr[j]){
                    arr[j+r] = arr[j];
                    j -= r;
                }
                arr[j+r] = t;
            }
        }
        return arr;
    }

这个实现思路:就是选择一个增量 增量之前一部分 增量之后一部分 进行插入排序,比增量前的小就交换位置,记住这个t的作用

建议背下来

5.归并排序(Merge Sort)

来源百度百科:

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

过程描述:

归并过程为:比较a[i]和b[j]的大小,若a[i]≤b[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;否则将第二个有序表中的元素b[j]复制到r[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。归并排序的算法我们通常用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。

原理:

归并操作的工作原理如下:

第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列

第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置

第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置

重复步骤3直到某一指针超出序列尾

将另一序列剩下的所有元素直接复制到合并序列尾

下面我就来做个小小的总结:

  • 两个排好序的数组合并一个有序的数组,称之为归并排序
  • 步骤:遍历两个数组,比较它们的值。谁比较小,谁先放入大数组中,直到数组遍历完成

一、演算归并排序过程

现在我有两个已经排好顺序的数组:int[] arr1 = {2, 7, 8}int[] arr2 = {1, 4, 9},我还有一个大数组来装载它们int[] arr = new int[6];

1.1

那么,我将两个数组的值进行比较,谁的值比较小,谁就放入大数组中

首先,拿出arr1[0]arr2[0]进行比较,显然是arr2[0]比较小,因此将arr2[0]放入大数组中,同时arr2指针往后一格

所以,现在目前为止arr = {1}

1.2

随后,拿arr1[0]arr2[1]进行比较,显然是arr1[0]比较小,将arr1[0]放入大数组中,同时arr1指针往后一格

所以,现在目前为止arr = {1,2}

1.3

随后,拿arr1[1]arr2[1]进行比较,显然是arr2[1]比较小,将arr2[1]放入大数组中,同时arr2指针往后一格

所以,现在目前为止arr = {1,2,4}

........

遍历到最后,我们会将两个已排好序的数组变成一个已排好序的数组arr = {1,2,4,7,8,9}

二、归并排序前提分析(分治法)

从上面的演算我们就直到,归并排序的前提是需要两个已经排好顺序的数组,那往往不会有两个已经排好顺序的数组给我们的呀(一般是杂乱无章的一个数组),那这个算法是不是很鸡肋的呢??

其实并不是的,首先假设题目给出的数组是这样子的:int[] arr = {2, 7, 8, 1, 4, 9};

当我们要做归并的时候就以arr[3]也就元素为1的那个地方分开。是然后用一个指针L指向arr[0],一个指针M指向arr[3],用一个指针R指向arr[5](数组最后一位)。有了指针的帮助,我们就可以将这个数组切割成是两个有序的数组了(操作的方式就可以和上面一样了)

可是上面说了,一般给出的是杂乱无章的一个数组,现在还是达不到要求。比如给出的是这样一个数组:int[] arrays = {9, 2, 5, 1, 3, 2, 9, 5, 2, 1, 8};

此时,我们就得用到分治的思想了:

  • 那么我们也可以这样想将int[] arr = {2, 7, 8, 1, 4, 9};数组分隔成一份一份的,arr[0]它是一个有序的"数组",arr[1]它也是一个有序的"数组",利用指针(L,M,R)又可以操作两个数组一样进行排序。最终合成{2,7}.......再不断拆分合并,最后又回到了我们的arr = {1,2,4,7,8,9},因此归并排序是可以排序杂乱无章的数组的

这就是我们的分治法--->将一个大问题分成很多个小问题进行解决,最后重新组合起来

三、归并代码实现

实现步骤:

  1. 拆分
  2. 合并

第一步: 左边排序 l,m 右边排序 m+1, r  然后合并 l, m+1, r

第二步: 合并操作 最后都是在这个操作上进行的 merge;具体就是

先把拿到的数据根据左右关系建立两个数组 left right

进行赋值操作 这时候要注意角标

然后开始设置一个k  一个left的指针i 一个right的指针r

当l < left.length &&  r< right.length的时候  比较其中小的那个 要知道此时 lefr righrt都是排好序的 因为之前递归到最小的时候是拍好的 这恢复出来的了 给原数组的arr[k] (就是你的这一快的最左边l)开始赋值

当弹出的时候一定存在l 或则 r还小于 left.length 或者right.lenfth 给他们复制完 完结

public static void mergeSort(int[] arr, int l, int r){
        if(l == r)
            return;
        else{
            int m = (l + r) / 2;
            mergeSort(arr, l, m);
            mergeSort(arr, m+1, r);
            merge(arr,l, m+1, r);
        }
    }
    public static void merge(int[] arr, int l, int m , int r){
        int[] leftArr = new int[m - l];
        int[] rightArr = new int[r - m + 1];

        for(int i = l; i < m; i++){
            leftArr[i-l] = arr[i];
        }

        for(int i = m; i <= r; i++){
            rightArr[i - m] = arr[i];
        }
        int i = 0, j = 0;
        int k = l;

        while(i < leftArr.length && j < rightArr.length){
            if(leftArr[i] < rightArr[j]){
                arr[k] = leftArr[i];
                i++;
                k++;
            }else{
                arr[k] = rightArr[j];
                j++;
                k++;
            }
        }

        while (i < leftArr.length){
            arr[k] = leftArr[i];
            i++;
            k++;
        }
        while (j < rightArr.length){
            arr[k] = rightArr[j];
            j++;
            k++;
        }

    }

快速排序

更简单的理解是这样的:在数组中找一个支点(任意),经过一趟排序后,支点左边的数都要比支点小,支点右边的数都要比支点大!

现在我们有一个数组:int arr[]={1,4,5,67,2,7,8,6,9,44};

经过一趟排序之后,如果我选择数组中间的数作为支点:7(任意的),那么第一趟排序后的结果是这样的:{1,4,5,6,2,7,8,67,9,44}

那么就实现了支点左边的数比支点小,支点右边的数比支点大

public static void main(String[] args){
//        String s = "abbdcadcd";
        int[] arr = {4,7,3,1,2,5, 0, };
        quickSort(arr, 0 ,6);
        for(int num : arr){
            System.out.print(num + "");
        }
    }


    public static void quickSort(int[] arr, int L, int R) {
        int i = L;
        int j = R;

        //支点
        int pivot = arr[(L + R) / 2];

        //左右两端进行扫描,只要两端还没有交替,就一直扫描
        while (i <= j) {

            //寻找直到比支点大的数
            while (arr[i] < pivot)
                i++;

            //寻找直到比支点小的数
            while (arr[j] > pivot)
                j--;

            //此时已经分别找到了比支点小的数(右边)、比支点大的数(左边),它们进行交换
            if (i <= j) {
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
                i++;
                j--;
            }
        }
        //上面一个while保证了第一趟排序支点的左边比支点小,支点的右边比支点大了。


        //“左边”再做排序,直到左边剩下一个数(递归出口)
        if (j > L)
            quickSort(arr, L, j);

        //“右边”再做排序,直到右边剩下一个数(递归出口)
        if (i < R)
            quickSort(arr, i, R);
    }

时间复杂度:https://blog.csdn.net/weixin_42187898/article/details/87874510

 

堆排序介绍 

  • 完全二叉树: 除了最后一层之外的其他每一层都被完全填充,并且所有结点都保持向左对齐

  • 完满二叉树:除了叶子结点之外的每一个结点一定有两个孩子结点。

  • 满二叉树:除了叶子结点之外的每一个结点都有两个孩子,每一层(当然包含最后一层)都被完全填充

下面用图来说话:

  • 完全二叉树(Complete Binary Tree):

 

 

  • 完满二叉树(Full Binary Tree):

  • 满二叉树(Perfect Binary Tree):

 

 

简单来说:堆排序是将数据看成是完全二叉树、根据完全二叉树的特性来进行排序的一种算法

  • 最大堆要求节点的元素都要不小于其孩子,最小堆要求节点元素都不大于其左右孩子

  • 那么处于最大堆的根节点的元素一定是这个堆中的最大值

这里我们讨论最大堆:当前每个父节点都大于子节点

 

完全二叉树有个特性:左边子节点位置 = 当前父节点的两倍 + 1右边子节点位置 = 当前父节点的两倍 + 2

堆排序体验:

现在我们有一个完全二叉树:左子树和右子树都符合最大堆-->父>子

但是我们会发现:根元素所在的数并不符合,明显的是:1是小于7的

 

我们就对其进行交换,交换完之后我们会发现:右子树又不符合了

因为,右子树变成了这样:

最后,我们将右子数的最大值也交换到右子树的根元素上

于是我们第一次的建堆操作就完成了!

可以发现的是:一次堆建立完之后,我们的最大值就在了堆的根节点上

随后将堆顶最大值和数组最后的元素进行替换,我们就完成了一趟排序了。

接下来,剩下的数不断进行建堆,交换就可以完成我们的堆排序了

总结一下 根据最大值建堆 然后层次遍历得到顺序 第一个值一定是最大值或者最小值,那么把数组最后位置

的元素进行替换 那么最后一个位置的值就是最大值,前面剩下的值再建堆 再重复

 

 

配合前面的图来看 堆排序也是arr 只不过是一个体现完全二叉树性质的arr 对完全二叉树的变换跟据 2*k 2*k+ 1就可以弄到arr里面,考虑两个方面: 

第一种是用:上浮实现, 就是 数组自左向右 堆(从上到下出现),需要遍历所有的结点

第二种:下沉实现,从中间位置开始 无需遍历所有的结点;先构造出来这个树,也就是完成了第一次排序(放到最后一个位置 然后只看前面的元素组成的数组;在进行与之前相仿的交换位置得到最大值,之后又乱了 继续进行下沉操作 直到最后剩余数组的长度为1;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值