排序算法之选择排序、冒泡排序、插入排序、希尔排序、归并排序

目录

一、简介

二、选择排序

1、简介

2、代码实现

三、冒泡排序(Bubble Sort)

1、简介

2、代码实现

四、插入排序(Insertion Sort)

1、简介

2、算法分析

3、代码实现

五、希尔排序(Shell Sort)

1、简介

2、分组的思想

​编辑

 3、缩小增量的过程

4、代码实现

六、归并排序(Merge Sort)

1、简介

2、算法原理

3、代码实现


一、简介

常见的排序算法可以分为两大类:比较类排序,和非比较类排序

  1. 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序
  2. 非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。主要思路是通过将数值以哈希(hash)或分桶(bucket)的形式直接映射到存储空间来实现的

算法复杂度总览:


二、选择排序

1、简介

选择排序是一种简单直观的排序算法。

它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后追加到已排序序列的末尾。以此类推,直到所有元素均排序完毕

如下图:

2、代码实现

package com.kgf.algorithm.sort;

/***
 * 选择排序
 * 它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后追加到已排序序列的末尾。
 * 以此类推,直到所有元素均排序完毕。
 */
public class SelectSort {

    public static void main(String[] args) {

        int[] nums = {24, 69, 80, 57, 13};

        SelectSort ss = new SelectSort();
        ss.selectSort(nums);
        for (int num : nums) {
            System.out.print(num+"\t");
        }
        System.out.println();
    }

    /***
     * 选择排序
     * @param nums
     */
    public void selectSort(int[] nums){
        for (int i = 0; i < nums.length; i++) {
            for (int j = i+1; j < nums.length; j++) {
                if (nums[i]>nums[j]){
                    int temp = nums[i];
                    nums[i] = nums[j];
                    nums[j] = temp;
                }
            }
        }

    }
}

三、冒泡排序(Bubble Sort)

1、简介

冒泡排序也是一种简单的排序算法。

它的基本原理是:重复地扫描要排序的数列,一次比较两个元素,如果它们的大小顺序错误,就把它们交换过来。这样,一次扫描结束,我们可以确保最大(小)的值被移动到序列末尾

这个算法的名字由来,就是因为越小的元素会经由交换,慢慢到数列的顶端。

如下图:

2、代码实现

package com.kgf.algorithm.sort;

/***
 * 冒泡排序
 * 冒泡排序也是一种简单的排序算法。
 * 它的基本原理是:重复地扫描要排序的数列,一次比较两个元素,如果它们的大小顺序错误,就把它们交换过来。这样,一次扫描结束,我们可以确保最大(小)的值被移动到序列末尾。
 * 这个算法的名字由来,就是因为越小的元素会经由交换,慢慢“浮”到数列的顶端
 */
public class BubbleSort {

    public static void main(String[] args) {

        int[] nums = {24, 69, 80, 57, 13};

        BubbleSort bs = new BubbleSort();
        bs.bubbleSort(nums);
        for (int num : nums) {
            System.out.print(num+"\t");
        }
        System.out.println();
    }

    public void bubbleSort(int[] nums){

        for (int i = 0; i < nums.length; i++) {
            for (int j = 1; j < nums.length-i; j++) {
                if (nums[j]<nums[j-1]){
                    int temp = nums[j];
                    nums[j] = nums[j-1];
                    nums[j-1] = temp;
                }
            }
        }
    }
}

四、插入排序(Insertion Sort

1、简介

插入排序的算法,同样描述了一种简单直观的排序。

它的工作原理是:构建一个有序序列。对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入

2、算法分析

2.1、思想

    插入排序(Insertion Sorting)的基本思想是:把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,在有序表中从后往前进行比较,将它插入到有序表中的适当位置,使之成为新的有序表

2.2、实例

  将101,34,119,1进行从小→大的排序

  1)第一趟:此时有序表为101,无序表为:34,119,1

  2)从后往前遍历有序表,将34和101进行比较,34<101,此时将101后移一个位置。此时已经遍历完有序表中的所有元素,故将34插入在101的前面,即有序表的第一个位置,得到新的有序表:34,101

  3)第二趟:从后往前遍历有序表,将119与34和101进行比较,发现119均大于两者。故将119直接插在101后面,即有序表的最后一个位置,得到新的有序表:34,101,119

  4)第三趟:从后往前扫描有序表,1<119,故将119往后移一个位置;1<101,将101往后移一个位置;1<34,将34往后移一个位置。此时已经遍历完有序表中的所有元素,故将1插入在34的前面,即有序表的第一个位置。

  5)此时所有元素已经完全有序:1,34,101,119

