首页 > 其他分享 >数据结构_排序

数据结构_排序

时间:2024-07-19 23:27:16浏览次数:11  
标签:排序 end temp int array 数据结构 left

目录

一、排序

二、插入排序

2.1 直接插入排序

2.2 希尔排序

三、选择排序

3.1 直接选择排序

3.2 堆排序

四、交换排序

4.1 冒泡排序

4.2 快速排序

五、归并排序

六、排序算法分析

总结


一、排序

排序:就是使一串记录序列,按照其中某个或某些关键字的大小,进行递增或递减的排列操作。

稳定性:假定在待排序的记录序列中,存在多个具有相同关键字的记录,若经过排序后,这些记录的相对次序保持不变,则称这种排序算法是稳定的;否则称为不稳定。

例如,在原序列中,r[i] = r[j],且 r[i] 在 r[j] 之前,若排序后的序列中,r[i] 仍在 r[j] 之前,则排序算法稳定,反之即不稳定。


二、插入排序

2.1 直接插入排序

核心思想:从第二个元素开始遍历,每次遍历将该元素与其前方元素对比,做出相应交换。

    public static void insertSort(int[] array) {
        //初始 i 下标指向第二个元素
        for (int i = 1; i < array.length; i++) {
            //将 i下标的值放入 temp 中
            int temp = array[i];
            //初始 j 下标指向 i 下标的前一位
            int j = i-1;
            for (; j >= 0; j--) {
                //若 j 下标的值比 temp 中的值大,则将其往后移一位
                if (array[j] > temp) {
                    array[j+1] = array[j];
                } else {
                    break;
                }
            }
            //循环结束将原本 i 的值放至 j 下标的后一位中
            array[j+1] = temp;
        }
    }

【总结】

时间复杂度:

        最坏情况下:O(n^2)

        最好情况下:O(n)

空间复杂度:O(1)

稳定性:稳定

适用性:待排序序列接近有序


2.2 希尔排序

希尔排序也叫缩小增量排序基本思想:选定一个整数 gap,将待排序序列分为 gap 组,所有距离为 gap 的元素为同一组,每组分别进行直接插入排序;然后缩小 gap,继续分组排序,直至 gap = 1 时,所有元素为一组,最后进行一次直接插入排序。

    public static void shellSort(int[] array) {
        int gap = array.length;
        //gap>1 都属于预排序
        while (gap > 1) {
            //取 gap 为全部元素的一半
            gap /= 2;
            //每组排序
            shell(array, gap);
        }
    }

    private static void shell(int[] array, int gap) {
        //初始 i 下标指向 gap,即每组的第二个元素
        for (int i = gap; i < array.length; i++) {
            //将 i下标的值放入 temp 中
            int temp = array[i];
            //初始 j 下标指向 i 下标的前 1gap 位
            //j 每次往前 1gap 位
            int j = i-gap;
            for (; j >= 0; j-=gap) {
                //若 j 下标的值比 temp 中的值大,则将其往后移 1gap 位
                if (array[j] > temp) {
                    array[j+gap] = array[j];
                } else {
                    break;
                }
            }
            //循环结束将原本 i 的值放至 j 下标的后 1gap 位中
            array[j+gap] = temp;
        }
    }

【总结】 

1、希尔排序是对直接插入排序的优化。

2、当 gap>1 时,都是预排序,是为了让序列趋于有序。

3、稳定性:不稳定


三、选择排序

3.1 直接选择排序

核心思想:遍历每个元素,每次遍历确定一个最小值令其有序。

    public static void selectSort(int[] array) {
        //遍历每个元素
        for (int i = 0; i < array.length; i++) {
            //初始化 minIndex 用于指向最小值
            int minIndex = i;
            //i 下标与后方每个元素对比
            for (int j = i+1; j < array.length; j++) {
                //确保 minIndex 指向 i 下标及其后方元素的最小值
                if (array[j] < array[minIndex]) {
                    minIndex = j;
                }
            }
            //交换 i 下标与 minIndex 下标的值
            int temp = array[i];
            array[i] = array[minIndex];
            array[minIndex] = temp;
        }
    }

