首页 > 编程语言 >【数据结构】排序算法

【数据结构】排序算法

时间:2024-07-29 10:54:47浏览次数:22  
标签:tmp 排序 int ++ 算法 array 数据结构 left

目录


排序

排序的概念:

假设含有n个记录的序列为{R1,R2,R3,···,Rn},其相应的关键字分别为{K1,K2,K3,···Kn},需确定1,2,3,···,n的一种排列P1,P2,···,Pn,使其相应的关键字满足Kp1 <= Kp2 <= Kp3 <= ···· <= Kpn非递减(或非递增)关系,即使得序列成为一个按关键字有序的序列{Rp1,Rp2,····,Rpn}。

排序稳定性:

假设Ki = Kj( 1 <= i <= n, 1 <= j <= n, i != j),且在排序前的序列中Ri领先于Rj,则称所用的排序方法时稳定的;反之,若可能使得排序后的序列中Rj领先于Ri,则称所用的排序算法是不稳定的。

也就是说如果两元素相同排序前啥位置排序后还是啥位置该排序就稳定,反之不稳定。

内排序:

内排序是在排序的整个过程中,将排序所有记录全部放置在内存中。

外排序:

外排序是由于排序的记录个数太多,不能同时放在内存中。外排序是由于排序的记录个数太多,不能同时放置在内存中,整个排序过程需要在内外存之间多次交换数据才能进行。

常见排序如下图:

以下排序都是实现从小到大。

冒泡排序

原理:两两比较相邻记录的关键字,如果反序就交换直到没有反序记录为止。

实现思路:

  1. 外循环实现比较趟数。
  2. 内循环实现从末尾找到最小值向前排。

如此每一趟都可以将一个最小值排在前面。

代码已优化点:

  1. 使用了一个falg标记,如果该趟没有交换证明数组已经有序,直接结束。
  2. 使用j > i作为第二层循环的结束条件而不是j > 0减少了循环次数。

时间复杂度:O(N^2) 。
空间复杂度:O(1)。
稳定性:稳定。

public static void bubbleSort(int[] array){
        for (int i = 0; i < array.length; i++) {
            boolean flag = false;
            for (int j = array.length - 1; j > i; j--) {
                if(array[j] < array[j - 1]){
                	//交换
                    int tmp = array[j - 1];
                    array[j - 1] = array[j];
                    array[j] = tmp;
                    
                    flag = true;
                }
            }
            if(!flag){
                break;
            }
        }
    }

选择排序

原理:就是通过n - i 次关键字之间的比较,从n - i + 1 个记录中选出关键字最小的记录,并和第 i (1 <= i <= n)个记录交换。

实现思路:

  1. 外循环控制每次最小值放入的位置。
  2. 内循环找到当前的最小值下标。
  3. 出内循环将最小值与当前应放入位置交换。
    每一次都可以挑选一个最小值出来。与冒泡排序有相似之处,但是冒泡相邻比较交换,选择排序是每次交换最小值。

时间复杂度:O(N^2)。
空间复杂度:O(1)。
稳定性:不稳定。

	// 选择排序
    public static 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[j] < array[minIndex]){
                    minIndex = j;
                }
            }
            int tmp = array[minIndex];
            array[minIndex] = array[i];
            array[i] = tmp;
        }
    }

直接插入排序

原理:直接插入排序的基本操作就是将一个记录插入到已经排好序的有序表中,从而得到一个新的,记录增1的有序表。

实现思路:

  1. 外部使用循环控制当前填入前方有序数组的值tmp。
  2. 内部循环从当前前一个下标开始往回遍历,遇到比tmp大的值就往后覆盖,否则就出循环(因为前面已经是有序的了)。
  3. 最后一定要将 j+1 下标置为tmp(因为出第二层循环时要么为-1要么当前j下标值小于等于tmp)。

时间复杂度:O(N^2)。
空间复杂度:O(1)。
稳定性:稳定。

//直接插入排序
public static void insertSort(int[] array){
        for (int i = 1; i < array.length; i++) {
            int tmp = array[i];
            int j = i - 1;
            for (; j >= 0; j--) {
                if(array[j] > tmp){
                    array[j+1] = array[j];
                }else{
                    break;
                }
            }
            array[j+1] = tmp;
        }
    }

