[java数据结构与算法分析]----8种必须掌握的排序算法总结理解笔记

本文深入解析了包括直接插入排序、希尔排序、简单选择排序、堆排序、冒泡排序、快速排序、归并排序和基数排序在内的八种排序算法。详细阐述了每种算法的原理、特点和稳定性,辅以图解和代码示例,帮助读者全面掌握排序算法的核心概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[java数据结构与算法分析]----8种排序算法详细总结理解笔记

主要记录大学所学过的8种常见排序算法,方便以后复习查阅

在这里插入图片描述

排序的稳定性

​ 若记录序列中有两个或两个以上关键字相等的记录:在排序前Ri先于Rj(i<j),排序后的记录序列仍然是Ri先于Rj,称排序方法是稳定的,否则是不稳定的。

1.直接插入排序----稳定的排序算法

1.1基本算法思想

​ 将待排序的记录Ri,插入到已排好序的记录表R1, R2 ,…., Ri-1中,得到一个新的、记录数增加1的有序表。 直到所有的记录都插入完为止。

​ 设待排序的记录顺序存放在数组R[1…n]中,在排序的某一时刻,将记录序列分成两部分:

◆ R[1…i-1]:已排好序的有序部分;

◆ R[i…n]:未排好序的无序部分。

在刚开始排序时,R[1]是已经排好序的。

简单的说就是 在要排序的一组数中,假设前面(n-1)[n>=2] 个数已经是排 好顺序的,现在要把第n 个数插到前面的有序数中,使得这 n个数 也是排好顺序的。如此反复循环,直到全部排好顺序。

1.2 图解例子—代码

如9,4,-2,60,13,7进行直接插入排序
在这里插入图片描述

public static void insertSort(int[] list) {
    // 打印第一个元素
    System.out.format("i = %d: ", 0);
    printPart(list, 0, 0);

    // 第1个数肯定是有序的,从第2个数开始遍历,依次插入有序序列
    for (int i = 1; i < list.length; i++) {
        int j = 0;
        int temp = list[i]; // 取出第i个数,和前i-1个数比较后,插入合适位置

        // 因为前i-1个数都是从小到大的有序序列,所以只要当前比较的数(list[j])比temp大,就把这个数后移一位
        for (j = i - 1; j >= 0 && temp < list[j]; j--) {
            list[j + 1] = list[j];
        }
        list[j + 1] = temp;
        System.out.format("i = %d: ", i);
        System.out.println();
        printPart(list, 0, i);
    }
}

2.0 希尔排序(缩小增量法)------不稳定的排序算法

希尔(Shell)排序又称为缩小增量排序,它是一种插入排序。它是直接插入排序算法的一种加强版。

2.1基本算法思想

把记录按步长 gap 分组,对每组记录采用直接插入排序方法进行排序。随着步长逐渐减小,所分成的组包含的记录越来越多,当步长的值减小到 1 时,整个数据合成为一组,构成一组有序记录,则完成排序。

2.2 图解例子—代码

增量序列是5,3,1,,希尔排序的过程如图所示。

在这里插入图片描述

public static void shellSort(int[] list) {
    int gap = list.length / 2;

    //进行判断,一个数就不用排序
    while (1 <= gap) {
        // 把距离为 gap 的元素编为一个组,扫描所有组
        for (int i = gap; i < list.length; i++) {
            int j = 0;
            int temp = list[i];

            // 对距离为 gap 的元素组进行排序
            for (j = i - gap; j >= 0 && temp < list[j]; j = j - gap) {
                list[j + gap] = list[j];
            }
            list[j + gap] = temp;
        }

        System.out.format("gap = %d:   ", gap);
        for (int n :
                list) {
            System.out.print(n+" ");


        }
        gap = gap / 2; // 减小增量
    }
}

3.0简单选择排序-------不稳定的排序算法

选择排序(Selection Sort)的基本思想是:每次从当前待排序的记录中选取关键字最小的记录表,然后与待排序的记录序列中的第一个记录进行交换,直到整个记录序列有序为止。

3.1基本算法思想

简单选择排序(Simple Selection Sort ,又称为直接选择排序)的基本操作是:通过n-i次关键字间的比较,从n-i+1个记录中选取关键字最小的记录,然后和第i个记录进行交换,i=1,2, … n-1 。

简单的就是每趟从待排序的记录中选出关键字最小的记录,顺序放在已排序的记录序列末尾或首位,直到全部排序结束为止。

图解

在这里插入图片描述

/**
 * 简单选择排序
 * @param array 待排数据
 */
 public void selectSort(int[] a) {
        int length = a.length;
        for (int i = 0; i < length; i++) {//循环次数
            int key = a[i];
            int position=i;
            for (int j = i + 1; j < length; j++) {//选出最小的值和位置
                if (a[j] < key) {
                    key = a[j];
                    position = j;
                }
            }
            a[position]=a[i];//交换位置
            a[i]=key;
        }
    }

