数据结构与算法--排序

249 阅读6分钟

简单排序

这里都将数据从小往大排序

冒泡排序

思想

  • 每次都比较相邻的两个数的大小,前面的比后面的大就交换位置

时间复杂度:O(n^2)

图解

image.png

具体实现

package com.study.sort.SimpleSort;

public class Bubble {
    public static void main(String[] args) {
        int[] a = {1, 9, 3, 4, 5, 6, 7};
        int[] a1 = {1, 9, 3, 4, 5, 6, 7};
        bubbleSort(a);
        BubbleSort(a1);
    }

    // 简单的冒泡排序
    public static void bubbleSort(int[] a) {
        int temp; // 临时变量,用来交换数据
        int sum = 0;//用来计算交换的总次数
        for (int i = a.length; i > 0; i--) { // 交换次数,有n个数就需要比较n次
            for (int j = 0; j < i - 1; j++) { //  需要进行比较的数字的个数
                if (a[j] > a[j + 1]) {
                    temp = a[j];
                    a[j] = a[j + 1];
                    a[j + 1] = temp;
                }
            }
            sum += 1;
        }
        // 打印排序好的数组
        System.out.println("冒泡排序后的数组:");
        for (int i = 0; i < a.length - 1; i++) {
            System.out.print(a[i] + ",");
        }
        System.out.println(a[a.length - 1]);
        System.out.println("冒泡排序的总次数为:" + sum);
    }

    // 改进版的冒泡排序
    // 即,原数组为 1,5,2,3,4
    // 原先需要排序 5次,但是在第一次冒泡排序之后,其实就已经排好了,不需要再进行另外的冒泡排序次数
    public static void BubbleSort(int[] a) {
        int temp;
        int sum = 0;
        boolean flag = false;
        for (int i = a.length; i > 0; i--) { // 交换次数,有n个数就需要比较n次
            for (int j = 0; j < i - 1; j++) { //  需要进行比较的数字的个数
                if (a[j] > a[j + 1]) {
                    temp = a[j];
                    a[j] = a[j + 1];
                    a[j + 1] = temp;
                    flag = false;
                } else {
                    flag = true;
                }
            }
            sum += 1;
            if (flag) {
                break;
            }
        }
        // 打印排序好的数组
        System.out.println("改进版冒泡排序后的数组:");
        for (int i = 0; i < a.length - 1; i++) {
            System.out.print(a[i] + ",");
        }
        System.out.println(a[a.length - 1]);
        System.out.println("改进版冒泡排序的总次数为:" + sum);
    }

}

插入排序

思想

  1. 把所有的元素分为两组,已经排序的未排序的;
  2. 找到未排序的组中的第一个元素向已经排序的组中进行插入;
  3. 倒叙遍历已经排序的元素,依次和待插入的元素进行比较,直到找到一个元素小于等于待插入元素那么就把待插入元素放到这个位置,其他的元素向后移动一位;

时间复杂度:O(n^2)

图解

image.png

具体实现

package com.study.sort.SimpleSort;

public class Insertion {
    public static void main(String[] args) {

        int[] a = {2, 5, 6, 3, 4, 8, 9};
        int temp; //临时变量

        for (int i = 1; i < a.length; i++) { //第一个数默认已经排序好了,直接从第二个数开始接着排序
            for (int j = i; j > 0 ; j--) {  // 已排序的数在前面,未排序的数在后面
                if (a[j]<a[j-1]){ // 比较 已排序的数和未排序的数
                    temp = a[j] ;
                    a[j] = a[j-1];
                    a[j-1]= temp;
                } else { // 没有交换,说明这个未排序的数的位置就是当前位置,退出,进行下一个排序数的比较
                    break;
                }
            }
        }

        // 打印结果:
        System.out.println("插入排序的结果:");
        for (int i = 0; i < a.length-1; i++) {
            System.out.print(a[i]+",");
        }
        System.out.print(a[a.length-1]);

    }
}

选择排序

思想

  1. 每一次遍历的过程中,都假定第一个索引处的元素是最小值,和其他索引处的值依次进行比较如果当前索引处的值大于其他某个索引处的值,则假定其他某个索引出的值为最小值,最后可以找到最小值所在的索引
  2. 交换第一个索引处和最小值所在的索引处的值

