Java数据结构第二十期:解构排序算法的艺术与科学(二)

专栏:Java数据结构秘籍

个人主页:手握风云

目录

一、常见排序算法的实现

[1.1. 直接选择排序](#1.1. 直接选择排序)

[1.2. 堆排序](#1.2. 堆排序)

[1.3. 冒泡排序](#1.3. 冒泡排序)

[1.4. 快速排序](#1.4. 快速排序)


一、常见排序算法的实现

1.1. 直接选择排序

每⼀次从待排序的数据元素中选出最小的⼀个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。第一轮,先让j下标遍历出数组中最小的元素,再利用MinIndex存放最小的下标,利用最小值与i下标的元素进行交换;第二轮,依然让j下标遍历出待排序中最小的元素,再利用MinIndex存放最小的下标,利用最小值与i下标的元素进行交换......知道i走到最后一个元素,这样就能保证i之前的元素全部是有序的。

java 复制代码
import java.util.Random;

public class Sort {
    public void SelectSort(int[] array){
        for (int i = 0; i < array.length; i++) {
            int MinIndex = i;
            for (int j = i+1; j < array.length; j++) {
                if(array[MinIndex] > array[j]){
                    MinIndex = j;
                }
            }
            swap(array,MinIndex,i);
        }
    }

    private void swap(int[] array,int i,int j){
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }

    public void DisOrder(int[] array) {
        Random ran = new Random();
        for (int i = 0; i < array.length; i++) {
            array[i] = ran.nextInt(100);
        }
    }
}
java 复制代码
import java.util.Arrays;

public class Test {
    public static void main(String[] args) {
        int[] array = new int[6];
        Sort sort = new Sort();
        sort.DisOrder(array);
        System.out.println("排序前:" + Arrays.toString(array));

        sort.SelectSort(array);
        System.out.println("排序后:"+Arrays.toString(array));
    }
}

直接选择排序是不稳定的。空间上,没有使用额外的空间,空间复杂度为。时间上,

直接选择排序还有另外一种思路。与上面的方法不同的是,我们需要两个值MinIndex接收最小值下标和MaxIndex来接收最大值下标,再额外定义两个指针left和right。这种选择排序的思路是从首尾找,起始两个值接收下标都为0,利用i去遍历数组,找出最大值与最小值下标,再让left下标的值与MinIndex下标的值交换,right下标的值与MaxIndex下标的值交换。接着再让left向左移动,right向右移动,直到相遇,循环结束

完整代码实现:

java 复制代码
import java.util.Random;

public class Sort {
    public void DisOrder(int[] array) {
        Random ran = new Random();
        for (int i = 0; i < array.length; i++) {
            array[i] = ran.nextInt(100);
        }
    }

    public void SelectSort(int[] array) {
        int left = 0, right = array.length - 1;
        while (left < right) {
            int MinIndex = left, MaxIndex = left;
            for (int i = left + 1; i <= right; i++) {
                if (array[i] < array[MinIndex]) {
                    MinIndex = i;
                }
                if (array[i] > array[MaxIndex]) {
                    MaxIndex = i;
                }
            }
            swap(array, MinIndex, left);
            swap(array, MaxIndex, right);
            left++;
            right--;
        }
    }

    private void swap(int[] array, int i, int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }
}
java 复制代码
import java.util.Arrays;

public class Test {
    public static void main(String[] args) {
        Sort sort = new Sort();
        int[] array = new int[6];
        sort.DisOrder(array);
        System.out.println("排序前:" + Arrays.toString(array));
        sort.SelectSort(array);
        System.out.println("排序后:" + Arrays.toString(array));
    }
}

但我们一运行,就会发现,排序出现了问题,这是因为,如果最大值或最小值本身就在首尾,那么一交换,最大值或最小值就会跑掉,,所以我们还需要判断一下。

java 复制代码
import java.util.Random;

public class Sort {
    public void DisOrder(int[] array) {
        Random ran = new Random();
        for (int i = 0; i < array.length; i++) {
            array[i] = ran.nextInt(100);
        }
    }

    public void SelectSort(int[] array) {
        int left = 0, right = array.length - 1;
        while (left < right) {
            int MinIndex = left, MaxIndex = left;
            for (int i = left + 1; i <= right; i++) {
                if (array[i] < array[MinIndex]) {
                    MinIndex = i;
                }
                if (array[i] > array[MaxIndex]) {
                    MaxIndex = i;
                }
            }
            swap(array, MinIndex, left);
            /*if (left == MaxIndex) {
                MaxIndex = MinIndex;
            }*/
            swap(array, MaxIndex, right);
            left++;
            right--;
        }
    }

    private void swap(int[] array, int i, int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }
}
java 复制代码
import java.util.Arrays;

public class Test {
    public static void main(String[] args) {
        Sort sort = new Sort();
        int[] array = new int[6];
        sort.DisOrder(array);
        System.out.println("排序前:" + Arrays.toString(array));
        sort.SelectSort(array);
        System.out.println("排序后:" + Arrays.toString(array));
    }
}

1.2. 堆排序

堆排序是指利⽤堆积树这种数据结构所设计的⼀种排序算法,它是选择排序的⼀ 种,通过堆来进⾏选择数据。需要注意的是排升序要建大堆,排降序建小堆。

java 复制代码
import java.util.Random;

public class Sort {
    public void HeapSort(int[] array) {
        CreateHeap(array);
        int end = array.length - 1;
        while(end > 0){
            swap(array,0,end);
            ShiftDown(array,0,end);
            end--;
        }
    }

    private void CreateHeap(int[] array) {
        for (int parent = (array.length - 1 - 1) / 2; parent >= 0; parent--) {
            ShiftDown(array, parent, array.length);
        }
    }

    private void ShiftDown(int[] array, int parent, int length) {
        int child = 2 * parent + 1;
        while (child < length) {
            if (child + 1 < length && array[child] < array[child + 1]) {
                child++;
            }
            if (array[child] > array[parent]) {
                swap(array,parent,child);
                parent = child;
                child = 2 * parent + 1;
            } else {
                break;
            }
        }
    }

    private void swap(int[] array,int i,int j){
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }

    public void DisOrder(int[] array){
        Random ran = new Random();
        for (int i = 0; i < array.length; i++) {
            array[i] = ran.nextInt(1,100);
        }
    }
}
java 复制代码
import java.util.Arrays;

public class Solution {
    public static void main(String[] args) {
        Sort sort = new Sort();
        int[] array = new int[6];
        sort.DisOrder(array);
        System.out.println("排序前:"+ Arrays.toString(array));
        sort.HeapSort(array);
        System.out.println("排序前:"+ Arrays.toString(array));
    }
}

堆排序使⽤堆来选数,效率就⾼了很多。但堆排序是不稳定的,时间复杂度为,空间复杂度为

1.3. 冒泡排序

定义下标j,比较array[j]与array[j+1]的值,如果array[j] > array[j+1],则交换两数的位置。我们还可以进行一个优化,如果数组本身就是有序,或者没有走完所有的趟数就已经有序,那么后面就不用再比较了。

java 复制代码
import java.util.Random;

public class Sort {
    public void DisOrder(int[] array) {
        Random ran = new Random();
        for (int i = 0; i < array.length; i++) {
            array[i] = ran.nextInt(1, 100);
        }
    }

    public 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){
                break;
            }
        }
    }

    private void swap(int[] array,int i,int j){
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }
}
java 复制代码
import java.util.Arrays;

public class Test {
    public static void main(String[] args) {
        Sort sort = new Sort();
        int[] array = new int[6];
        sort.DisOrder(array);
        System.out.println("排序前:"+Arrays.toString(array));
        sort.BubbleSort(array);
        System.out.println("排序后:"+Arrays.toString(array));
    }
}

冒泡排序是稳定的。时间复杂度:,空间复杂度:

1.4. 快速排序

快速排序是Hoare于1962年提出的⼀种⼆叉树结构的交换排序⽅法,其基本思想为:任取待排序元素 序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列。

我们需要定义两个指针left和right,假设以6作为基准值,left向左移动,遇到比6大的数停下;right向右移动,遇到比6小的数停下,然后交换两个元素。当两个指针相遇时,再与基准值进行交换。这样就能保证6左边都是比6小的数,6右边都是比6大的数。按照这个过程再次进行,,构成递归的条件,直到分离出只含一个值的子序列。这样就构成了如下图所示的二叉树结构。

java 复制代码
public class Sort {

    public void QuickSort(int[] array){

    }

    public void Quick(int[] array,int start,int end){
        if(start >= end){//如果结点的右子树为空,就不用遍历右边
            return;
        }
        int par = partition(array,start,end);
        Quick(array,start,par-1);
        Quick(array,par+1,end);
        //当start==end时,递归条件结束
    }

    private int partition(int[] array, int left, int right) {
        return -1;
    }
}

我们接下来要思考的问题是如何写partition这个方法。无论是递归左边还是右边,与上面的过程都是一样的。只要left下标的值比基准值小,left右移;只要right下标的值比基准值大,right右移。这里我们还需要注意里层的while循环,指针不能越界。

java 复制代码
    private int partition(int[] array, int left, int right) {
        int i = left;
        int tmp = array[left];
        while(left < right){
            while(left < right && array[right] >= tmp){
                right--;
            }
            while(left < right && array[left] <= tmp){
                left++;
            }
            swap(array,left,right);
        }
        swap(array,left,i);
        return left;
    }

完整代码实现:

java 复制代码
import java.util.Random;

public class Sort {
    public void DisOrder(int[] array){
        Random ran = new Random();
        for (int i = 0; i < array.length; i++) {
            array[i] = ran.nextInt(1,40);
        }
    }

    public void QuickSort(int[] array){
        Quick(array,0,array.length-1);
    }

    public void Quick(int[] array,int start,int end){
        if(start >= end){//如果结点的右子树为空,就不用遍历右边
            return;
        }
        int par = partition(array,start,end);
        Quick(array,start,par-1);
        Quick(array,par+1,end);
        //当start==end时,递归条件结束
    }

    private int partition(int[] array, int left, int right) {
        int i = left;
        int tmp = array[left];
        while(left < right){
            while(left < right && array[right] >= tmp){
                right--;
            }
            while(left < right && array[left] <= tmp){
                left++;
            }
            swap(array,left,right);
        }
        swap(array,left,i);
        return left;
    }

    private void swap(int[] array,int i,int j){
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }
}
java 复制代码
import java.util.Arrays;

public class Test {
    public static void main(String[] args) {
        Sort sort = new Sort();
        int[] array = new int[6];
        sort.DisOrder(array);
        System.out.println("排序前:"+Arrays.toString(array));
        sort.QuickSort(array);
        System.out.println("排序后:"+Arrays.toString(array));
    }
}

这里解释一下为什么先让right移动,防止越过某些值。这里我们还需要注意,>=或<=的等号不能省略,如果left和right的值与基准值相等,那么就不会进入内层的while循环,导致外层的while循环陷入死循环。

快速排序是不稳定的。快速排序的时间复杂度通常是指最好情况下,因为我们经常对快速排序进行优化。

快速排序还有一种做法------挖坑法。与上面的方法类似,我们依然是以6为基准值,把6存进tmp中,right向左移动,遇到比6小的数,把6之前的位置填上;left向右移动,遇到比6大的数,把5之前的位置填上......我们只需要对上面的代码进行修改就可以。

java 复制代码
import java.util.Random;

public class Sort {
    public void DisOrder(int[] array){
        Random ran = new Random();
        for (int i = 0; i < array.length; i++) {
            array[i] = ran.nextInt(1,40);
        }
    }

    public void QuickSort(int[] array){
        Quick(array,0,array.length-1);
    }

    public void Quick(int[] array,int start,int end){
        if(start >= end){//如果结点的右子树为空,就不用遍历右边
            return;
        }
        int par = partition(array,start,end);
        Quick(array,start,par-1);
        Quick(array,par+1,end);
        //当start==end时,递归条件结束
    }

    private int partition(int[] array, int left, int right) {
        int i = left;
        int tmp = array[left];
        while(left < right){
            while(left < right && array[right] >= tmp){
                right--;
            }
            array[left] = array[right];
            while(left < right && array[left] <= tmp){
                left++;
            }
            array[right] = array[left];
        }
        array[left] = tmp;
        return left;
    }

}
java 复制代码
import java.util.Arrays;

public class Test {
    public static void main(String[] args) {
        Sort sort = new Sort();
        int[] array = new int[6];
        sort.DisOrder(array);
        System.out.println("排序前:"+ Arrays.toString(array));
        sort.QuickSort(array);
        System.out.println("排序后:"+ Arrays.toString(array));
    }
}

我们接下来思考一下快排的优化。我们先来看第一种三数取中法。我们找出start和end的中位数,让二叉树的形状尽量不要出现单分支的情况。那我们怎么再这段区间里面去找中位数呢?我们可以通过下标来找

java 复制代码
    private static int midNum(int[] array, int left, int right) {
        int mid = (left+right)/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[left]) {
                return left;
            }else if(array[mid] < array[right]) {
                return right;
            }else {
                return mid;
            }
        }
    }

第二种,递归到⼩的⼦区间时,可以考虑使⽤插⼊排序。

相关推荐
wxr的理想之路7 分钟前
list链表的使用
c语言·数据结构·链表·list
fqsword12 分钟前
C++ 算法竞赛STL以及常见模板
c++·算法
曦月逸霜17 分钟前
第五次CCF-CSP认证(含C++源码)
数据结构·c++·算法·ccf-csp
机器学习社区40 分钟前
QwQ-32B 开源!本地部署+微调教程来了
深度学习·算法·面试·大模型·面试题
Illusionna.1 小时前
KMP 算法的 C 语言实现
c语言·数据结构·算法
dokii11 小时前
leetcode454 四数相加
算法·哈希算法·散列表
阿巴~阿巴~1 小时前
C/C++蓝桥杯算法真题打卡(Day3)
c语言·c++·算法·蓝桥杯
QQ12971579401 小时前
51单片机 矩阵
单片机·嵌入式硬件·深度学习·算法·硬件工程·集成学习
<但凡.1 小时前
题海拾贝:P9241 [蓝桥杯 2023 省 B] 飞机降落
数据结构·算法·蓝桥杯
被AI抢饭碗的人1 小时前
算法题(90):队列安排
算法