【总结】

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

空间复杂度:O(1)

稳定性:不稳定


3.2 堆排序

① 创建大根堆,初始化 end = array.length-1,即 end 指向最后一个结点;

② 将栈顶元素交换至 end 下标。由于大根堆中栈顶元素最大,故交换一次,end--,保证每次交换后的栈顶元素位置不变;

③ 重新向下调整为大根堆;

④ 重复 ②、③ 操作,直至排序完成。

    //创建大根堆
    private static void createHeap(int[] array) {
        //从最后一棵子树开始调整,依次往前,直至根结点
        //父亲结点 = (孩子结点-1) / 2
        //usedSize-1 是树中最后一个结点
        for (int parent = (array.length-1-1) / 2; parent >= 0; parent--) {
            //向下调整
            siftDown(array, parent,array.length);
        }
    }
    //堆排序
    public static void heapSort(int[] array) {
        //创建大根堆
        createHeap(array);
        
        int end = array.length-1;
        while (end > 0) {
            //将大根堆中栈顶元素交换至 end
            swap(array, 0, end);
            //向下调整为大根堆
            siftDown(array, 0, end);
            //保证每次调整的栈顶元素位置不变
            end--;
        }
    }
    //向下调整
    private static void siftDown(int[] array, int parent, int length) {
        //左孩子结点 = 父亲结点*2 + 1
        int child = parent*2 + 1;

        //首先保证该结点有左孩子结点
        while (child < length) {

            //再次确定该结点有右孩子结点,再进行比较
            //保证 child 引用指向孩子结点中最大值
            if ((child+1) < length && array[child] < array[child+1]) {
                //若右孩子的值大于左孩子,则 child 引用指向右孩子
                child = child + 1;
            }

            if (array[child] > array[parent]) {
                //若 child 比 parent 大,则交换元素
                swap(array, child, parent);

                //parent 指向 child 位置,向下调整至下方无子树
                parent = child;
                child = parent*2 + 1;
            } else {
                //子结点都比父结点小
                break;
            }
        }
    }
    //交换元素
    private static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

【总结】 

时间复杂度:O(n*log n)

空间复杂度:O(1)

稳定性:不稳定


四、交换排序

4.1 冒泡排序

核心思想:进行 array.length-1 趟比较,每次确定一个最大值元素。

    public static void bubbleSort(int[] array) {
        //i 表示趟数
        for (int i = 0; i < array.length-1; i++) {
            //设置一个标签,检测该趟是否发生交换
            boolean flag = false;

            //j 表示该趟比较次数
            for (int j = 0; j < array.length-1-i; j++) {
                //每趟确定一个最大值
                if (array[j] > array[j+1]) {
                    int temp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = temp;
                    //若交换,则改变标签状态
                    flag = true;
                }
            }
            //若标签状态未改变,则序列已经有序
            if (!flag) {
                break;
            }
        }
    }

【总结】

时间复杂度:O(n^2),若加上 flag 优化,则最好可以达到 O(n)

空间复杂度:O(1)

稳定性:稳定


4.2 快速排序

1、Hoare版

核心思想:以 0 下标元素为基准,保证每次排序后,基准左侧元素小于基准,右侧大于基准,分别在左序列与右序列进行递归排序。

    public static void quickSort(int[] array) {
          quick(array, 0, array.length-1);
    }

    private static void quick(int[] array, int start, int end) {
        // start < end 时才需要排序
        if (start >= end) {
            return;
        }

        //找到基准的下标
        int pivot = partitionHoare(array, start, end);

        //左序列
        quick(array, start, pivot-1);
        //右序列
        quick(array, pivot+1, end);
    }

    private static int partitionHoare(int[] array, int left, int right) {
        //保存基准的值
        int temp = array[left];
        //保存基准的下标
        int index = left;

        while (left < right) {
            //在右侧找到比基准小的数
            while (left < right && array[right] >= temp) {
                right--;
            }
            //在左侧找到比基准小的数
            while (left < right && array[left] <= temp) {
                left++;
            }
            swap(array, left, right);
        }
        //保证基准左侧都比基准小,右侧都比基准大
        swap(array, index, left);
        //确定基准的位置时,left = right
        return left;
    }

