数据结构之排序

Posted @从一到无穷大

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构之排序相关的知识,希望对你有一定的参考价值。

文章目录


1 排序概述

将杂乱无章的数据元素,通过一定的方法按关键字顺序排列的过程叫做排序
排序的分类:
(1)内部排序:指将需要处理的所有数据都加载到内部存储器中进行排序。
(2)外部排序:数据量过大,无法全部加载到内存中,需要借助外部存储进行排序。
常见的排序算法分类如下图所示,本文主要讨论内部排序。

2 插入排序

2.1 直接插入排序

插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。
插入排序的基本思想是:把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。

	public static void directInsertSort(int[] arr)
	    for (int i = 1; i < arr.length; ++i)
	        for (int j = i; j > 0; --j)
	            if (arr[j] < arr[j - 1])
	                swap(arr, j, j - 1);
	             else 
	                break;
	            
	        
	    
	

	public static void swap(int[] arr, int i, int j)
	    int temp = arr[i];
	    arr[i] = arr[j];
	    arr[j] = temp;
	

2.2 希尔排序

希尔排序法基本思想:希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

	public static void shellSort(int[] arr)
        int gap = arr.length;
        while (true)
            gap /= 2;
            for (int i = 0; i < gap; ++i)
                for (int j = i + gap; j < arr.length; j += gap)    // 这个循环里其实就是一个插入排序
                    int k = j;
                    while (k >= gap && arr[k] < arr[k - gap])
                        swap(arr, k, k - gap);
                        k -= gap;
                    
                
            
            if (gap == 1)
                break;
            
        
    

3 选择排序

3.1 简单选择排序