希尔排序

原理:将大量记录数的记录进行分组。分割成若干个子序列,此时每个子序列待排序的记录个数就比较少了,然后在这些子序列中分别进行直接插入排序,当整个序列基本有序时,再对全体记录进行一次直接插入排序。

实现思路:

  1. 在希尔排序中循环调用修改后的直接插入排序。
  2. gap代表增量,相当于 i 下标与 i + gap * n下标的值为一组。
  3. 对每组进行直接插入排序。

时间复杂度:O(n^1.3 )至 O( n^1.5)之间。
空间复杂度:O(1)。
稳定性:不稳定。

        // 希尔排序
    public static void shellSort(int[] array){
            int gap = array.length;
            while(gap > 1){
                gap /= 2;
                shell(array,gap);
            }
    }

    private 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;
        }
    }

堆排序

原理:将待排序的序列构造成一个大根堆。此时,整个序列的最大值就是堆顶元素。将它移走(其实就是将堆顶元素与堆尾元素交换,此时末尾元素就是最大值),然后将剩余的n - 1个序列重新构造成一个大根堆,这样就可以得到n个元素中的次大值。如此反复操作,便能得到一个有序序列。

实现思路:

  1. 根据排序需求(升序实现大根堆,降序实现小根堆),实现堆。
  2. 每次将堆顶元素放末尾,将剩余元素再变为堆。

实现堆的描述

时间复杂度:O(N*logN)。
空间复杂度:O(1)。
稳定性:不稳定。