时间复杂度:O(n^2)

图解

image.png

具体实现

package com.study.sort.SimpleSort;

public class Selection {

    public static void main(String[] args) {
        int[] a = {1, 5, 6, 3, 4, 8, 9};
        int temp; //临时变量
        int min; //最小值的下标索引

        for (int i = 0; i < a.length-1; i++) {
            min = i; // 每次都假设第一个值为最小值
            for (int j = i+1; j <a.length; j++) {
                if (a[j]<a[min]){
                    min = j; // 找出值更小的小标索引
                }
            }
            // 交换值
            temp = a[i];
            a[i] = a[min];
            a[min] = temp;
        }
        // 打印结果
        System.out.println("选择排序的结果:");
        for (int i = 0; i < a.length-1; i++) {
            System.out.print(a[i]+",");
        }
        System.out.print(a[a.length-1]);
    }


}

高级排序

这里都将数据从小往大排序

希尔排序

思想

  1. 选定一个增长量h,按照增长量h作为数据分组的依据,对数据进行分组;
  2. 对分好组的每一组数据完成插入排序;
  3. 减小增长量,最小减为1,重复第二步操作。

时间复杂度:O(n^1.25)

图解

image.png

具体实现

// 增长量h的定义
        int h = 1;
        while (h < 数组长度 / 2) { 
            h = 2 * h + 1;
        }
// 增长量h的减小规则:
        h=h/2;
package com.study.sort.AdvanceSort;
// 希尔排序 : 插入排序的改进版

// 时间复杂度:O(n^1.25)
public class Shell {
    public static void main(String[] args) {
        int[] a = {2, 5, 6, 3, 4, 8, 9};
        int temp;
        int h = 1;
        while (h < a.length / 2) { //增长量h的初始定义
            h = 2 * h + 1;
        }
        while (h >= 1) {
            for (int i = h; i < a.length; i++) {//每组的第一个数默认是排序好的,又因为他们的根据h分组的,
                                                // 所以,第二个数是h,i每次加1
                                                // 第一组的第一个数是a[0]
                for (int j = i; j >= h; j -= h) {
                    if (a[j] < a[j - h]) {
                        temp = a[j];
                        a[j] = a[j - h];
                        a[j - h] = temp;
                    } else {
                        break;
                    }
                }
            }
            h = h / 2;//一趟排序后,h减少规则
        }


        // 打印结果
        System.out.println("希尔排序的结果:");
        for (int i = 0; i < a.length - 1; i++) {
            System.out.print(a[i] + ",");
        }
        System.out.print(a[a.length - 1]);

    }

}

归并排序

思想

  1. 尽可能的一组数据拆分成两个元素相等的子组,并对每一个子组继续拆分,直到拆分后的每个子组的元素个数是1为止。
  2. 将相邻的两个子组进行合并成一个有序的大组;
  3. 不断的重复步骤2,直到最终只有一个组为止。

时间复杂度: O(nlogn)

图解

image.png

具体实现

package com.study.sort.AdvanceSort;

public class Merge {
    public static int[] assist;//辅助数组

    public static void main(String[] args) {
        int[] a = {2, 5, 6, 3, 4, 8, 9};
        assist = new int[a.length];//初始化辅助数组
        int low = 0; // 最小的索引值
        int high = a.length - 1; // 最大的索引值
        sort(a, low, high);  // 排序

        // 打印排序结果
        System.out.println("使用归并排序的结果:");
        for (int i = 0; i < a.length - 1; i++) {
            System.out.print(a[i] + ",");
        }
        System.out.print(a[a.length - 1]);
    }

    private static void sort(int[] a, int low, int high) {
        //安全性检验:low一定是小于high
        if (low >= high) {
            return;
        }
        //分组
        int mid = low + (high - low) / 2;
        sort(a, low, mid);
        sort(a, mid + 1, high);
        //归并
        merge(a, low, mid, high);
    }