2、挖坑版

核心思想:以 0 下标元素为基准,0 下标处为第一个坑位,从右侧开始遍历,找到比基准小的元素放至 left 下标 (初始即 0 下标) 处;然后从左侧遍历到比基准大的放至 right 下标处,循环至 left 与 right 相遇,将基准放入最后一个坑位。

    public static void quickSort(int[] array) {
          quick(array, 0, array.length-1);
    }

    private static void quick(int[] array, int start, int end) {
        // start < end 时才需要排序
        if (start >= end) {
            return;
        }

        //找到基准的下标
        int pivot = partition(array, start, end);

        //左序列
        quick(array, start, pivot-1);
        //右序列
        quick(array, pivot+1, end);
    }

    private static int partition(int[] array, int left, int right) {
        //保存基准的值
        int temp = array[left];
        //保存基准的下标
        int index = left;

        while (left < right) {
            //在右侧找到比基准小的数
            while (left < right && array[right] >= temp) {
                right--;
            }
            //找到放置左边的坑位
            array[left] = array[right];

            //在左侧找到比基准小的数
            while (left < right && array[left] <= temp) {
                left++;
            }
            //找到放置右边的坑位
            array[right] = array[left];
        }
        //将基准值放入最后一个坑中
        array[left] = temp;
        //确定基准的位置时,left = right
        return left;
    }

【总结】 

时间复杂度:O(n*log n)

空间复杂度:O(log n)

稳定性:不稳定


五、归并排序

将两个有序序列合并成一个有序序列的操作,称为二路归并

核心步骤:先分解再合并。

    public static void mergeSort(int[] array) {
        merge(array, 0, array.length-1);
    }

    private static void merge(int[] array, int start, int end) {
        // start < end 时才需要排序
        if (start >= end) {
            return;
        }

        //标记序列中间下标
        int mid = (start+end)/2;
        //左序列
        merge(array, start, mid);
        //右序列
        merge(array, mid+1, end);

        //排序, 合并
        mergeMethod(array, start, mid, end);
    }

    private static void mergeMethod(int[] array, int left,int mid, int right) {
        //指向左右序列最小值
        int min1 = left;
        int min2 = mid+1;

        //定义一个新的空间用于排序
        int[] sort = new int[right-left+1];
        //新空间的下标
        int k = 0;

        //保证左右序列都有元素
        while (min1 <= mid && min2 <= right) {
            //左右序列从最左侧(最小值)开始比较
            if (array[min1] <= array[min2]) {
                sort[k++] = array[min1++];
            } else {
                sort[k++] = array[min2++];
            }
        }

        //表示右序列中无元素,即左序列剩下元素比右序列都要大
        while (min1 <= mid) {
            sort[k++] = array[min1++];
        }
        //表示左序列中无元素,即右序列剩下元素比左序列都要大
        while (min2 <= right) {
            sort[k++] = array[min2++];
        }

        //将排序好的数组,拷贝回原数组
        for (int i = 0; i < sort.length; i++) {
            //利用 left 下标(每个序列起始下标) 保证右序列可以顺利拷贝
            array[i+left] = sort[i];
        }
    }

【总结】 

时间复杂度:O(n*log n)

空间复杂度:O(n)

稳定性:稳定

适用性:更多是在解决磁盘中的外排序问题


六、排序算法分析

排序方法最好平均最坏空间复杂度稳定性
冒泡排序O(n)O(n^2)O(n^2)O(1)稳定
插入排序O(n)O(n^2)O(n^2)O(1)稳定
选择排序O(n^2)O(n^2)O(n^2)O(1)不稳定
希尔排序O(n)O(n^1.3)O(n^2)O(1)不稳定
堆排序O(n*log n)O(n*log n)O(n*log n)O(1)不稳定
快速排序O(n*log n)O(n*log n)O(n^2)O(log n) ~ O(n)不稳定
归并排序O(n*log n)O(n*log n)O(n*log n)O(n)稳定

