数据结构 排序

数据结构 排序

文章目录

  • 数据结构 排序
    • 1. 排序的概念及引用
      • 1.1 排序的概念
      • 1.2 常见的排序算法
    • 2.常见排序算法的实现
      • 2.1 插入排序
        • 2.1.1 基本思想
        • 2.1.2 直接插入排序
        • 2.1.3 希尔排序(缩小增量排序)
      • 2.2 选择排序
        • 2.2.1 基本思想
        • 2.2.2 直接选择排序
        • 2.2.3 堆排序
      • 2.3 交换排序
        • 2.3.1 基本思想
        • 2.3.2 冒泡排序
        • 2.3.2 快速排序
        • 2.3.3 非递归-快排
      • 2.4 归并排序
    • 3. 排序算法复杂度及稳定性分析
    • 4. 其它非基于比较的排序(拓展)

1. 排序的概念及引用

1.1 排序的概念

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

稳定性: 若两元素相同,排序后第一个元素依旧在第二个元素之前,则称排序算法是稳定的,否则称为不稳定的,如图:

在这里插入图片描述

内部排序: 数据元素全部放在内存中的排序;

外部排序: 数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

1.2 常见的排序算法

在这里插入图片描述

本文将对以上排序算法进行分析

2.常见排序算法的实现

2.1 插入排序

2.1.1 基本思想

直接插入排序是一种简单的插入排序法,其基本思想是:

把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。实际中我们玩扑克牌时,就用了插入排序的思想:

在这里插入图片描述

2.1.2 直接插入排序

操作方法:

  • 传入数组arr,此时需要插入元素下标设为i
  • arr[i]依次与arr[i-1], arr[i-2]等元素进行比较,直到找到合适的插入位置k
  • 将(k-i)之间的元素依次往后移动一个位置,再将arr[i] 插入到k位置
  • 重复上述操作,直到i下标遍历结束

在这里插入图片描述

代码示例:

/*** 直接插入排序* 时间复杂度:最好O(n) 最坏O(n²)* 空间复杂度:O(1)* 稳定性:稳定* 适用范围:基本上趋于有序的序列,其越有序速度越快*/
public  void insertSort(int[] arr) {for (int i = 0;i < arr.length;i++) {int temp = arr[i];int j = i - 1;for (;j >= 0;j--) {if (arr[j] > temp) {arr[j+1] = arr[j];}else {break;}}arr[j+1] = temp;}}

在这里插入图片描述

直接插入排序特性:

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高
  2. 时间复杂度:最好O(N),最坏O(N²)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定
2.1.3 希尔排序(缩小增量排序)

希尔排序法由称缩小增量法。希尔排序法的基本思想是:先选定一个整数(gap),把待排序文件中所有记录分成多个组,所有距离相等的分在同一个组内,并对每一组内的记录进行排序。然后,取原来gap的一半,重复上述分组和排序的工作。当gap = 1时,所有记录已经在统一组内排好序

在这里插入图片描述

代码示例:

 /*** 希尔排序* 是对直接插入排序的优化* @param arr*/public void shellSort(int[] arr) {int gap = arr.length;while(gap > 1) {gap /= 2;shell(arr, gap);}}private void shell(int[] arr, int gap) {for (int i = gap;i < arr.length;i++) {int temp = arr[i];int j = i - gap;for (;j >= 0;j -= gap) {if (arr[j] > temp) {arr[j+gap] = arr[j];}else {break;}}arr[j+gap] = temp;}}

在这里插入图片描述

希尔排序特性:

  1. 可以认为希尔排序是对直接插入排序的优化

  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap = 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比

  3. 希尔排序的时间复杂度不好计算,因为gap的取值方式很多,导致很难去计算,因此一些书中给出的希尔排序的时间复杂度都不固定:

    《数据结构(C语言版)》—严蔚敏

    在这里插入图片描述

    《数据结构–用面向对象方法与C++描述》—殷人昆

    在这里插入图片描述

    在这里我们按照O(n^1.25)到O(1.6 * n ^ 1.25)来算

  4. 稳定性:不稳定

2.2 选择排序

