【一起学数据结构与算法】几种常见的排序(插入排序、选择排序、交换排序

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

(2).序列再次被分为2组,分别为{4,2,5,8,5},{1,3,9,6,7},再对这两组进行直接插入排序,那么序列就更加有序了!
在这里插入图片描述
在这里插入图片描述
(3).然后缩小步长gap = gap / 2 = 1,这时整个序列被分为{2,1,4,3,5,6,5,7,8,9};

在这里插入图片描述
在这里插入图片描述

1.2.2 代码实现
  public static void shell(int[] array,int gap) {
        for (int i = gap; i < array.length; i++) {
            int tmp = array[i];
            int j = i-gap;
            for (; j >= 0;j-=gap) {
                if(array[j] > tmp) {
                    array[j+gap] = array[j];
                }else {
                    break;
                }
            }
            array[j+gap] = tmp;
        }
    }
    public static void shellSort(int[] array) {
        int gap = array.length;
        while (gap > 1) {
            gap /= 2;
            shell(array,gap);
        }
    }

1.2.3 特征分析
1. 时间复杂度  
 因为gap的取值方法很多,导致很难去计算,因此在好些书中给出的希尔排序的时间复杂度都不固定,如果没有特殊说明,我们通常就取时间复杂度为O(n^1.3)。
2. 空间复杂度  
 空间复杂度为O(1)
3. 稳定性  
 不稳定的。## 2、选择排序

选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。

2.1 直接选择排序

直接选择排序(Straight Select Sorting) 也是一种简单的排序方法,它的基本思想是:第一次从R[0]~R[n-1]中选取最小值,与R[0]交换,第二次从R[1] ~ R[n-1]中选取最小值,与R[1]交换,…,第i次从R[i-1]~R[n-1]中选取最小值,与R[i-1]交换,…,第n-1次从R[n-2] ~ R[n-1]中选取最小值,与R[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列。

请添加图片描述

2.1.1 思路

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

举一个例子来帮助大家理解:

9,1,2,5,7,4,8,6,3,5

具体排序过程:

1. 将整个记录序列划分为有序区和无序区,初始时有序区为空,无序区含有待排序的所有记录
2. 在无序区选择关键码最小的记录,将其与无序区中的第一个元交换,使得有序区扩展一个记录,同时无序区减少了一个记录
3. 不断重复步骤 2,直到无序区只剩下一个记录为止![在这里插入图片描述](https://img-blog.csdnimg.cn/3962cfd559dc42aeaebff9ac85672232.png)
2.1.2 代码实现
 public static void selectSort(int[] array){
        for (int i = 0; i < array.length; i++) {
            int minIndex = i;
            int j = i + 1;
            for (; j < array.length; j++) {
                if (array[j] < array[minIndex]) {
                    minIndex = j;
                }
            }
            swap(array, i, minIndex);
        }
    }
    public static void swap(int[] array,int i,int j){
        int tmp = array[i];
        array[i] = array[j];
        array[j] =tmp;
    }

2.1.3 特性分析
1. 时间复杂度  
 时间复杂度为O(n^2)
2. 空间复杂度  
 空间复杂度为O(1)
3. 稳定性  
 不稳定的。### 2.2 堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。 需要注意的是排升序要建大堆,排降序建小堆。
请添加图片描述

2.2.1 思路

想要学习堆排序的可以去看一下【一起学习数据结构与算法】优先级队列(堆),这里已经讲过堆排序了!

2.2.2 代码实现

我们这里还是附上堆排序的代码!

  public static void heapSort(int[] array) {//堆排序
        createBigHeap(array);//O(n)
        int end = array.length-1;
        while (end > 0) {
            swap(array,0,end);
            shiftDown(array,0,end);
            end--;
        }
    }
 
    private static void createBigHeap(int[] array) {//创建大根堆
        for (int parent = (array.length-1-1)/2; parent >= 0 ; parent--) {
            shiftDown(array,parent,array.length);
        }
    }
 
    private static void shiftDown(int[] array,int parent,int len) {//向下调整
        int child = (2 \* parent) + 1;
        while (child < len) {
            if (child + 1 < len && array[child] < array[child + 1]) {
                child++;
            }
            if (array[child] > array[parent]) {
                swap(array, child, parent);
                parent = child;
                child = 2 \* parent + 1;
            } else {
                break;
            }
        }
    }

特性分析
1. 时间复杂度  
 时间复杂度为O(n \* logn)
2. 空间复杂度  
 空间复杂度为O(1)
3. 稳定性  
 不稳定的。## 3、交换排序

所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序 的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

3.1 冒泡排序

冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。
它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行,直到没有相邻元素需要交换,也就是说该元素列已经排序完成。
这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。
请添加图片描述

3.1.1 思路

冒泡排序基本思想就是相邻元素挨个比较,遇到比自己小的就交换,直到最后元素有序。

3.1.2 代码实现
    public static void bubbleSort(int[] array) {
        //最外层控制的是趟数
        for (int i = 0; i < array.length-1; i++) {
            boolean flg = false;
            for (int j = 0; j < array.length-1-i; j++) {
                if(array[j] > array[j+1]) {
                    swap(array,j,j+1);
                    flg = true;
                }
            }
            if(flg == false) {
                break;
            }
        }
    }

3.1.3 特性分析
1. 时间复杂度  
 时间复杂度为O(n ^ 2)
2. 空间复杂度  
 空间复杂度为O(1)
3. 稳定性  
 稳定的。### 3.2 快速排序

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元 素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有 元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

请添加图片描述

3.2.1 思路
3.2.1.1 Hoare法

假定基准值pivot是最左侧的元素,比较的时候从数组的尾部进行比较,

(1).当最右侧的元素大于基准值pivot的时候,right–.如果arr[left]<pivot的时候,就交换arr[left]和arr[right]的值。交换比较的方向,即从数组的头部left的位置向后扫描。

(2).如果arr[lleft]的值小于基准值pivot的话,left++,当arr[right]>=key的时候,交换arr[left]和arr[right]的值。再次交换比较的方向,数组从尾部high的位置从后往前扫描。

(3)不断重复1.2步,最终直到(left==right)的时候,low的位置就是该基准值在数组中的正确索引位置。

3.2.1.2 挖坑法

具体的步骤:

1. 我们需要设定一个基准值(一般为序列的最左边元素,也可以是最右边元素)此时最左边的是一个坑。
2. 开辟两个指针left和right,分别指向序列的头节点和尾节点(选取的基准值在左边,则从右边开始出发,反之,异然)
3. 假如让right从右往左走,找到第一个比基准小的元素,如果有,则把该值放到坑里,lefr从前往后遍历。
4. 找到比基准大的元素的下标,然后用这个元素放到坑里。
5. 依次循环,直到left指针和right指针重合时,我们把基准值放入这连个指针重合的位置。举个例子:

4,7,6,5,3,2,8,1

我们选定基准元素Pivot,并记住这个位置index,这个位置相当于一个“坑”。并且设置两个指针left和right,指向数列的最左和最右两个元素:

在这里插入图片描述
接下来,从right指针开始,把指针所指向的元素和基准元素做比较。如果比pivot大,则right指针向左移动;如果比pivot小,则把right所指向的元素填入坑中。

在当前数列中,1<4,所以把1填入基准元素所在位置,也就是坑的位置。这时候,元素1本来所在的位置成为了新的坑。同时,left向右移动一位。
在这里插入图片描述

接下来,我们切换到left指针进行比较。如果left指向的元素小于pivot,则left指针向右移动;如果元素大于pivot,则把left指向的元素填入坑中。

在当前数列中,7>4,所以把7填入index的位置。这时候元素7本来的位置成为了新的坑。同时,right向左移动一位。
在这里插入图片描述
8>4,元素位置不变,right左移
在这里插入图片描述
2<4,用2来填坑,left右移,切换到left。
在这里插入图片描述
6>4,用6来填坑,right左移,切换到right。
在这里插入图片描述
3<4,用3来填坑,left右移,切换到left。
在这里插入图片描述
5>4,用5来填坑,right右移。这时候left和right重合在了同一位置。
在这里插入图片描述

这时候,把之前的pivot元素,也就是4放到index的位置。此时数列左边的元素都小于4,数列右边的元素都大于4,这一轮交换终告结束。
在这里插入图片描述

3.2.1.3 前后指针法

什么是前后遍历,前后遍历就是两个指针一前一后,从头开始遍历,当遇到比基准小的值,俩个指针往后走一步,遇到比基准值大的就prev指针不动,cur往后走,当cur遇到比基准值小的就停下来, 然后cur指针每一次停止俩个指针之间的位置比较一下,如果俩个之间的差不是一的话,就交换俩个位置的数据,一直循环,直到遍历结束,用prev的后一个不是基准元素的位置的话,就,让prev和基准值进行交换。

3.2.2 代码实现
3.2.2.1 Hoare法
 private static int partitionHoare(int[] array,int left,int right) {//hoare法
        int i = left;
        int pivot = array[left];
        while (left < right) {
            //left < right && 这个条件不能少 预防后面都比基准大
            while (left < right && array[right] >= pivot) {
                right--;
            }
            //代码走到这里表示right下标的值 小于pivot
            while (left < right && array[left] <= pivot) {
                left++;
            }
            //left下标的值 大于pivot
            swap(array,left,right);
        }
        //交换 和 原来的left
        swap(array,left,i);
        return left;
    }

3.2.2.2 挖坑法
  private static int partition(int[] array,int left,int right) {//挖坑法,优先使用
        int pivot = array[left];
        while (left < right) {
            //left < right && 这个条件不能少 预防后面都比基准大
            while (left < right && array[right] >= pivot) {
                right--;
            }
            array[left] = array[right];
            //right下标的值 小于pivot
            while (left < right && array[left] <= pivot) {
                left++;
            }
            array[right] = array[left];
        }
        //交换 和 原来的left
        array[left] = pivot;
        return left;
    }

3.2.2.3 前后指针法
    private static int partition(int[] array, int left, int right) {//前后指针法
        int prev = left ;
        int cur = left+1;
        while (cur <= right) {
            if(array[cur] < array[left] && array[++prev] != array[cur]) {
                swap(array,cur,prev);
            }
            cur++;
        }
        swap(array,prev,left);
        return prev;
    }

3.2.3 特性分析
1. 时间复杂度  
 时间复杂度为O(n \* logn)  
 最坏情况达到O(n \* 2)
2. 空间复杂度  
 空间复杂度为O(logn)
3. 稳定性  
 不稳定。#### 3.2.3 快速排序的优化
3.2.3.1 规模较小的优化

每次递归的时候,数据都是再慢慢变成有序的
当数据量少且趋于有序的时候,我们可以直接使用插入排序进行优化

  public static int partitionHole(int[] array,int low,int high){
        int tmp = array[low];
        while(low < high) {
            while (low < high && array[high] >= tmp){
                high--;
            }
            array[low] = array[high];
            while (low < high && array[low] <= tmp){
                low++;
            }
            array[high] = array[low];
        }
        array[low] = tmp;
        return low;
 
    }
  public static void quickSort(int[] array,int left,int right){
        if(left >= right) return;
        if(right-left+1 <= 10000){  //某个区间内的小规模排序直接插入排序
            //进行插入排序
            insertSortRange(array,left,right);
            return;
        }
        int pivot = partitionHole(array,left,right);
        quickSort(array,left,pivot-1);
        quickSort(array,pivot+1,right);
    }
  public static void insertSortRange(int[] array,int low, int end){
        for(int i = low+1 ; i<=end ;i++){
            int tmp = array[i];
            int j = i-1;
            for(; j >= low ; j--){
                if(array[j] > tmp){
                    array[j+1] = array[j];
                }else{
                    break;
                }
            }
            array[j+1] = tmp;
        }
    }

但是这个优化并没有根本解决 有序情况下 递归深度太深的优化

3.2.3.2 三数取中法

我们用 三数取中法 选key
三数取中:头,尾,中间元素中 大小居中 的那一个,再把这个元素和队头元素互换,作为key

 public static int partitionHole(int[] array,int low,int high){
        int tmp = array[low];
        while(low < high) {
            while (low < high && array[high] >= tmp){
                high--;
            }
            array[low] = array[high];
            while (low < high && array[low] <= tmp){
                low++;
            }
            array[high] = array[low];
        }
        array[low] = tmp;
        return low;
 
    }    
//三数取中,找到首,中,尾三个数中 中等大小的数的下标
    private static int medianOfThreeIndex(int[] array, int left, int right){
        int mid = left + ((right-left)>>>1);
        //int mid = (right+left)/2 ;
        if(array[left] < array[right]){
            if(array[mid] < array[left]){
                return left;
            }else if(array[mid] > array[right]){
                return right;
            }else{
                return mid;
            }
        }else{
            if(array[mid] < array[right]){
                return right;
            }else if(array[mid] > array[left]){
                return left;
            }else{
                return mid;
            }
        }
    }
    public static void quickSort(int[] array,int left,int right){
        if(left >= right) return;
        //1.某个区间内的小规模排序直接插入排序【优化的是区间内的比较】
        if(right-left+1 <= 10000){
            //进行插入排序
            insertSortRange(array,left,right);
            return;
        }
        //2.三数取中法【优化的是本身的分割】
        int index = medianOfThreeIndex(array,left,right);
        swap(array,left,index);
 
        int pivot = partitionHole(array,left,right);
        quickSort(array,left,pivot-1);
        quickSort(array,pivot+1,right);
    }

3.2.3.3 非递归实现快速排序

我们需要用到栈.

我们之前是在已经确定基准点之后,对剩余的区间递归进行同样的操作

我们现在创建一个栈,把剩余区间的左、右位置的下标分别放入栈中,如图是已经找到一个基准3的情况

在这里插入图片描述
然后弹出栈顶一个元素9给H,再弹出一个栈顶元素6给L,根据新的L和H找到新的基准,再重复上面的操作

在这里插入图片描述

//挖坑法
    public static int partitionHole(int[] array,int low,int high){
        int tmp = array[low];
        while(low < high) {
            while (low < high && array[high] >= tmp){
                high--;
            }


![img](https://img-blog.csdnimg.cn/img_convert/02ada377ebabd1bb7733349f828d033c.png)
![img](https://img-blog.csdnimg.cn/img_convert/811301bdd666da1e3513e3975823f927.png)
![img](https://img-blog.csdnimg.cn/img_convert/847af192cbdba34ee5871377fb4c487e.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**




//挖坑法
public static int partitionHole(int[] array,int low,int high){
int tmp = array[low];
while(low < high) {
while (low < high && array[high] >= tmp){
high–;
}

[外链图片转存中…(img-qjOxfWte-1715706406509)]
[外链图片转存中…(img-JRxENuG5-1715706406509)]
[外链图片转存中…(img-hfEmJ6AQ-1715706406509)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值