    private static void merge(int[] a, int low, int mid, int high) {
        //定义三个指针
        int i = low; // 辅助数组的指针
        int p1 = low;// [low,mid]分组的指针
        int p2 = mid + 1;// [mid+1,high]分组的指针
        // 遍历,移动p1和p2,比较索引处的值,将小的值放到辅助数组中
        while (p1 <= mid && p2 <= high) {
            if (a[p1] <= a[p2]) {
                assist[i++] = a[p1++];
            } else {
                assist[i++] = a[p2++];
            }
        }
        // 将p1或者p2那个还有剩下的值的数组放到辅助数组中
        while (p1 <= mid) {
            assist[i++] = a[p1++];
        }
        while (p2 <= high) {
            assist[i++] = a[p2++];
        }
        //将辅助数组中的元素拷贝到原数组
        for (int index = 0; index <= high; index++) {
            a[index] = assist[index];
        }
    }

}

快速排序

思想

  1. 首先设定一个分界值,通过该分界值将数组分成左右两部分;
  2. 大于或等于分界值的数据放到到数组右边小于分界值的数据放到数组的左边。此时左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值;
  3. 然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
  4. 重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左侧和右侧两个部分的数据排完序后,整个数组的排序也就完成了。

分界值的设定

1.找一个基准值,用两个指针分别指向数组的头部和尾部;
2.先从尾部向头部开始搜索一个比基准值小的元素,搜索到即停止,并记录指针的位置;
3.再从头部向尾部开始搜索一个比基准值大的元素,搜索到即停止,并记录指针的位置;
4.交换当前左边指针位置和右边指针位置的元素;
5.重复2.3.4步骤,直到左边指针的值大于右边指针的值停止。

时间复杂度:O(nlogn)

图解

image.png

具体实现

package com.study.sort.AdvanceSort;

public class Quick {
    public static void main(String[] args) {
        int[] a = {2, 5, 6, 3, 4, 8, 9};
        int low = 0;
        int high = a.length - 1;
        sort(a, low, high);
        // 打印排序结果
        System.out.println("使用快速排序的结果:");
        for (int i = 0; i < a.length - 1; i++) {
            System.out.print(a[i] + ",");
        }
        System.out.print(a[a.length - 1]);
    }

    // 排序分组完的数据
    private static void sort(int[] a, int low, int high) {
        // 安全性校验
        if (low>=high){
            return;
        }
        // 找到分界值所在的位置
        int partition = partition(a, low, high);
        // 分组
        sort(a,low,partition-1);//递归排序左子树
        sort(a,partition+1,high);//递归排序右子树
    }

    //确定分界值所在的索引位置
    // 注意: 第一个分界值的位置是 交换过一次后,它所在的索引位置
    private static int partition(int[] a, int low, int high) {
        // 确定分界值
        int key = a[low];
        // 定义两个指针来扫描分组
        int left = low;
        int right = high + 1;
        int temp;//临时变量

        //找出分界值所在的位置
        while (true) {
            while (a[--right] > key) { // 从右往左扫描,找到一个比分界值小的数
                if (right==low){
                    break;
                }
            }
            while (a[++left] < key) { // 从左往右扫描,找到一个比分界值大的数
                if (left==high){
                    break;
                }
            }
            if (left > right) { // 代表已经全部扫描完,退出扫描
                break;
            }else { // 没有全部扫描完,交换元素
                temp = a[left];
                a[left] = a[right];
                a[right] = temp;
            }
        }
        // 交换分界值的位置:初始位置low  现在找到的位置right
        temp = a[low];
        a[low] = a[right];
        a[right] = temp;
        return right;// 返回分界值所在的索引
    }


}

排序的稳定性

何为稳定性

数组arr中有若干元素,其中A元素和B元素相等,并且A元素在B元素前面,在使用前面的排序算法后,A元素还在B元素前面,则该排序算法是稳定的。

上述排序的稳定性

稳定的排序算法:
1. 冒泡排序
2. 插入排序
3. 归并排序

不稳定的排序算法:
1. 快速排序
2. 选择排序
3. 希尔排序

排序总结

image.png

其中有关堆排序的算法,可以看这篇博客图解堆排序