数据结构10--排序算法

目录

1.冒泡排序

2.插入排序

3.选择排序

 4.希尔排序:

5.归并排序

6.快速排序

7.堆排序


1.冒泡排序

   1.概述

    N个数字要排序完成,总共进行N-1趟排序,每i趟的排序次数为(N-i)次,所以可以用双重循环语句,外层控制循环多少趟,内层控制每一趟的循环次数,每次都沉淀一个最大的数。

在一轮循环中,如果没有发生交换,那么说明数组已经是有序的,此时可以直接退出。

      2.代码实现

    定义一个布尔变量 hasChange,用来标记每轮是否进行了交换。在每轮遍历开始时,将 hasChange 设置为 false。若当轮没有发生交换,说明此时数组已经按照升序排列,hasChange 依然是为 false。此时外层循环直接退出,排序结束。

static void bubbleSorting(int[] arr) {
        boolean hasChange = true;
        for (int i = 0, n = arr.length; i < n && hasChange; i++) {
            hasChange = false;
            for (int j = 0; j < n - i - 1; j++) {
                if (arr[j] > arr[j + 1]) {
                    int temp = arr[j + 1];
                    arr[j + 1] = arr[j];
                    arr[j] = temp;
                    hasChange = true;
                }
            }
        }

    }

 3.算法分析

 空间复杂度 O(1)、时间复杂度 O(n²)。

 这是一个稳定的算法,稳定是指,两个相等的数,在排序过后,相对位置保持不变。

     

分情况讨论:

  1. 给定的数组按照顺序已经排好:只需要进行 n-1 次比较,两两交换次数为 0,时间复杂度为 O(n),这是最好的情况。
  2. 给定的数组按照逆序排列:需要进行 n*(n-1)/2 次比较,时间复杂度为 O(n²),这是最坏的情况。
  3. 给定的数组杂乱无章。在这种情况下,平均时间复杂度 O(n²)。

2.插入排序

  1.概述

  每次都将当前元素插入到左侧已经排序的数组中,使得插入之后左侧数组依然有序。

                

  2. 代码实现 

 static void insertionSort(int[] arr) {
        for (int i = 1; i < arr.length; i++) {
            for (int j = 0; j <= i; j++) {
                if (arr[i] < arr[j]) {
                    int temp = arr[j];
                    arr[j] = arr[i];
                    arr[i] = temp;
                }
            }
        }
    }

3. 算法分析

  • 在冒泡排序中,经过每一轮的排序处理后,数组后端的数是排好序的。
  • 在插入排序中,经过每一轮的排序处理后,数组前端的数是排好序的。

  空间复杂度 O(1),时间复杂度 O(n²)。

分情况讨论:

  1. 给定的数组按照顺序排好序:只需要进行 n-1 次比较,两两交换次数为 0,时间复杂度为 O(n),这是最好的情况。
  2. 给定的数组按照逆序排列:需要进行 n*(n-1)/2 次比较,时间复杂度为 O(n²),这是最坏的情况。
  3. 给定的数组杂乱无章:在这种情况下,平均时间复杂度是 O(n²)。

因此,时间复杂度是 O(n²),这也是一种稳定的排序算法。

4.优化插入排序

  折半插入排序:向有序的序列中插入元素,那么插入位置可以不断的平分有序序列,并把待插入的元素的关键字与平分有序序列得到的关键字比较。以确定下一步要平分的子序列。直到找到合适的插入位置为止。找到插入位置后 从插入位置开始将数组整体右移到循环的位置。

 static void binaryInsertionSort(int[] arr) {
        for (int i = 1; i < arr.length; i++) {
            int left = 0;
            int right = i;  // 1

            while (left < right) {
                int mid = (left + right) >> 1;  // 0
                if (arr[i] <= arr[mid]) {
                    right = mid;
                } else {
                    left = mid + 1;
                }
            }
            int temp = arr[i];
            //找到合适的插入位置,数组就从插入的位置开始移动
            for (int j = i; j > left; j--) {
                arr[j] = arr[j - 1];
            }
            arr[left] = temp;
        }
    }

 时间复杂度O(nlogn) 空间复杂度O(1)

3.选择排序

 1.概述

  从数组中选择最小元素,将它与数组的第一个元素交换位置。再从数组剩下的元素中选择出最小的元素,将它与数组的第二个元素交换位置。不断进行这样的操作,直到将整个数组排序。

  

2.代码实现