3、代码实现

package com.kgf.algorithm.sort;

/***
 * 插入排序
 * 构建一个有序序列。对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入
 */
public class InsertionSort {

    public static void main(String[] args) {

        int[] nums = {24, 69, 80, 57, 13};

        InsertionSort is =  new InsertionSort();
        is.insertionSort(nums);
        for (int num : nums) {
            System.out.print(num+"\t");
        }
        System.out.println();
    }

    private void insertionSort(int[] nums) {
        for (int i = 1; i < nums.length; i++) {
            int temp = nums[i];
            int j = i-1;
            for (; j >=0; j--) {
                if (temp<nums[j]){
                   nums[j+1] = nums[j];
                }else{
                   nums[j+1] = temp;
                   break;
                }
            }
            nums[j+1] = temp;
        }
    }
}

五、希尔排序(Shell Sort

1、简介

        1959年由Shell发明,是第一个突破O(n2)的排序算法,是简单插入排序的改进版。

        它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序

        希尔排序在数组中采用跳跃式分组的策略,通过某个增量将数组元素划分为若干组,然后分组进行插入排序,随后逐步缩小增量,继续按组进行插入排序操作,直至增量为1

        希尔排序中对于增量序列的选择十分重要,直接影响到希尔排序的性能。一些经过优化的增量序列如Hibbard经过复杂证明可使得最坏时间复杂度为O(n^3/2)

2、分组的思想

 上图中gap为5,说明要分成5组。
这5组分别用了五种颜色的线条连接起来了。

第1组:9、4
第2组:1、8
第3组:2、6
第4组:5、3
第5组:7、5

 为什么要采取上面的分组方法呢?换一种方法可以吗?
例如:挨着的元素分为一组。

如果是上面的这种分组方式的话,排序之后会变成下面的情况。

 

 如果是最开始的分组方法的话

如果是按照最开始的分组思想分组的话,最后会排序成

 

可以发现左边都是叫小的数据,右边都是较大的数据。
更方便把分成的每一个组进行插入排序。

 3、缩小增量的过程

前面gap为5的情况排序后会变成下面情况

按照gap把数据分成了两组。

当数据很大的时候,数据的间隔很大。
小的数据会往前方,大的数据会往后放。

gap会逐渐缩小,间隔也会逐渐缩小。

整体的数据会更加趋于有序,这个时候使用直接插入排序效率会更高。

gap为2的时候会排序成下面情况

此时分为了1组,再排序一次后变成下面情况 

 

此时的数据即为有序的。 

4、代码实现

package com.kgf.algorithm.sort;

/***
 * 希尔排序
 */
public class ShellSort {

    public static void main(String[] args) {
        int[] nums = {9,1,2,5,7,4,8,6,3,5};
        ShellSort ss = new ShellSort();
        ss.shellSort(nums);
        for (int num : nums) {
            System.out.print(num+"\t");
        }
        System.out.println();
    }
    /***
     *
     * @param nums
     */
    public void shellSort(int[] nums){
        int gap = nums.length/2;//设置起始的gap为数组长度的二分之一
        while (gap>=1){//只要间隔大于1就一直循环
            for (int i = 0; i < nums.length; i++) {//从0开始进行循环
                int temp = nums[i];//记录当前的数据
                int j = i+gap;//步长为gap
                for (; j < nums.length; j+=gap) {
                    if (temp>nums[j]){
                        nums[j-gap] = nums[j];//相邻步长的数据进行交换
                    }else{
                        break;
                    }
                }
                nums[j-gap] = temp;//这里最后需要将之前记录的数据,插入到j-gap这个下标上,因为上面的循环在这个位置截止
            }
            gap = gap/2;
        }
    }
}

六、归并排序(Merge Sort

1、简介

        归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

        将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

归并排序的时间复杂度是O(nlogn)。代价是需要额外的内存空间

        归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法,归并排序对序列的元素进行逐层折半分组,然后从最小分组开始比较排序,合并成一个大的分组,逐层进行,最终所有的元素都是有序的

2、算法原理

 这是一个无序数列:4、5、8、1、7、2、6、3,我们要将它按从小到大排序。按照归并排序的思想,我们要把序列逐层进行拆分

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WPmtncWG-1592551094225)(./快速1.png)]

