08排序算法

# Snipaste_2021-10-23_20-56-371.冒泡排序

冒泡排序(BubbleSorting)的基本思想是:通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。

优化:因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。(这里说的优化,可以在冒泡排序写好后,在进行)

原始数组:3, 9, -1, 10, 20

第一趟排序

(1) 3, 9, -1, 10, 20 // 如果相邻的元素逆序就交换

(2) 3, -1, 9, 10, 20

(3) 3, -1, 9, 10, 20

(4) 3, -1, 9, 10, 20

第二趟排序

(1) -1, 3, 9, 10, 20 //****交换

(2) -1, 3, 9, 10, 20

(3) -1, 3, 9, 10, 20

第三趟排序

(1) -1, 3, 9, 10, 20

(2) -1, 3, 9, 10, 20

第四趟排序

(1) -1, 3, 9, 10, 20

小结冒泡排序规则

(1) 一共进行 数组的大小-1 次 大的循环

(2)每一趟排序的次数在逐渐的减少

  • 优化

如果我们发现在某趟排序中,没有发生一次交换, 可以提前结束冒泡排序。这个就是优化

代码:

// 将前面额冒泡排序算法,封装成一个方法
public static void bubbleSort(int[] arr) {
    // 冒泡排序 的时间复杂度 O(n^2), 自己写出
    int temp = 0; // 临时变量
    boolean flag = false; // 标识变量,表示是否进行过交换
    for (int i = 0; i < arr.length - 1; i++) {

        for (int j = 0; j < arr.length - 1 - i; j++) {
            // 如果前面的数比后面的数大,则交换
            if (arr[j] > arr[j + 1]) {
                flag = true;
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
        //System.out.println("第" + (i + 1) + "趟排序后的数组");
        //System.out.println(Arrays.toString(arr));

        if (!flag) { // 在一趟排序中,一次交换都没有发生过
            break;
        } else {
            flag = false; // 重置flag!!!, 进行下次判断
        }
    }
}

2.选择排序

  • 算法思想

选择排序(select sorting)也是一种简单的排序方法。它的基本思想是:

第一次从 arr[0]~arr[n-1]中选取最小值, 与 arr[0]交换, 第二次从 arr[1]~arr[n-1]中选取最小值, 与 arr[1]交换, 第三次从 arr[2]~arr[n-1]中选取最小值, 与 arr[2] 交换,…,第 i 次从 arr[i-1]~arr[n-1]中选取最小值,与 arr[i-1]交换,…, 第 n-1 次从 arr[n-2]~arr[n-1]中选取最小值, 与 arr[n-2]交换,总共通过 n-1 次,得到一个按排序码从小到大排列的有序序列。

Snipaste_2021-10-20_12-08-29

原始的数组 : 101, 34, 119, 1

第一轮排序 : 1, 34, 119, 101

第二轮排序 : 1, 34, 119, 101

第三轮排序 : 1, 34, 101, 119

说明:

\1. 选择排序一共有 数组大小 - 1 轮排序

\2. 每1轮排序,又是一个循环, 循环的规则(代码)

2.1先假定当前这个数是最小数

2.2 然后和后面的每个数进行比较,如果发现有比当前数更小的数,就重新确定最小数,并得到下标

2.3 当遍历到数组的最后时,就得到本轮最小数和下标

2.4 交换

代码:


public static void selectsort(int []arr){

    for(int i=0;i<arr.length;i++){
        int index = i;
        int max=arr[i];
        for(int j=i+1;j<arr.length;j++){
            if(max<=arr[j]){
                max=arr[j];
                index=j;
            }
        }
        if(index!=i) {
            arr[index] = arr[i];
            arr[i] = max;
        }
    }
}

public static void show(int []arr){
    for(int i=0;i<arr.length;i++){
        System.out.printf("%d"+" ",arr[i]);
    }
    System.out.println();
}


3.插入排序

算法思想:

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

Snipaste_2021-10-20_12-12-36

代码:


	
public static void insertsort(int[] arr) {
    int insertval = 0;
    int insertindex = 0;
    for (int i = 1; i < arr.length; i++) {
        insertval = arr[i];
        insertindex = i - 1;
        // 给insertVal 找到插入的位置
        // 说明
        // 1. insertIndex >= 0 保证在给insertVal 找插入位置,不越界
        // 2. insertVal < arr[insertIndex] 待插入的数,还没有找到插入位置
        // 3. 就需要将 arr[insertIndex] 后移
        while (insertindex >= 0 && insertval < arr[insertindex]) {
            arr[insertindex + 1] = arr[insertindex];
            insertindex--;
        }
        // 当退出while循环时,说明插入的位置找到, insertIndex + 1
        //这里我们判断是否需要赋值
        if (insertindex + 1 != i) {
            arr[insertindex + 1] = insertval;
        }
    }
}




4.希尔排序

我们看简单的插入排序可能存在的问题. 数组 arr = {2,3,4,5,6,1}

这时需要插入的数 1( 最小), 这样的过程是:

{2,3,4,5,6,6}

{2,3,4,5,5,6}

{2,3,4,4,5,6}

{2,3,3,4,5,6}

{2,2,3,4,5,6}

{1,2,3,4,5,6}

结论: 当 需要插入的数是较小的数时, 后移的次数明显增多,对 效率有影响.

算法思想:

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

Snipaste_2021-10-20_12-15-24

希尔排序时,对有序序列在插入时采用交换法,并测试排序速度;

交换法代码(速度慢):

public static void shellsort(int []arr){
    int temp=0;
    // 根据前面的逐步分析,使用循环处理
    for(int g=arr.length/2;g>0;g/=2){
        for(int i = g;i<arr.length;i++){
            // 遍历各组中所有的元素(共gap组,每组有个元素), 步长gap
            for(int j=i-g;j>=0;j-=g){
                // 如果当前元素大于加上步长后的那个元素,说明交换
                if(arr[j]>arr[j+1]){
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
    }

}

插入法(较快):

public static void shellsort2(int []arr){

    // 增量gap, 并逐步的缩小增量
    for(int g=arr.length/2;g>0;g/=2){
        // 从第gap个元素,逐个对其所在的组进行直接插入排序
        for(int i = g;i<arr.length;i++){
            int temp =arr[i];
            int index = i-g;
            while(index>=0&&temp<arr[index]){
                //移动
                arr[index+g]=arr[index];
                index-=g;
            }
            // 当退出while循环时,说明插入的位置找到, insertIndex + 1
            //这里我们判断是否需要赋值
            //当退出while后,就给temp找到插入的位置
            if (index + g != i) {
                arr[index + g] = temp;
            }
        }

    }
}

5.快速排序

快速排序(Quicksort)是对冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。Snipaste_2021-10-20_15-56-30

//以中间的数为基准
public static void quicksort(int[] arr, int left, int right) {
    int l = left;//左下标
    int r = right;//右下标

    //pivot中轴值
    int pivot = arr[(left + right) / 2];
    int temp = 0;//临时变量,作为交换时使用
    //while循环的目的是让比pivot值小的放到左边
    //比pivot大的放在右边
    while (l < r) {
        //在pivot的左边一直找,找到大于等于pivot值,才推出
        while (arr[l] < pivot) {
            l += 1;
        }
        //在pivot的右边一直找,找到小于等于pivot值才退出
        while (arr[r] > pivot) {
            r -= 1;
        }

        //如果l>=r说明pivot的左右两的值,已经按照左边全是
        //小于等于pivot的值,右边全是大于等于pivot的值
        if (l >= r) {
            break;
        }

        //交换
        temp = arr[l];
        arr[l] = arr[r];
        arr[r] = temp;

        //如果交换完成后,发现这个arr[l]==pivot值相等 则r--,前移
        if (arr[l] == pivot) {
            r -= 1;
        }
        //如果交换完成后,发现arr[r]==pivot值相等 则l++,后移
        if (arr[r] == pivot) {
            l += 1;
        }
    }
    //如果l==r,必须l++,r--,否则出现栈溢出
    if (l == r) {
        l += 1;
        r -= 1;
    }
    //向左递归
    if (left < r) {
        quicksort(arr, left, r);
    }
    //向右递归
    if (right > l) {
        quicksort(arr, l, right);
    }
}

//以第一个数为基准
public static void quicksort2(int[] arr, int left, int right) {

    if (left > right) {
        return;
    }
    int basevalue = arr[left];
    int i = left, j = right;//左指针


    while (i != j) {
        //右指针从右至左遍历,直至遇见比基准值小的数
        while (arr[j] >= basevalue && i < j) {
            j--;
        }

        //左指针从第一个遍历,直至遇到
        while (arr[i] <= basevalue && i < j) {
            i++;
        }

        if (i < j) {//l与r没有相遇
            //交换位置
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }

    }
    //此时l与r相遇与基准值交换顺序
    arr[left] = arr[i];
    arr[i] = basevalue;


    quicksort2(arr, left, i - 1);
    quicksort2(arr, i + 1, right);

}

6.归并排序

归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。

Snipaste_2021-10-21_11-02-16

再来看看治阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤Snipaste_2021-10-21_11-02-52

package Algorithm.Sort;

import java.util.Arrays;

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];

        mergetsort(arr, 0, arr.length - 1, temp);
        System.out.println(Arrays.toString(arr));

    }


    //分+合方法
    public static void mergetsort(int[] arr, int left, int right, int[] temp) {
        if (left < right) {
            int mid = (left + right) / 2; //中间索引
            //向左递归进行分解
            mergetsort(arr, left, mid, temp);
            //向右递归进行分解
            mergetsort(arr, mid + 1, right, temp);
            //合并
            merge(arr, left, mid, right, temp);

        }
    }


    //合并的方法

    /**
     * @param arr   排序的原始数组
     * @param left  左边有序序列的初始索引
     * @param mid   中间索引
     * @param right 右边索引
     * @param temp  做中转的数组
     */
    public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
        int i = left;//初始化i, 左边有序序列的初始索引
        int j = mid+1;//初始化j, 右边有序序列的初始索引
        int t = 0;// 指向temp数组的当前索引
        //(一)
        //先把左右两边(有序)的数据按照规则填充到temp数组
        //直到左右两边的有序序列,有一边处理完毕为止
        while (i <= mid && j <= right) {
            //如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
            //即将左边的当前元素,填充到 temp数组
            //然后 t++, i++
            if (arr[i] <= arr[j]) {
                temp[t] = arr[i];
                i++;
                t++;
            } else {//反之,将右边有序序列的当前元素,填充到temp数组
                temp[t] = arr[j];
                j++;
                t++;
            }
        }
        //(二)
        //把有剩余数据的一边的数据依次全部填充到temp
        while (i <= mid) {//左边有剩余
            temp[t] = arr[i];
            t++;
            i++;
        }
        while (j <= right) {//右边有剩余
            temp[t] = arr[j];
            t++;
            j++;
        }

        //(三)
        //将temp数组的元素拷贝到arr
        //注意,并不是每次都拷贝所有
        t = 0;
        int templeft = left;
        //第一次合并 tempLeft = 0 , right = 1 //  tempLeft = 2  right = 3 // tL=0 ri=3
        //最后一次 tempLeft = 0  right = 7
        while (templeft <= right) {
            arr[templeft] = temp[t];
            t++;
            templeft++;
        }
    }
}

7. 基数排序

基本思想:将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。

将数组 {53, 3, 542, 748, 14, 214} 使用基数排序, 进行升序排序

第一轮排序:

Snipaste_2021-10-23_20-06-22

第二轮排序:

Snipaste_2021-10-23_20-07-07

第三轮排序:

Snipaste_2021-10-23_20-07-35

代码:

public static void radixsort(int []arr){    //根据前面的推导过程,我们可以得到最终的基数排序代码    //1. 得到数组中最大的数的位数    int max = arr[0]; //假设第一数就是最大数    for(int i = 1; i < arr.length; i++) {        if (arr[i] > max) {            max = arr[i];        }    }    //得到最大数是几位数    int maxLength = (max + "").length();    //定义一个二维数组,表示10个桶, 每个桶就是一个一维数组    //说明    //1. 二维数组包含10个一维数组    //2. 为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.length    //3. 名明确,基数排序是使用空间换时间的经典算法    int[][] bucket = new int[10][arr.length];    //为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数    //可以这里理解    //比如:bucketElementCounts[0] , 记录的就是  bucket[0] 桶的放入数据个数    int[] bucketElementCounts = new int[10];    //这里我们使用循环将代码处理    for(int i = 0 , n = 1; i < maxLength; i++, n *= 10) {        //(针对每个元素的对应位进行排序处理), 第一次是个位,第二次是十位,第三次是百位..        for(int j = 0; j < arr.length; j++) {            //取出每个元素的对应位的值            int digitOfElement = arr[j] / n % 10;            //放入到对应的桶中            bucket[digitOfElement[bucketElementCounts[digitOfElement]]= arr[j];            bucketElementCounts[digitOfElement]++;        }        //按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)        int index = 0;        //遍历每一桶,并将桶中是数据,放入到原数组        for(int k = 0; k < bucketElementCounts.length; k++) {            //如果桶中,有数据,我们才放入到原数组            if(bucketElementCounts[k] != 0) {                //循环该桶即第k个桶(即第k个一维数组), 放入                for(int l = 0; l < bucketElementCounts[k]; l++) {                    //取出元素放入到arr                    arr[index++] = bucket[k][l];                }            }            //第i+1轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!!            bucketElementCounts[k] = 0;        }    }

8. 堆排序

堆排序基本介绍

  1. 堆排序是利用这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。

  2. 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆, 注意 : 没有要求结点的左孩子的值和右孩子的值的大小关系。

  3. 每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆

  4. 大顶堆举例说明

image-20211029100314085

我们对堆中的结点按层进行编号,映射到数组中就是下面这个样子:

image-20211029100331909

大顶堆特点:arr[i] >= arr[2i+1] &&arr[i] >=arr[2i+2] // i 对应第几个节点,i从0开始编号.

  1. 小顶堆举例说明

image-20211029100622221

小顶堆:arr[i]<=arr[2i+1]&&arr[i]<=arr[2i+2]//i对应第几个节点,i从0开始编号

堆排序基本思想

  1. 将待排序序列构造成一个大顶堆

  2. 此时,整个序列的最大值就是堆顶的根节点。

  3. 将其与末尾元素进行交换,此时末尾就为最大值。

  4. 然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。

可以看到在构建大顶堆的过程中,元素的个数逐渐减少,最后就得到一个有序序列了.

要求:给你一个数组 {4,6,8,5,9} , 要求使用堆排序法,将数组升序排序。

步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。

  1. 假设给定无序序列结构如下

clip_image002

  1. 此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。clip_image004

  2. 找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。

clip_image006

  1. 这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。

  2. 此时,我们就将一个无序序列构造成了一个大顶堆。clip_image008

步骤二 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。

  1. 将堆顶元素9和末尾元素4进行交换

clip_image010

  1. 重新调整结构,使其继续满足堆定义clip_image012

  2. 进行交换,得到第二大元素8.

clip_image014

  1. 后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序

clip_image016

再简单总结下堆排序的基本思路:

1. 将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;

  2. 将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;

  3. 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

//将一个数组(二叉树), 调整成一个大顶堆    public  static void adjustHeap(int arr[], int i, int len){        int temp = arr[i];//先取出当前元素的值,保存在临时变量        //开始调整        //说明        //1. k = i * 2 + 1 k 是 i结点的左子结点        for(int k=2*i+1;k<len;k=2*k+1){            if(k+1<len&&arr[k]<arr[k+1]){                k++;//比较父节点i对应的两个左右孩子节点大小            }            if(arr[i]<arr[k]){//如果子结点大于父结点                arr[i]=arr[k];//把较大的值赋给当前结点                i=k;//!!! i 指向 k,继续循环比较            }        }        //当for 循环结束后,我们已经将以i 为父结点的树的最大值,放在了 最顶(局部)        arr[i]=temp;//将temp值放到调整后的位置    }

代码解析:

Snipaste_2021-10-29_15-33-00

posted @ 2021-11-21 16:35  zjh1170  阅读(53)  评论(0)    收藏  举报