static void selectSort(int[] arr) {

        for (int i = 0; i < arr.length; i++) {
            int temp = arr[i];

            int minIndex = i;
            //找出剩余元素中最小的
            for (int j = i; j < arr.length - 1; j++) {
                if (arr[minIndex] > arr[j + 1]) {
                    minIndex = j + 1;
                }
            }

            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
    }

3.算法分析

空间复杂度 O(1),时间复杂度 O(n²)。

那选择排序是稳定的排序算法吗?

答案是否定的,选择排序是一种不稳定的排序算法。选择排序每次都要找剩余未排序元素中的最小值,并和前面的元素交换位置,这样破坏了稳定性。

比如 5,8,5,2,9 这样一组数据,使用选择排序算法来排序的话,第一次找到最小元素 2,与第一个 5 交换位置,那第一个 5 和中间的 5 顺序就变了,所以就不稳定了。正是因此,相对于冒泡排序和插入排序,选择排序就稍微逊色了。

 4.希尔排序:

 1. 概述

      对于大规模的数组,插入排序很慢,因为它只能交换相邻的元素,每次只能将逆序数量减少 1。希尔排序的出现就是为了解决插入排序的这种局限性,它通过交换不相邻的元素,每次可以将逆序数量减少大于 1。

   先将整个待排序的序列分割成若干个子序列,每个子序列由相差一定长度的数据元素组成(这个相差的长度称为增量),然后我们分别对这些子序列进行直接插入排序,一轮排序后再取第二个增量,以此类推,需要注意的是对于希尔排序中增量的确定没有统一的规定,通常的做法是:第一个增量为待排序序列长度的二分之一(取整),然后逐渐减半(取整),直到等于1为止。

  增量怎么选:素数的比较好。最好用素数作为增量。

  例如:

  第一步:按照增量为3的进行分组,即1,4一组,2,5一组,3单独一组

  

     

     第二步:增量为2,1,3一组,2,4一组,3,5一组

  

  第三步:

    

  2. 代码实现

  static void shellSort(int[] arr) {
        int increment = (arr.length >> 1);
        for (int i = increment; i > 0; i--) {  // 步长 >0
            for (int j = 0; j < arr.length - i; j++) {
                if (arr[j] > arr[j + i]) {
                    int temp = arr[j];
                    arr[j] = arr[i + j];
                    arr[i + j] = temp;
                }
            }
        }
    }
package com.wx.datastructure;
/*
   希尔排序的思想:
   先将整个待排序的序列分割成若干个子序列,每个子序列由相差一定长度的数据元素组成(这个相差的长度称为增量),
   然后我们分别对这些子序列进行直接插入排序,一轮排序后再取第二个增量,
   以此类推,需要注意的是对于希尔排序中增量的确定没有统一的规定,通常的做法是:
   第一个增量为待排序序列长度的二分之一(取整),然后逐渐减半(取整),直到等于1为止。
 */
public class ShellSort {
    public static void main(String[] args)
    {
        int[] data={2,6,4,3,9,5};
        int len=data.length;
        int temp;
        //设置增量,增量是数据长度的一半依次减一减到一
        for (int k=len/2;k>0;k--)
        {
            //将依据增量分割的序列进行对比
            for(int i=k;i<len;i++)
            {
                if (data[i-k]>data[i])
                {
                    //交换数据
                    temp=data[i-k];
                    data[i-k]=data[i];
                    data[i]=temp;
                }
            }
        }
        //输出
        for(Integer x:data)
        {
            System.out.print(" "+x);
        }
    }
}

  

3.算法分析

  时间复杂度:

希尔排序的时间性能取决于所取“增量”序列的函数,这涉及到一些数学上尚未解决的难题。但是有人通过大量的实验,给出了较好的结果:当 n 较大时,比较和移动的次数约在 n^1.25 到 (1.6n)^1.25 之间。所以可以这样简单记忆:

  • 当 n 较小时,希尔排序和插入排序相差不大,都为 n² 左右。
  • 当 n 很大时,时间增长幅度逐渐放缓,平均复杂度是 nlogn。

空间复杂度:O(1)。

5.归并排序

1.概述

归并排序的核心思想是分治,把一个复杂问题拆分成若干个子问题来求解。

归并排序的算法思想是:把数组从中间划分为两个子数组,一直递归地把子数组划分成更小的数组,直到子数组里面只有一个元素的时候开始排序。排序的方法就是按照大小顺序合并两个元素。接着依次按照递归的顺序返回,不断合并排好序的数组,直到把整个数组排好序。

 2.代码实现

   首先 将数组一分为二,然后将左半部分继续分,分到只有一个元素为止,代码就是

int mid = (low + high) >>> 1;
mergeSort(arr, low, mid);

  由于是递归,他会返回,返回时每层都达到条件low >= high,直接返回到最高层,此时左边部分分治结束,然后开始进行右边的分治

mergeSort(arr, mid + 1, high);

  当右边以相同德方式分治完成后,low=high=mid=0;开始归并两个归并段。

 是比较两个归并段的首位元素的大小,然后将其放入临时数组中,最后剩下的归并段的数据直接拷入临时数组。

   最后将临时数组拷贝到原始数组,然后i一层层递归回去。

package com.wx.sort;

/**
 * Created by wangxiang on 2022/2/22
 */
public class MergeSortWX {
    public static void main(String[] args) {
        int[] data = {7, 16, 23, 42, 30, 1, 9, 5, 6, 55, 2, 3, 3};

        mergeSort(data, 0, data.length - 1);


    }


    static void mergeSort(int[] arr, int low, int high) {
        //采用分治的思想,先分
        if (low >= high) {
            return;
        }
        int mid = (low + high) >>> 1;
        mergeSort(arr, low, mid);
        mergeSort(arr, mid + 1, high);

        //分完之后就开始和,合并的条件就是
        merge(arr, low, high, mid);
    }

    private static void merge(int[] arr, int low, int high, int mid) {
        //将两个归并集合进行合并,首先需要保证的是归并集合钟有元素
        int s1 = low;  //第一个归并集合的起始序列
        int s2 = mid + 1;  //四二个归并集合的起始序列

        //归并两个集合后的临时数组
        int[] tempArr = new int[high - low + 1];
        int i = 0;//临时数组的初始下标

        //保证两个归并集合必须有元素才能合并
        while (s1 <= mid && s2 <= high) {
            //从两个归并集合的第一个元素开始比较,谁小谁排前面
            if (arr[s1] >= arr[s2]) {
                tempArr[i] = arr[s2];
                //将小的拉下后小的归并段下标加1
                s2++;
                i++;
            } else {
                tempArr[i] = arr[s1];
                s1++;
                i++;
            }
        }
        //当其中一个归并段先排完时,把剩下的归并段直接填到临时数组
        while (s1 <= mid) {
            tempArr[i] = arr[s1];
            i++;
            s1++;
        }
        while (s2 <= high) {
            tempArr[i] = arr[s2];
            i++;
            s2++;
        }

        //最后一步就是将临时数组拷贝到原数组的位置
        for (int j = low; j <= high; j++) {
            arr[j] = tempArr[j - low];
        }

    }
}

3.算法分析

  时间复杂度 O(nlogn)

  空间复杂度 O(n)

   稳定性:稳定

6.快速排序

1.  概述

   快速排序也采用了分治的思想:把原始的数组筛选成较小和较大的两个子数组,然后递归地排序两个子数组。

  首先把数组中的一个数当做基准,一般会把数组中最左边的数当做基准base,从右边开始检索,找到比基准base小的数,然后从左边开始检索,找到比基准大的数之后停下,然后交换两个数的位置,继续检索,如果两边的检索相遇了,那么与基准数交换,这个时候数组就被分成了左右两个,然后递归继续排序左边的部分,完了之后递归排序右边的部分。

  

 

 递归的终止条件就是low>high,  因为low是可以等于high的所以递归的终止条件就是low>high,然后开始循环检索,检索的终止条件是i==j,因为这个时候就要交换基准数,交换基准数之前,从右边检索,在i<j的条件下找到小于基准数的位置,从左边检索,在i<j的条件下找到大于基准数的位置。然后交换两个数。

如果i==j,交换基准数,然后递归处理左右两边

2.代码实现

package com.wx.sort;

/**
 * Created by wangxiang on 2022/2/26
 */
public class QuickSort {

    public static void main(String[] args) {
        int[] data = {7, 16, 23, 42, 30, 1, 9, 5, 6, 55, 2, 3, 3};
        int low = 0;
        int high = data.length - 1;


        quickSort(data, low, high);

    }

    static void quickSort(int[] arr, int low, int high) {
        if (low > high) {
            return;
        }
        //
        int i = low;
        int j = high;
        int base = arr[i];
        while (i != j) {
            //从右开始检索,如果小于基准,则停下
            while (arr[j] >= base && i < j) {
                j--;
            }
            //从左往右检索,如果大于基准则停下
            while (arr[i] <= base && i < j) {
                i++;
            }
            //走到这里,说明low和high都停下了,交换两个的位置
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }

        //走到这里说明两者相遇了,这个时候就要交换基准的位置
        arr[low] = arr[i];
        arr[i] = base;

        //交换完基准以后数组被分为左右两边,递归处理左边
        quickSort(arr, low, i - 1);
        //递归处理右边
        quickSort(arr, j + 1, high);


    }
}

3.算法分析

快速排序是原地排序,不需要辅助数组,但是递归调用需要辅助栈。快速排序最好的情况下是每次都正好将数组对半分,这样递归调用次数才是最少的。这种情况下比较次数为 CN=2CN/2+N,复杂度为 O(NlogN)。

空间复杂度:logN

稳定性:不稳定

7.堆排序

1.概述

2.代码实现

3.算法分析

       

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

时空恋旅人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值