序列逐层拆分如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nvJMNUvk-1592551094228)(./归并1.png)] 

然后从下往上逐层合并,首先对第一层序列1(只包含元素4)和序列2(只包含元素5)进行合并

创建一个大序列,序列长度为两个小序列长度之和,p1、p2指针分别指向两个小序列的第一个元素,p指向大序列的第一个元素

 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oHexl6py-1592551094230)(./归并2.png)]

比较p1、p2指向的元素,4小于5,将4填入p指向的元素,p、p1往右移一位

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-51zK7Ns2-1592551094231)(./归并3.png)] 

此时,序列1已经没有元素,将序列2的元素依次填入大序列中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3QMWsy0X-1592551094232)(./归并4.png)] 

序列8和1,序列7和2,序列6和3,用同样的方式填入新的序列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rWPH114Z-1592551094235)(./归并5.png)] 

接着,以4、5为序列1,1、8为序列2,继续进行合并

创建一个序列长度为4的大序列,p1指向序列1的第一个元素4,p2指向序列2的第一个元素1,p指向大序列的第一个元素

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oNDu9TdZ-1592551094236)(./归并6.png)]

4和1比较,4大于1,1填入p指向的元素,p、p2往右移一位

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nnIhGQnf-1592551094237)(./归并7.png)] 

4和8比较,4小于8,4填入p指向的元素,p、p1往右移一位

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YQxfZV0b-1592551094239)(./归并8.png)] 

 5和8比较,5小于8,5填入p指向的元素,p、p1往右移一位

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wFyyXNrc-1592551094240)(./归并9.png)]

自此,序列1已经没有元素,将序列2的元素依次填入大序列中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kiyj3tbz-1592551094241)(./归并10.png)] 

序列2、7和序列3、6以同样的方式合并成新的序列 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u26c0pOr-1592551094244)(./归并11.png)]

 最后,将序列1、4、5、8和序列2、3、6、7以同样的方式继续合并成新的序列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tK2Rw29s-1592551094245)(./归并12.png)]

 至此所有的元素都是有序的

3、代码实现

package com.kgf.algorithm.sort;

import java.util.Arrays;

/***
 * 归并排序
 */
public class MergeSort {

    public static void main(String[] args) {
        Integer[] nums = {9,1,2,5,7,4,8,6,3,5};
        MergeSort ss = new MergeSort();
        ss.sort(nums,0,nums.length-1);
        for (int num : nums) {
            System.out.print(num+"\t");
        }
        System.out.println();
    }

    /***
     * 使用归并排序
     * @param nums
     */
    private void sort(Integer[] nums,int startIndex,int endIndex) {
        //判断下标
        if (startIndex>=endIndex)return;
        //折半查找
        int midIndex = (startIndex+endIndex)/2;
        //进行递归排序
        sort(nums,startIndex,midIndex);
        sort(nums,midIndex+1,endIndex);
        //进行合并
        mergeSort(nums,startIndex,endIndex,midIndex);
    }

    /**
     * 进行子数据合并
     * @param nums
     * @param startIndex
     * @param endIndex
     * @param midIndex
     */
    private void mergeSort(Integer[] nums, int startIndex, int endIndex, int midIndex) {
        //创建一个临时数组
        int[] tempArray = new int[(endIndex-startIndex)+1];
        int index = 0;
        int p1 = startIndex;
        int p2 = midIndex+1;
        while (p1<=midIndex && p2<=endIndex){
            if (nums[p1]>nums[p2]){
                tempArray[index++] = nums[p2++];
            }else{
                tempArray[index++] = nums[p1++];
            }
        }
        while  (p1<=midIndex){
            tempArray[index++] = nums[p1++];
        }
        while  (p2<=endIndex){
            tempArray[index++] = nums[p2++];
        }
        for (int i = 0; i < tempArray.length; i++) {
            nums[i + startIndex] = tempArray[i];
        }
//        System.out.println("nums="+ Arrays.deepToString(nums) +",startIndex="+startIndex+", endIndex="+endIndex+", midIndex="+midIndex);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值