// 堆排序
    public static void heapSort(int[] array) {
        createHeap(array);
        int end = array.length-1;
        while (end > 0) {
            swap(array,0,end);
            siftDown(array,0,end);
            end--;
        }
    }
    //实现大根堆
    private static void createHeap(int[] array) {
        for (int parent = (array.length-1-1)/2; parent >= 0; parent--) {
            siftDown(array,parent,array.length);
        }

    }
    private static void siftDown(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 static void swap(int[] array, int x, int y) {
        int tmp = array[x];
        array[x] = array[y];
        array[y] = tmp;
    }

归并排序

原理:假设初始序列含有n个记录,则可以看成n个有序的子序列,每个子序列的长度为1,然后两两归并,得到n/2向上取整个长度为2或1的有序子序列;再两两归并,如此重复,直至得到一个长度为n的有序序列为止,这种排序称为2路归并排序。

实现思路:

  1. 分解逻辑:将递归结束条件变为左下标大于右下标达到每个子序列只有1个元素。
  2. 合并逻辑:因为两个子序列本身是有序的了,只要将两个序列从左到右的最小值依次放入临时数组,最后有一个数组剩余元素直接放入即可。(注意临时数组返回给目标数组时开头不是0下标开始)

时间复杂度:O(N*logN)。
空间复杂度:O(N)。
稳定性:稳定。

//归并排序
        public static void mergeSort(int[] array) {
            mergeSortTmp(array,0,array.length-1);
        }

        private static void mergeSortTmp(int[] array,int left,int right) {
            if(left >= right) {
                return;
            }
            int mid = (left + right) / 2;
            mergeSortTmp(array,left,mid);
            mergeSortTmp(array,mid+1,right);
            //分解完毕
            // 合并
            merge(array,left,mid,right);
        }

        private static void merge(int[] array, int left, int mid, int right) {
            int[] tmp = new int[right-left+1];
            int k = 0;
            int s1 = left;
            int s2 = mid+1;
            while (s1 <= mid && s2 <= right) {
                if(array[s1] <= array[s2]) {
                    tmp[k++] = array[s1++];
                }else {
                    tmp[k++] = array[s2++];
                }
            }
            while (s1 <= mid) {
                tmp[k++] = array[s1++];
            }
            while (s2 <= right) {
                tmp[k++] = array[s2++];
            }
            //可以保证tmp数组 是有序的
            for (int i = 0; i < k; i++) {
                array[i+left] = tmp[i];
            }
        }

快速排序

原理:通过一趟排序将待排序记录分割成独立的两个部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录进行排序,以达到整个序列有序的目的。

时间复杂度:O(N*logN)至O(n^2)。
空间复杂度:O(logN)至O(N)。
稳定性:不稳定。

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

    private static void quick(int[] array,int start,int 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 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;
    }

标签:tmp,排序,int,++,算法,array,数据结构,left
From: https://blog.csdn.net/yj20040627/article/details/140643539

相关文章

  • 山东大学数据结构与算法实验13最小生成树(Prim算法/Kruskal算法)
    A : Prim算法题目描述使用prim算法实现最小生成树输入输出格式输入第一行两个整数n,e。n(1≤n≤200000)代表图中点的个数,e(0≤m≤500000)代表边的个数。接下来e行,每行代表一条边:ijw 表示顶点i和顶点j之间有一条权重为w的边输出最小生成树所有边的......
  • js实现JSON数据根据某个字段排序
    1、js含有内置的sort()方法该方法是按字母顺序对数组进行排序的即按照字符编码进行排序,当数组中元素为数字类型时,排序结果与我们设想的完全不同,因为默认是按照字符编码的顺序进行排序的2、实现/**@description根据某个字段实现对json数组的排序*@para......
  • 烧录算法制作
    前言在使用Keil的时候,我们一般会通过一个下载器与目标芯片连接,这样就可以实现的代码下载或调试。那么下载器是如何将我们的应用程序烧写在我们芯片内部Flash当中的呢,是否可以同样的方式烧录在外部Flash上呢?这是此片文章所要说明的。MDK下载算法原理通过MDK创建一批与地址信息无......
  • 算法
    算法库函数next_permutation(start,end)prev_permutation(start,end)(全排列函数)[库函数介绍](C++中全排列函数next_permutation用法-CSDN博客)[库函数原理](C++中next_permutation函数的使用方法、原理及手动实现_c++next_permutation-CSDN博客)库函数原理基本解释:......
  • 最细哈希表相关的力扣题和讲解和Java、C++常用的数据结构(哈希法)来源于代码随想录,十分
    20240725一、什么时候适用什么样的结构。1.java中1.1HashSet:1.2TreeSet:1.3LinkedHashSet:1.4HashMap:1.5TreeMap:1.6LinkedHashMap:1.7总结2.c++中2.1std::unordered_set:2.2std::set:2.3std::multiset:2.4std::unordered_map:2.5std::map:2.6std::multimap:3代码......
  • (8-6-05)优先级遍历(Priority-based Search)算法:基于tkinter的多算法路径规划程序(5)
    (7)函数breadth_first_search实现了广度优先搜索算法。它使用一个队列来存储待探索的节点,并通过迭代地从队列中取出节点来搜索路径。在搜索过程中,它会调用`add_neighbours`函数来添加节点的相邻节点,并在添加节点后继续搜索。当找到目标节点时,函数会停止搜索,并调用`paint`函数来......
  • 机器学习算法——常规算法,在同的业务场景也需要使用不同的算法(一)
    ......
  • 浅谈简单的数据结构1(树状数组 、线段树)(c++)
    *_*课间休息后的知识点轰炸树状数组引入给定长为n的序列a,q次操作,每次查询一段区间的和,或修改一个数的权值。1≤n,q≤5×10^5,0≤a_i≤10^9。分析如果没有修改操作,这是一道非常简单的前缀和题。假如我们修改前缀和数组,查询就还是O(1)的,是否可行呢?当然不行。考虑......
  • 408 数据结构线性表算法
    第一章线性表定义:线性表是具有相同数据类型的n(n>=0)个数据元素的有限序列。线性表的表示:若用L命名,表示:L=(a1,a2,a3,a4,a5,……,an)线性表的逻辑特性:a1:唯一的表头元素an:唯一的表尾元素除去a1:每个元素有且仅有一个直接前驱除去an:每个元素有且仅有一个直接后继......
  • 深度模型中的优化 - 基本算法篇
    序言在深度学习中,模型优化是提升模型性能与训练效率的关键环节。深度模型通过优化算法不断调整其内部参数,以最小化损失函数,从而实现对复杂数据的有效拟合与预测。本篇章将简要概述深度模型中的几种基本优化算法,包括梯度下降法及其变种,这些算法在推动深度学习领域的发展中起......