总结

1、希尔排序是对直接插入排序的优化。

2、直接选择排序效率不高,很少使用;堆排序效率高。

3、快速排序综合性能和使用场景都是比较好的。

4、归并排序更多是在解决磁盘中的外排序问题。

标签:排序,end,temp,int,array,数据结构,left
From: https://blog.csdn.net/m0_73620971/article/details/139751785

相关文章

  • 【数据结构】学习day 1.1线性表中的顺序表
    1 "&"区别(c++)#include<stdio.h>voidtest(intx){   x=2024;   printf("intestx=%d\n",x);}intmain(){   intx=1;   printf("beforetestx=%d\n",x);   test(x);   printf("aftertestx=%d\n"......
  • 数据结构——哈希
    前言顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O(logN),搜索的效率取决于搜索过程中元素的比价次数。理想的搜索方法:可以不经过任何比较,一次直接从表中得......
  • 堆排序的实现
    首先需要知道的是,如果想要对一个数组排升序,要建大堆,排降序,要建小堆。具体过程:(以排降序为例)1)建小堆;2)交换堆顶(第一个)和堆尾(最后一个)的数据;3)交换完后,最后一个数据不动,剩下的数据对堆顶元素进行向下调整;4)循环2)3)步骤。下面用图来展示一下过程:1)假设对数组arr[10]={3,1......
  • 数据结构—双向链表
    文章目录1.双向链表的概念和结构1.1双向链表的概念1.2双向链表与单链表的对比1.3双向链表的结构2.双向链表的接口实现2.1申请一个新结点2.2初始化2.3打印2.4尾插2.5头插2.6判断链表是否为空2.7尾删2.8头删2.9查找2.10在指定位置之后插入数据2.11在指定位......
  • 合并排序数组
    合并排序数组(蓝桥杯题库)题目描述给定排序数组A和B,实现一个算法将B按排序顺序合并到A中。介绍如下:数组A和B的均为排序数组,数字按从小到大排列。数组A的的长度为 ......
  • 数据结构与算法 数组篇之长度最小的子数组
    问题描述:给定一个含有 n 个正整数的数组和一个正整数 target 。找出该数组中满足其总和大于等于 target 的长度最小的 子数组 [numsl,numsl+1,...,numsr-1,numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。题目链接:.-力扣(LeetCode)解题思路:双指针,......
  • EXCEL:按有序列表对数组进行排序,无需自定义列表
    我有一张邮政编码表,其中包含发送到每个邮政编码的货件数量。我想按特定顺序按邮政编码对这个数组进行排序,我将其放在第二个列表中。我不想按客户数量或邮政编码的数字顺序排序,而是按这个专门排名的列表排序。我无法使用自定义排序功能,因为我的列表对于此功能来说太长了。......
  • 排序代码示例
    快速排序publicstaticvoidmain(String[]args){int[]arr={0,5,9,1,3,6};//intpartition=partition(arr,0,arr.length-1);quick(arr,0,arr.length-1);System.out.println(Arrays.toString(arr));}publicsta......
  • 【C语言】深入解析归并排序
    文章目录什么是归并排序?归并排序的基本实现代码解释归并排序的优化归并排序的性能分析归并排序的实际应用结论在C语言编程中,归并排序是一种高效且稳定的排序算法。它采用分治法将问题分解成更小的子问题进行解决,然后合并结果。本文将详细介绍归并排序算法,包括其......
  • 基础数据结构——初级——链表
    链表的特点是一组位于任意位置的存储单元存储线性表的数据元素。一般分为单向链表和双向链表(如下图所示)。 使用链表时,可以用STL的list,也可以自己写链表。如果自己写代码实现链表,有两种编码实现方法:动态链表和静态链表。在算法竞赛中为加快编码速度,一般用静态链表或者STL的l......