2.2.1 基本思想

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完

2.2.2 直接选择排序
  1. 在元素集合中选择关键码(下标)最大(或最小)的元素,将最小值设置为该下标元素
  2. 依次与下标后面的元素进行比较,将最小值的下标记录下来
  3. 直到下标遍历到数组最后,将此时最小值下标元素与最开始最小值的下标元素进行交换

在这里插入图片描述
1380)

代码示例:

 /*** 直接选择排序* 时间复杂度 O(N²)* 空间复杂度 O(1)* 稳定性:不稳定* 适用范围:效率不高,实际很少使用*/public void selectSort(int[] arr) {for (int i = 0;i < arr.length;i++) {int min = i;for (int j = i;j < arr.length;j++) {if (arr[j] < arr[min]) {min = j;}}swap(arr,min,i);}}private void swap(int[] arr, int x, int y) {int temp = arr[x];arr[x] = arr[y];arr[y] = temp;}

在这里插入图片描述

直接选择排序性质:

  1. 直接选择排序思路容易理解,但效率不高,实际中很少使用
  2. 时间复杂度 O(N²)
  3. 空间复杂度 O(1)
  4. 稳定性:不稳定

在这里我们可以对直接选择排序进行一个优化:

/*直接排序优化版*/public void selectSort2(int[] arr) {int left = 0;int right = arr.length - 1;while(left < right) {int minIndex = left;int maxIndex = left;for (int i = left + 1;i <= right;i++) {if (arr[i] > arr[maxIndex]) {maxIndex = i;}if (arr[i] < arr[minIndex]) {minIndex = i;}}swap(arr,minIndex,left);//此时如果maxIndex与left相等,// 需要将maxIndex位置修改为此时的minIndex位置,// 因为前面已经将left与minIndex位置的元素进行了交换if (maxIndex == left) {maxIndex = minIndex;}swap(arr,maxIndex,right);left++;right--;}}

在这里插入图片描述

2.2.3 堆排序

堆排序在之前的文章堆的应用中有举例过,这里再复习一下

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

在这里插入图片描述

代码示例:

/*** 堆排序* 1. 堆排序使用堆来选数,效率高了很多* 2. 时间复杂度:O(N*log₂N)* 3. 空间复杂度:O(1)* 4. 稳定性:不稳定* @param arr*/public void heapSort(int[] arr) {int end = arr.length - 1;while(end > 0) {swap(arr, 0, end);shiftBigDown(arr,0,end-1);end--;}}public void shiftBigDown(int[] elem, int parent,int len) {//左孩子int child = 2 * parent + 1;while(child < len) {//判断是否存在右孩子 且右孩子和左孩子哪个更大,大的为childif (child+1 < len &&  elem[child+1] > elem[child]) {child++;}//判断根节点和child的大小,若根节点大则结束遍历,否则交换两个节点if (elem[child] > elem[parent]) {swap(elem, child, parent);parent = child;child = 2 * parent + 1;}else {break;}}}

在这里插入图片描述

堆排序特性:

  1. 堆排序使用堆来选数,效率高了很多
  2. 时间复杂度:O(N*log₂N)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

2.3 交换排序

2.3.1 基本思想

所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动

2.3.2 冒泡排序

在写数组的时候有介绍过冒泡,这里再来复习一下:

以升序为例:

  1. 将数组中相邻元素从前往后依次进行比较,如果前一个元素比后一个元素大,则交换,一趟下来后最大元素就在数组的末尾
  2. 依次重复上述过程,直到数组中所有的元素都排列好

在这里插入图片描述

代码示例:

/*** 冒泡排序* 1. 容易理解,优化后效率高* 2. 时间复杂度:O(N²)  优化后最好情况可达O(N)* 3. 空间复杂度:O(1)* 4. 稳定性:稳定*/public void bubbleSort(int[] arr) {for (int i = 0;i < arr.length;i++) {boolean flg = false;for (int j = 0;j < arr.length - i - 1;j++) {//如果走完一圈后发现没有交换过,说明整个数组元素已经有序,后面直接退出循环if (arr[j+1] < arr[j]) {swap(arr,j,j+1);flg = true;}}if (!flg) {break;}}}

在这里插入图片描述

冒泡排序特性:

  1. 容易理解,优化后效率高
  2. 时间复杂度:O(N²) 优化后最好情况可达O(N)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定
2.3.2 快速排序

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复==(利用递归)==该过程,直到所有元素都排列在相应位置上为止

主框架代码示例:

 //假设按照升序对arr数组中[left,right)区间的元素进行排序public void quickSort(int[] arr, int left, int right) {if (left >= right) {return;}//按照基准值对arr数组的[left,right)区间中的元素进行划分int div = partitionHoare(arr, left, right);//划分成功后以div为边界形成了左右两部分[left,div)和[div,right)//递归排[left,div)quickSort(arr, left, div);//递归排[div+1,right)quickSort(arr, div + 1, right);}

将区间按照基准值划分为左右两半部分的常见方式有:

  1. Hoare版

    在这里插入图片描述

    代码示例:

    /*Hoare法*/
    public int partitionHoare(int[] arr, int left, int right) {int i = left;int j = right;int pivot = left;while(i < j) {while(i < j && arr[j] >= arr[pivot]) {j--;}while(i < j && arr[i] <= arr[pivot]) {i++;}swap(arr, i, j);}swap(arr, i, pivot);return i;}
    

在这里插入图片描述

  1. 挖坑法

    基本思路:

    1. 先选取一个基准值(这里我们设置第一个数为基准值),选出来后认为此位置为空(坑)
    2. 从右遍历直到找到小于该基准值的元素,然后将该元素填到前面的坑,同时此位置变为空(坑)
    3. 从左遍历直到找到大于该基准值的元素,然后将该元素填到前面的坑,同时此位置变为空(坑)
    4. 若左右指针未相遇,重复上述2,3操作
    5. 当左右指针相遇时,将相遇位置的值修改为基准值

    在这里插入图片描述

    代码示例:

    /*挖坑法*/public int partition(int[] arr, int left, int right) {int i = left;int j = right;int pivot = left;while(i < j) {while(i < j && arr[j] >= arr[pivot]) {j--;}arr[i] = arr[j]; //将j位置的元素赋给i位置,此时虽然有两个相同的,但j位置的元素可以相当于空while(i < j && arr[i] <= arr[pivot]) {i++;}arr[j] = arr[i]; //将i位置的元素}swap(arr, pivot, i);return i;}
    

    在这里插入图片描述

  2. 前后指针法:

    基本思路:

    1. 先选取一个基准值(这里我们设置第一个数为基准值),设置两个指针prevcur,prev从左边第一个值开始,cur开始为prev+1
    2. 若cur此时小于基准值,则prev++,并且判断prev++后是否等于此时的cur,若不等于,交换cur与prev
    3. 每次判断完都要移动cur(cur++)
    4. 直到cur>最后一个下标,则交换prev和基准值,然后将此时prev的位置返回

    在这里插入图片描述

    代码示例:

     /*前后指针法*/public int partition2(int[] arr, int left, int right) {int pivot = left; //基准值int prev = left;int cur = left + 1;while(cur <= right) {if (arr[cur] < arr[pivot] && arr[++prev] != arr[cur]) {swap(arr,prev,cur);}cur++;}swap(arr,pivot,prev);return prev;}
    

    在这里插入图片描述

2.3.3 非递归-快排

非递归方式版本的快速排序:

	/*非递归-快排*/public void quickSortNor(int[] arr) {int start = 0;int end = arr.length - 1;Stack<Integer> stack = new Stack<>();int pivot = partitionHoare(arr, start, end);if (pivot - 1 > start) {stack.push(start);stack.push(pivot-1);}if (pivot + 1 < end) {stack.push(pivot+1);stack.push(end);}while(!stack.isEmpty()) {end = stack.pop();start = stack.pop();pivot = partitionHoare(arr, start, end);if (pivot - 1 > start) {stack.push(start);stack.push(pivot-1);}if (pivot + 1 < end) {stack.push(pivot+1);stack.push(end);}}}

在这里插入图片描述

快排特性:

  1. 快速排序整体的综合性能和使用场景都是比较好的
  2. 时间复杂度:O(N*log₂N)
  3. 空间复杂度:O(log₂N)
  4. 稳定性:不稳定

2.4 归并排序

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。做法就是将已有序的子序列合并,从而得到完全有序的序列,即先使每个子序列有序,再使子序列段之间有序。若将两个有序表合并成一个有序表,称为二路归并:

在这里插入图片描述

在这里插入图片描述

代码示例:

/*** 归并排序*/public void mergeSort(int[] arr) {mergeSortFun(arr, 0, arr.length - 1);}private void mergeSortFun(int[] arr, int start, int end) {if (start >= end) {return;}int mid = (start + end) / 2;mergeSortFun(arr, start, mid);mergeSortFun(arr, mid + 1, end);//合并merge(arr, start, mid, end);}private void merge(int[] arr, int left, int mid, int right) {int s1 = left; //数组一起始位置int e1 = mid; //数组一结束位置int s2 = mid + 1; //数组二起始位置int e2 = right; //数组二结束位置// 定义一个新的数组int[] newArr = new int[right-left+1];int k = 0; //新数组下标while(s1 <= e1 && s2 <= e2) {if (arr[s1] < arr[s2]) {newArr[k++] = arr[s1++]; //赋完值后两个下标都要往后走}else {newArr[k++] = arr[s2++];}}//其中一个数组走完了,将另一个数组所有的值都赋到新的数组while(s1 <= e1) {newArr[k++] = arr[s1++];}while(s2 <= e2) {newArr[k++] = arr[s2++];}//将传入数组转换为新数组for (int i = 0;i < newArr.length;i++) {arr[i + left] = newArr[i];}}

在这里插入图片描述

归并排序特性:

  1. 归并排序的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题
  2. 时间复杂度:O(N*log₂N)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定

拓展:

  • 外部排序:排序过程需要在磁盘等外部存储进行的排序

    前提:内存只有1G,需要排序的数据有100G

    因为内存中无法把所有的数据全部放下,所有需要外部排序,而归并排序是最常用的外部排序

    1. 先把文件切分成200份,每个512M
    2. 分别对512M排序,因为内存已经可以放的下,所有任意排序方式都可以
    3. 进行二路归并,同时对200分有序文件做归并过程,最终结果就有序了

3. 排序算法复杂度及稳定性分析

在这里插入图片描述

排序方法最好平均最坏空间复杂度稳定性
冒泡排序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)稳定

4. 其它非基于比较的排序(拓展)

我们这里拓展一个排序算法-计数排序

基本思想:计数排序又称鸽巢原理,是对哈希直接定址法的变形应用。操作步骤:

  1. 统计相同元素出现的次数
  2. 根据统计的结果将序列回收到原来的序列中

在这里插入图片描述

代码示例:

/*** 计数排序* 1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限* 2. 时间复杂度:O(MAX(N,范围))* 3. 空间复杂度:O(范围)* 4. 稳定性:稳定*/public void countSort(int[] arr) {int minVal = arr[0];int maxVal = arr[0];for (int i = 0;i < arr.length;i++) {//找到数组最大值和最小值用来求新数组长度if (arr[i] > maxVal) {maxVal = arr[i];}if (arr[i] < minVal) {minVal = arr[i];}}// 确定计数数组长度int len = maxVal - minVal + 1;int[] count = new int[len];// 遍历arr数组,把arr数组中出现元素的次数记录在计数数组中for (int i = 0;i < arr.length;i++) {count[arr[i] - minVal]++;}// 此时计数数组已经存放了每个数据出现的次数// 遍历计数组,把实际的数据写回arr数组int index = 0;for (int j = 0;j < count.length;j++) {while(count[j] > 0) {arr[index] = minVal + j;index++;count[j]--;}}}

在这里插入图片描述

计数排序特性:

  1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限
  2. 时间复杂度:O(MAX(N,范围))
  3. 空间复杂度:O(范围)
  4. 稳定性:稳定

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/138717.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Echarts(1)

Echarts官方文档----快速上手 - Handbook - Apache ECharts 1.1ECharts的快速上手 ECharts 的入门使用特别简单, 5分钟就能够上手. 他大体分为这几个步骤 步骤1&#xff1a;引入 echarts.js 文件 该文件获取方式可通过&#xff1a;在 https://www.jsdelivr.com/package/npm…

软件测试用例设计方法-因果图法

边界值法是等价类划分法的补充&#xff0c;所以&#xff0c;它们是一对搭档。 那么&#xff0c;判定表法有没有它的搭档呢&#xff1f; 答案是&#xff0c;有的。那就是本篇文章分享的用例设计方法—— 因果图法 。 定义 因果图法&#xff1a; 用来处理等价类划分和边界值考…

JNDI-Injection-Exploit工具安装

从github上下载安装 git clone https://github.com/welk1n/JNDI-Injection-Exploit.git 打开 cd JNDI-Injection-Exploit 编译安装&#xff0c;Maven入门百科_maven中quickstart是什么意思-CSDN博客 mvn clean package -DskipTests 因为提示mvn错误&#xff0c;解决下…

IDEA中点击New没有Java Class

解决办法&#xff1a;右键src&#xff0c;也可以是其他文件名&#xff0c;点击Mark Directory as 点击Sources Root即可

VMware 配置记录

VMware 配置笔记 CentOS 7.9 镜像下载 官网太慢&#xff0c;建议在阿里云镜像站去CentOS配置页找标准版下载。 选标准版即可&#xff0c;各版本区别&#xff1a; DVD&#xff1a;标准版&#xff0c;包含常用软件&#xff0c;体积为 4.4 G&#xff1b;Everything&#xff1a…

AFL模糊测试+GCOV覆盖率分析

安全之安全(security)博客目录导读 覆盖率分析汇总 目录 一、代码示例 二、afl-cov工具下载 三、编译带覆盖率的版本并启动afl-cov 四、AFL编译插桩并运行afl-fuzz 五、结果查看 AFL相关详见AFL安全漏洞挖掘 GCOV相关详见GCOV覆盖率分析 现将两者结合&#xff0c;即进…

数据结构 - 4(栈和队列6000字详解)

一&#xff1a;栈 1.1 栈的概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原…

【算法训练-回溯算法 一】【经典模版】全排列

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是【回溯算法】&#xff0c;使用【数组】这个基本的数据结构来实现&#xff0c;这个高频题的站点是&#xff1a;CodeTop&#xff0c;筛选条件为&…

lvgl 页面管理器

lv_scr_mgr lvgl 界面管理器 适配 lvgl 8.3 降低界面之间的耦合使用较小的内存&#xff0c;界面切换后会自动释放内存内存泄漏检测 使用方法 在lv_scr_mgr_port.h 中创建一个枚举&#xff0c;用于界面ID为每个界面创建一个页面管理器句柄将界面句柄添加到 lv_scr_mgr_por…

55 零钱兑换

零钱兑换 题解1 DP另一种解法(更好记) 题解2 递归 给你一个整数数组 coins &#xff0c;表示不同面额的硬币&#xff1b;以及一个整数 amount &#xff0c;表示总金额。 计算并返回可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额&#xff0c;返回…

【vr】【unity】白马VR课堂系列-VR开发核心基础05-主体设置-手柄对象的引入和设置

【视频教学】 【白马VR课堂系列-VR开发核心基础05-主体设置-手柄对象的引入和设置】 https://www.bilibili.com/video/BV19D4y1N73i/?share_source=copy_web&vd_source=7f5c96f5a58b7542fc6b467a9824b04e 【内容】 上一节引入了XR Origin并进行了初步设置,运行测试时V…

通达OA 2016网络智能办公系统 handle.php SQL注入漏洞

一、漏洞描述 北京通达信科科技有限公司通达OA2016网络智能办公系统 handle.php 存在sql注入漏洞&#xff0c;攻击者可利用此漏洞获取数据库管理员权限&#xff0c;查询数据、获取系统信息&#xff0c;威胁企业单位数据安全。 二、网络空间搜索引擎查询 fofa查询 app"T…