4.0堆排序----不稳定的排序算法

4.1堆的定义

顺序存储的完全二叉树

  • 每个结点的关键字都不大于其孩子结点的关键字,这样的堆称为小根堆。

  • 其中每个结点的关键字都不小于其孩子结点的关键字,这样的堆称为大根堆。

举例来说,对于 n 个元素的序列 {R0, R1, … , Rn} 当且仅当满足下列关系之一时,称之为堆:

  • Ri <= R2i+1 且 Ri <= R2i+2 (小根堆)
  • Ri >= R2i+1 且 Ri >= R2i+2 (大根堆)

其中 i=1,2,…,n/2 向下取整;

堆排序是一树形选择排序,它的特点是,在排序过程中,将R[1…n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无序区中选择关键字最大(或最小)的记录。

4.2基本算法思想

  • 对一组待排序的记录,按堆的定义建立堆(最大堆);交换 R[0]和 R[n];
  • 然后,将 R[0…n-1]调整为堆,交换 R[0]和 R[n-1];
  • 如此反复,直到交换了 R[0]和 R[1]为止。

以上思想可归纳为两个操作:

  • 根据初始数组去构造初始堆(构建一个完全二叉树,保证所有的父结点都比它的孩子结点数值大)。
  • 每次交换第一个和最后一个元素,输出最后一个元素(最大值),然后把剩下元素重新调整为大根堆。

4.3图解实例及代码理解分析

1 3 4 5 2 6 9 7 8 0

在这里插入图片描述

public class HeapAdjust {

    public static  void HeapAdjust(int[] array, int parent, int length) {
        int child = 2 * parent + 1; // 先获得左孩子的下标
        //如果有右孩子结点,则进行循环
        while (child < length) {
            // 如果右孩子结点的值大于左孩子结点,则选取右孩子结点,child + 1 < length判断是否越界
            if (child + 1 < length && array[child] < array[child + 1]) {
                child++;
            }
            // 如果父结点的值已经大于孩子结点的值,则直接结束
            if (array[parent] >= array[child])
                break;
            // 把孩子结点的值赋给父结点
            array[child] = array[parent]^array[child];
            array[parent] = array[child]^array[parent];
            array[child]= array[parent]^array[child];
            // 选取孩子结点的左孩子结点,继续向下筛选
            parent = child;
            child = 2 * child + 1;
        }

    }

    public static void heapSort(int[] list) {
        // 循环建立初始堆
        for (int i = list.length / 2; i >= 0; i--) {
            HeapAdjust(list, i, list.length);
        }

        // 进行n-1次循环,完成排序
        for (int i = list.length - 1; i > 0; i--) {
            // 最后一个元素和第一元素进行交换
            list[i] = list[i]^list[0];
            list[0] = list[i]^list[0];
            list[i] = list[0]^list[i];

            // 筛选 R[0] 结点,得到i-1个结点的堆
            HeapAdjust(list, 0, i);
            System.out.format("第 %d 趟:     ", list.length - i);
            Arrays.stream(list).forEach(s-> System.out.print(s+" "));
            System.out.println();
        }

    }

    public static void main(String[] args) {
        int[] array = {6,8,7,9,0,1,3,2,4,5};
        heapSort(array);
    }
}

[外链图片转存失败(img-FK8UwkKl-1565064633673)(C:\Users\dell-pc\Desktop\笔记md\1565058073416.png)]

5.0冒泡排序-----稳定排序算法

5.1基本算法思想

设待排序记录为(K1,K2,…,Kn)。冒泡排序的基本思想是:从K1开始,依次比较两个相邻的记录Ki和Ki+1(i=1,2,…,n-1) 。若Ki>Ki+1,则交换相应记录ki和ki+1的位置;否则,不进行交换。经过这样一遍处理后,使关键字最大的记录如冒泡一样逐步“漂浮”至“水面”,关键字最大的记录移到了第n个位置上。然后,对前面的n-1个记录进行第2遍排序,重复上述处理过程。第2遍之后,前n-1个记录中关键字最大的记录移到了第n-1个位置上。继续进行下去,直到经过n-1遍为止完成升序排序操作。

简单的说就是在要排序的一组数中,对当前还未排好序的范围内的全部数,重复地自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。
即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。 也因此叫冒泡才行

5.2例子

如下以5、1、6、9、7这5个数据为例进行排序,同时从图中也看出弊端,因此在下面的代码演示中,采用了标志位。不会造成比较次数的多余。

在这里插入图片描述

代码实现

public class bubbleSort {
    public static void bubbleSort(int[] num) {
        int length = num.length;
        int i, j;
        int temp = 0;

        int compareSum = 0;
        boolean change = true;/*置交换标志*/
        for (i = 0; i < length - 1 && change; ++i)/*最多做n-1遍*/ {
            change = false;        /*清除交换标志*/
            for (j = 0; j < length - 1 - i; ++j)
                // 如果逆序则交换记录
                if (num[j] > num[j + 1]) {
                    /* num[0]为暂存单元 */
                    temp = num[j + 1];
                    num[j + 1] = num[j];
                    num[j] = temp;
                    change = true;
                    compareSum++;
                }
        }
        System.out.println("比较次数" + compareSum);

    }
    
    public static void main(String[] args) {
        int[] num = {5, 1, 6, 9, 7};
        System.out.print("未比较:");
        Arrays.stream(num).forEach(s -> System.out.print(s + ","));
        bubbleSort(num);
        System.out.print("比较:");
        Arrays.stream(num).forEach(s -> System.out.print(s + ","));
    }
}

6.0快速排序------不稳定的排序算法

6.1基本思想

选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。

一趟快速排序方法:

​ 从序列的两端交替扫描各个记录,将关键字小于基准关键字的记录依次放置到序列的前边;而将关键字大于基准关键字的记录从序列的最后端起,依次放置到序列的后边,直到扫描完所有的记录。

6.2 图解及代码实现

在这里插入图片描述

public class QKSort {
    public int division(int[] list, int left, int right) {
        // 以最左边的数(left)为基准元素
        int base = list[left];
        while (left < right) {
            // 从序列右端开始,向左遍历,直到找到小于base的数
            while (left < right && list[right] >= base)
              //向左
                right--;
            // 找到了比base小的元素,将这个元素放到最左边的位置
            list[left] = list[right];
            // 从序列左端开始,向右遍历,直到找到大于base的数
            while (left < right && list[left] <= base)
                left++;
            // 找到了比base大的元素,将这个元素放到最右边的位置
            list[right] = list[left];
        }
        // 最后将base放到left位置。此时,left位置的左侧数值应该都比left小;
        // 而left位置的右侧数值应该都比left大。
        list[left] = base;
        //基准元素的下标
        return left;
    }

    private void quickSort(int[] list, int left, int right) {

        // 左下标一定小于右下标,否则就越界了
        if (left < right) {
            // 对数组进行分割,取出下次分割的基准标号
            int base = division(list, left, right);

            System.out.format("base = %d:  ", list[base]);
//            printPart(list, left, right);
            Arrays.stream(list).forEach(s-> System.out.print(s+" "));
            // 对“基准标号“左侧的一组数值进行递归的切割,以至于将这些数值完整的排序
            quickSort(list, left, base - 1);
            // 对“基准标号“右侧的一组数值进行递归的切割,以至于将这些数值完整的排序
            quickSort(list, base + 1, right);
        }
        System.out.println();
    }
    @Test
    public void TestQkSort(){
        int[] array ={60,50,48,37,10,90,84,36};
        quickSort(array,0,array.length-1);
    }
}

快速排序与冒泡
冒泡每一轮只移动一个元素到一端

7.0 归并排序-------稳定

归并(Merging) :是指将两个或两个以上的有序序列合并成一个有序序列。在内部排序中,通常采用的是2路归并排序。即:将两个位置相邻的记录有序子序列。归并为一个记录的有序序列。

开始归并时,每个记录是长度为1的有序子序列,对这些有序子序列逐趟归并,每一趟归并后有序子序列的长度均扩大一倍;当有序子序列的长度与整个记录序列长度相等时,整个记录序列就成为有序序列。
在这里插入图片描述

public class mergingSort {


    /* 在每次合并过程中,都是对两个有序的序列段进行合并,然后排序。

      这两个有序序列段分别为 array[low, mid] 和 array[mid+1, high]。

      先将他们合并到一个局部的暂存数组tempArray 中,带合并完成后再将 tempArray 复制回 array 中。

    为了方便描述,我们称 tempArray[low, mid] 第一段,tempArray[mid+1, high] 为第二段。

    每次从两个段中取出一个记录进行关键字的比较,将较小者放入 tempArray 中。最后将各段中余下的部分直接复制到 tempArray 中。

    经过这样的过程,tempArray 已经是一个有序的序列,再将其复制回 array 中,一次合并排序就完成了。*/
    public void Merge(int[] array, int low, int mid, int high) {

        int first = low; // 第一段序列
        int second = mid + 1; // 是第二段序列
        int temp = 0; // 是临时存放合并序列的下标
        int[] tempArray = new int[high - low + 1]; // 临时合并后序列

        // 扫描第一段和第二段序列,直到有一个扫描结束
        while (first <= mid && second <= high) {
            // 判断第一段和第二段取出的数哪个更小,将其存入合并序列,并继续向下扫描
            if (array[first] <= array[second]) {
                tempArray[temp] = array[first];
                first++;
                temp++;
            } else {
                tempArray[temp] = array[second];
                second++;
                temp++;
            }
        }

        // 若第一段序列还没扫描完,将其全部复制到合并序列
        while (first <= mid) {
            tempArray[temp] = array[first];
            first++;
            temp++;
        }

        // 若第二段序列还没扫描完,将其全部复制到合并序列
        while (second <= high) {
            tempArray[temp] = array[second];
            second++;
            temp++;
        }

        // 将合并序列复制到原始序列中
        for (temp = 0, first = low; first <= high; first++, temp++) {
            array[first] = tempArray[temp];
        }
    }

    /**
     * 进行相邻归并
     * @param array
     * @param gap
     * @param length
     */
    public void MergePass(int[] array, int gap, int length) {
        int i = 0;

        // 归并gap长度的两个相邻子表
        for (i = 0; i + 2 * gap - 1 < length; i = i + 2 * gap) {
            Merge(array, i, i + gap - 1, i + 2 * gap - 1);
        }

        // 余下两个子表,后者长度小于gap
        if (i + gap - 1 < length) {
            Merge(array, i, i + gap - 1, length - 1);
        }
    }

    public int[] sort(int[] list) {
        for (int gap = 1; gap < list.length; gap = 2 * gap) {
            MergePass(list, gap, list.length);
            System.out.print("gap = " + gap + ":   ");
            Arrays.stream(list).forEach(s-> System.out.print(s+ " "));
        }
        return list;
    }
}

8.0基数排序------稳定

8.1基本思想:

将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。 它的各个位数上的基数都是以 0~9 来表示的。也就可以把 0~9 视为 10 个桶。我们先根据序列的个位数的数字来进行分类,将其分到指定的桶中。例如:R[0] = 10,个位数上是 0,将这个数存入编号为 0 的桶中。

8.2图解

设有关键字序列为19,20, 0, 82, 66, 18的一组记录,采用基数排序的过程如下图8所示

位数值关键字关键字
020 /0000
1/18/ 19
282 / 20
3
4
5
666 /66
7
818 / 82
919

个位的排序后:20,0,82,66,18,19

十位排序后:00 ,18,19,20,66,82

 public class radixsSort {
    public static void main(String[] args) {
        int a[]={19,20, 0, 82, 66, 18};
        radixSort(a);
        System.out.print("基数排序:");
        for(int i=0;i<a.length;i++){
            System.out.format("%s ",a[i]);

        }
    }

    public  static void radixSort(int[] array){
        //1.首先确定排序的趟数;
        // 假设第一个数为为大值,通过确定最大的数来找最大的位数,就是放置桶的趟数
       int max=array[0];
       for(int i=1;i<array.length;i++){
            if(array[i]>max){
              max=array[i];
            }
       }
       int time=0;
       //判断位数;
       while(max>0){
          max/=10;
           time++;
       }

        //建立10个队列; 就是10个桶 0-9十个数
       List<ArrayList> queue=new ArrayList<ArrayList>();
       for(int i=0;i<10;i++){
              ArrayList<Integer>queue1=new ArrayList<Integer>();
           queue.add(queue1);
       }

       //外循环 进行time 次分配和收集;放置time次桶
       for(int i=0;i<time;i++){
           //从0-9依次进行 分配数组元素;
          for(int j=0;j<array.length;j++){
               //得到数字的第time+1 位数; 函数是求x的y次方 取出位值
                 int x=array[j]%(int)Math.pow(10,i+1)/(int)Math.pow(10, i);

                 ArrayList<Integer> queue2=queue.get(x);
                 queue2.add(array[j]);
                 queue.set(x, queue2);
          }
          int count=0;//元素计数器;
          //收集队列元素;
          for(int k=0;k<10;k++){
               while(queue.get(k).size()>0){
                   ArrayList<Integer>queue3=queue.get(k);
                   array[count]=queue3.get(0);
                   queue3.remove(0);
                   count++;
               }

          }
       }
    }
}
  ArrayList<Integer> queue2=queue.get(x);
                 queue2.add(array[j]);
                 queue.set(x, queue2);
          }
          int count=0;//元素计数器;
          //收集队列元素;
          for(int k=0;k<10;k++){
               while(queue.get(k).size()>0){
                   ArrayList<Integer>queue3=queue.get(k);
                   array[count]=queue3.get(0);
                   queue3.remove(0);
                   count++;
               }

          }
       }
    }
}

9.0最后总结

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值