简单选择排序思想:第1次从arr[0]~arr[n-1]中选取最小值,与arr[0]交换,第二次从arr[1] ~arr[n-1]中选取最小值,与arr[1]交换,第三次从arr[2] ~arr[n-1]中选取最小值,与arr[2]交换,…,第n-1次从arr[n-2] ~arr[n-1]中选取最小值,与arr[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列。

	public static void simpleSelectSort(int[] arr)
        int n = arr.length;
        for (int i = 0; i < n - 1; ++i)
            int minIndex = i;
            for (int j = i + 1; j < n; ++j)
                if (arr[j] < arr[minIndex])
                    minIndex = j;
                
            
            if (i != minIndex)
                swap(arr, i, minIndex);
            
        
    

3.2 堆排序

堆排序基本介绍
(1)堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序。
(2)堆是具有以下性质的二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆。
(3)每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
(4)大顶堆如下图所示:

堆排序基本思想
(1)将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点;
(2)将其与末尾元素进行交换,此时末尾就为最大值;
(3)然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值,如此反复执行,便能得到一个有序序列了。

	public static void heapSort(int[] arr)
        // 将数组调整为大根堆
        for (int i = arr.length / 2 - 1; i >= 0; --i)   // 因为数组从0开始计数,所以要减1
            adjustHeap(arr, i, arr.length);
        

        // 排序
        for (int i = arr.length - 1;  i > 0; --i)
            swap(arr, 0, i);
            adjustHeap(arr, 0, i);
        
    

    public static void adjustHeap(int[] arr, int i, int length)
        for (int k = 2 * i + 1; k < length; k = 2 * k + 1)     // 因为数组从0开始计数,所以要加1
            if (k + 1 < length && arr[k] < arr[k + 1])    // 选取左,右孩子当中较大的值与父节点交换
                ++k;
            
            if (arr[i] < arr[k])
                swap(arr, i, k);
                i = k;    // 继续调整子树当中的节点
             else 
                break;
            
        
    

4 交换排序

4.1 冒泡排序

冒泡排序基本思想:通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就像水底下的气泡一样逐渐向上冒。
因为排序的过程中各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换,从而减少不必要的比较。

   public static void bubbleSort(int[] arr)
        for (int i = 0; i < arr.length - 1; ++i)
            boolean flag = true;     // 标识变量,表示是否进行过交换
            for (int j = 0; j < arr.length - i - 1; ++j)
                if (arr[j] > arr[j + 1])
                    flag = false;
                    swap(arr, j, j + 1);
                
            
            if (flag)
                break;
            
        
    

4.2 快速排序

快速排序法基本思想:快速排序是对冒泡排序的一种改进,基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

    public static void quickSort(int[] arr, int left, int right)
        if (left > right)
            return;
        
        int i = left;
        int j = right;
        int temp = arr[left];

        while (i < j)
            while (arr[j] >= temp && i < j)    // 从右向左找到第一个比基准值小的元素
                --j;
            
            while (arr[i] <= temp && i < j)    // 从左向右找到第一个比基准值大的元素
                ++i;
            
            swap(arr, i, j);
        
        // 交换基准值
        arr[left] = arr[j];
        arr[j] = temp;

        quickSort(arr, left, j - 1);
        quickSort(arr, j + 1, right);

    

5 归并排序

归并排序基本思想:归并排序是利用归并的思想实现的排序方法,该算法采用经典的分治策略(分治法将问题成一些小的问题然后递归求解,而的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。

空间复杂度为O(n),来自于辅助数组。

    public static void mergeSort(int[] arr, int left, int right, int[] temp)
        if (left < right)
            int mid = left + (right - left) / 2;    // 中间索引
            mergeSort(arr, left, mid, temp);    // 向左递归进行分解
            mergeSort(arr, mid + 1, right, temp);    // 向右递归进行分解
            merge(arr, left, mid, right, temp);    // 合并
        
    

    public static void merge(int[] arr, int left, int mid, int right, int[] temp)
        int i = left;    // 左侧区间的第一个数
        int j = mid + 1;    // 右侧区间的第一个数
        int k = 0;

        // 把左右两边(有序)的数据按照规则填充到temp数组,直到左右两边的有序序列一边处理完为止
        while (i <= mid && j <= right)
            if (arr[i] < arr[j])
                temp[k++] = arr[i++];
             else 
                temp[k++] = arr[j++];
            
        

        // 把有剩余数据的一边的数据依次填充到temp
        while (i <= mid)
            temp[k++] = arr[i++];
        
        while (j <= right)
            temp[k++] = arr[j++];
        

        // 将temp数组的元素拷贝到arr
        k = 0;
        int t = left;
        while(t <= right)
            arr[t++] = temp[k++];
        
    

6 基数排序

基数排序基本思想:将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。



一趟分配O(n),一趟收集O( r),总共d趟分配、收集,总的时间复杂度为O(d(n+r));需要r个辅助队列,空间复杂度=O( r)。

    public static int number(int[] arr)
        int res = 0;
        for (int i = 0; i < arr.length; ++i)
            if (arr[i] > res)
                res = arr[i];
            
        
        return (res + "").length();
    

    public static void radixSort(int[] arr)
        int num = number(arr);
        int[][] bucket = new int[10][arr.length];    // 一共建立10个桶
        int[] bucketElement = new int[10];    // 记录每个桶的数据量
        int index = 0;

        for (int i = 0; i < num; ++i)
            // 将数据放入桶中
            for (int j = 0; j < arr.length; ++j)
                int k = (int) (arr[j] / Math.pow(10, i) % 10);
                bucket[k][bucketElement[k]] = arr[j];
                ++bucketElement[k];
            

            // 将数据写回arr数组
            for(int j = 0; j < 10; ++j)
                int k = 0;
                while (k < bucketElement[j])
                    arr[index++] = bucket[j][k];
                    ++k;
                
                bucketElement[j] = 0;
            
            index = 0;
        
    

7 各种排序算法的性质

算法的稳定性
若待排序表中有两个元素Ri和Rj,其对应的关键字相同即keyi=keyj,且在排序前Ri在Rj的前面,若使用某一排序算法排序后,Ri仍然在Rj的前面,则称这个排序算法是稳定的,否则称这个排序算法是不稳定的。


参考:
https://www.bilibili.com/video/BV1E4411H73v?p=1&vd_source=ff364c22743db2666f3a26c417a3f759
https://www.bilibili.com/video/BV1b7411N798?p=1&vd_source=ff364c22743db2666f3a26c417a3f759

以上是关于数据结构之排序的主要内容,如果未能解决你的问题,请参考以下文章

算法-排序系列04之快速排序

Python算法之快速排序

算法之快速排序

算法高频面试之:快速排序

排序之快速排序

排序算法之快速排序