当前位置: 首页 > news >正文

[数据结构] 排序

目录

1. 排序的概念

2. 常见的排序算法

3. 常见排序算法的实现

3.1 插入排序

3.1.1 基本思想

3.1.2 直接插入排序

3.1.3 希尔排序

3.2 选择排序

3.1.1 基本思想

3.1.2 直接选择排序

3.1.3 堆排序

3.3 交换排序

3.3.1 基本思想

3.3.2 冒泡排序

3.3.3 快速排序

3.3.4 快速排序的优化

3.3.5 快速排序的非递归方法实现

3.5 归并排序

3.5.1 递归实现

3.5.2 非递归实现

3.5.3 归并排序的应用场景

4. 排序算法的总结

5. 其他非比较排序

5.1 计数排序

5.2 基数排序

5.3 桶排序


1. 排序的概念

排序:排序就是指将将一连串的数据,按照某种规则排列成从小到大或者从大到小的顺序。

稳定性:稳定性就是指在排序的过程中,如果两个数相同,排序前两个数的先后顺序与排序后两个数的先后顺序相同,则说明这种排序是稳定的。

内部排序:数据元素少的时候可以全部放在内存中排序。

外部排序:数据元素多时,就只能部分数据放在内存中排序,其余元素放在外存中,跟内存来回交换元素。

2. 常见的排序算法

3. 常见排序算法的实现

3.1 插入排序

3.1.1 基本思想

插入排序就是将一个数据插入到一组已经排好序的数据当中,知道所有数据都插入进来,此时数据就是有序的。比如说打扑克牌时候,整理牌的过程。

3.1.2 直接插入排序

直接插入排序是依次将元素插入,第一次插入时有序,第二次插入时,将插入元素和前面元素比较,排成有序插入,依次类推,知道插入所有元素。

思路:

这里定义一个i下标作为将要插入的元素的下标,定义一个j下标作为插入位置的前一个位置,设置一个变量tmp存储插入的元素,将j下标依次递减,将前面的元素跟插入的元素比较,插入元素小的话,就将比较的元素后移一位,依次类推。

代码实现:

    /*** 直接插入排序* 时间复杂度:O(n^2)* 空间复杂度:O(1)* 稳定性:稳定*/public void insertSort(int[] array) {for (int i = 0; i < array.length; i++) {int tmp = array[i];int j = i-1;for (; j >= 0; j--) {if(tmp < array[j]) {array[j+1] = array[j];}else {//array[j+1] = tmp;break;}}array[j+1] = tmp;}}

3.1.3 希尔排序

希尔排序是先选定一个数n,将这组数据每间隔n的数据设置为1组,将每组数据进行排序,在将这个数n减小,再分组,直到这个数等于1,排序结束。

在希尔排序中,分组是根据n来分的,每间隔n的数据算一组。

代码实现如下:

    /*** 希尔排序* 时间复杂度:O(n^1.3   - n^1.5)* 空间复杂度:O(1)* 稳定性:不稳定*/public void shellSort(int[] array) {int n = array.length;while(n > 1) {n = n / 2;shell(array,n);}}private void shell(int[] array, int n) {for (int i = n; i < array.length; i++) {int tmp = array[i];int j = i - n;for (; j >= 0; j -= n) {if(tmp < array[j]) {array[j+n] = array[j];}else {break;}}array[j+n] = tmp;}}

3.2 选择排序

3.1.1 基本思想

选择排序是从未排序的序列中找到最小的或最大的一个元素,将该元素与未排序的第一个元素进行行交换,以此类推。

3.1.2 直接选择排序

这种排序方式也就是选择排序方式.

代码如下:

    /*** 选择排序* 时间复杂度:O(n^2)* 空间复杂度:O(1)* 稳定性:不稳定*/public void selectSort(int[] array) {for (int i = 0; i < array.length; i++) {int min = i;for (int j = i+1; j < array.length; j++) {if(array[j] < array[min]) {min = j;}}swap(array,i,min);}}private void swap(int[] array,int i,int j) {int tmp = array[i];array[i] = array[j];array[j] = tmp;}

3.1.3 堆排序

堆排序先创建一个大根堆,然后再将大根堆排序成小根堆,将最顶上元素跟最后一个元素交换位置,再将前面元素转换为大根堆。

   /*** 堆排序* 时间复杂度:O(n*logn)* 空间复杂度:O(1)* 稳定性:不稳定*/public void heapSort(int[] array) {//创建一个大根堆//O(n)createLeap(array);//根排序//O(n*logn)int i = array.length-1;while(i > 0) {swap(array,0,i);siftDown(array,0,i);i--;}}private void createLeap(int[] array) {int parent = (array.length - 1 -1 ) /2;while(parent >= 0) {siftDown(array,parent,array.length);parent--;}}private 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 = child + 1;}if(array[child] > array[parent]) {swap(array,child,parent);parent = child;child = 2*parent +1;}else {break;}}}

3.3 交换排序

3.3.1 基本思想

交换排序就是设置两个下标,将两个下标对应的值进行比较,小的往前面移动,大的往后面移动。

3.3.2 冒泡排序

冒泡排序是设置两个变量,第一个变量是需要比较的趟数,因为有几个元素就需要比较几趟,每趟都能将最大的元素放到最后一个位置,第二个变量是对应的下标遍历这串数据,相邻之间比较,大的数交换位置到后面,将未排序的数据比较完成就行。

代码如下:

    /***冒泡排序* 时间复杂度:O(n^2)* 空间复杂度:O(1)* 稳定性:稳定*/public void bubbleSort(int[] array) {for (int i = 0; i < array.length; i++) {for (int j = 0; j < array.length - 1 - i; j++) {if(array[j] > array[j+1]) {swap(array,j,j+1);}}}}private void swap(int[] array, int i, int j) {int tmp = array[i];array[i] = array[j];array[j] = tmp;}

3.3.3 快速排序

快速排序是先找一个位置的值tmp,然后将数据中的比tmp元素小的数据,放到tmp的左边,将比tmp大的数据放到tmp的右边。知道最后遍历成功排序。

1. Hoare版

这个版本的快速排序是将数据的第一个位置的数据设置为tmp,然后设置两个下标left和right,刚好位于数据的两端,先从右边找到小于tmp的数,再从左边找到大于tmp的数,然后交换顺序,最后left和right相遇时,将该位置的数据和left初始位置的数据交换位置。

上面是一遍的过程,当第一次排序完,tmp的数据所在位置的左边都比tmp小,右边都比tmp大,然后左边和右边继续比较,最后知道数据只剩一个时候不用比较了,停止递推。

代码如下:

    /*** 快速排序(Hoare版)* 时间复杂度:最坏的情况(顺序和逆序)O(n^2) 最好的情况O(nlogn)* 空间复杂度:最坏情况O(n)   最好情况O(logn)* 稳定性:不稳定*/public void quickSort(int[] array) {quick(array,0,array.length-1);}private void quick(int[] array, int left, int right) {if(left >= right) {return;}int pivot = partition(array, left, right);quick(array,left,pivot-1);quick(array,pivot+1,right);}//该方法在每次结束后返回left和right相遇的地方。private int partition(int[] array, int left, int right) {int first = left;int tmp = array[left];while(left < right) {while(left < right && array[right] >= tmp) {right--;}while(left < right && array[left] <= tmp) {left++;}swap(array,left,right);}swap(array,first,left);return left;}

2. 挖坑法

该方法是将第一个元素放到tmp中,然后从右边遍历,遇到小于tmp的元素,将该元素放到左边的坑的地方,此时右边有坑,从左边遍历,遇到大于tmp的元素,将该元素放到右边的坑位置。

代码如下:

    //挖坑法private 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;}

3. 前后指针法

这种方法的思路是先将开头的数据存到tmp中,设置一个指针prev为开始位置,cur为第二个位置,当碰到cur位置的数据小于tmp,cur和prev都向前移动,当cur位置的元素大于tmp时,prev停止移动,知道cur再次遇到比tmp小的元素时,prev移动一次,然后将prev和cur位置的元素交换位置。

结束条件是cur超过了数据的最后一个元素位置。

代码如下:

    //前后指针法private int partition(int[] array, int left, int right) {int prev = left;int cur = left + 1;while(cur <= right) {if(array[cur] < array[left] && array[++prev] != array[cur]) {swap(array,prev,cur);}cur++;}swap(array,prev,left);return prev;}

3.3.4 快速排序的优化

我们可以发现快速排序对于从小到大的数据和从大到小的数据的排序效率毕竟底,那么我们可以通过优化来提高快速排序的效率,让它的时间复杂度接近于O(nlogn)。

方法一:三数取中法

这里是为了提高顺序的数据,开头中间和最后的三个数进行比较,得到中间的那个数的下标,将改下标位置的数与开头的数交换位置,然后利用挖坑法排序。

代码如下:

    private void quick(int[] array, int left, int right) {if(left >= right) {return;}//三数取中法int index = findCenter(array,left,right);swap(array,left,index);int pivot = partition(array, left, right);quick(array,left,pivot-1);quick(array,pivot+1,right);}private int findCenter(int[] array, int left, int right) {int tmp = (left + right) / 2;if(array[left] < array[right]) {if(array[left] > array[tmp]) {return left;}else if(array[tmp] > array[right]) {return right;}else {return tmp;}}else {if(array[left] < array[tmp]) {return left;}else if(array[tmp] < array[right]) {return right;}else {return tmp;}}}

方法二:递归到小的区间时可以考虑使用插入排序。

这种优化是当数据大小<=自己设置的值时候,就使用直接插入排序。

代码如下:

        //直接插入排序if(right - left + 1 <= 20) {insertSort(array,left,right);return;}//当数据大小小于等于20时采用插入排序private void insertSort(int[] array, int left, int right) {for (int i = left+1; i <= right; i++) {int tmp = array[i];int j = i-1;for (; j >= left; j--) {if(array[j] > tmp) {array[j+1] = array[j];}else {break;}}array[j+1] = tmp;}}

3.3.5 快速排序的非递归方法实现

非递归实现快速排序,需要我们设置一个栈,将挖空排序后返回的值pivot进行判断,如果pivot+ 1 < right,说明pivot的右边有至少两个数据,此时将右边的开头下标和最后下标入栈,再出栈作为参数传给挖空法快速排序,进行排序,知道栈里面没有元素,排序完成。

代码实现:

    //非递归实现快速排序public void quickSortNonR(int[] array) {int left = 0;int right = array.length - 1;Stack<Integer> stack = new Stack<>();//调用挖空法快速排序int pivot = partition(array,left,right);if(pivot > left + 1) {stack.push(left);stack.push(pivot - 1);}if(pivot < right - 1) {stack.push(pivot+1);stack.push(right);}while(!stack.isEmpty()) {right = stack.pop();left = stack.pop();pivot = partition(array,left,right);if(pivot > left + 1) {stack.push(left);stack.push(pivot - 1);}if(pivot < right - 1) {stack.push(pivot+1);stack.push(right);}}}

3.5 归并排序

3.5.1 递归实现

归并排序是利用分治法来进行排序的,排序整组数据,可以先将数据平分为两半,一直分,分到最后单个元素为一组停止,然后回溯进行排序,最后合并成一整组时排序完毕。

代码如下:

      /*** 归并排序* 时间复杂度:O(nlogn)* 空间复杂度:O(n)* 稳定性:稳定*/public void mergeSort(int[] array) {merge(array,0,array.length-1);}private void merge(int[] array, int left, int right) {if(left >= right) {return;}//拆分int mid = (left + right) / 2;merge(array,left,mid);merge(array,mid+1,right);//合并mergeChild(array,left,mid,right);}private void mergeChild(int[] array, int left, int mid, int right) {int s1 = left;int e1 = mid;int s2 = mid+1;int e2 = right;//临时数组int[] tmp = new int[right - left + 1];int k = 0;while(s1 <= e1 && s2 <= e2) {if(array[s1] < array[s2]) {tmp[k++] = array[s1++];}else {tmp[k++] = array[s2++];}}while(s1 <= e1) {tmp[k++] = array[s1++];}while(s2 <= e2) {tmp[k++] = array[s2++];}//将临时数组的值放到原来数组中for (int i = 0; i < tmp.length; i++) {array[i+left] = tmp[i];}}

3.5.2 非递归实现

非递归实现归并排序是先按照一个元素为一组,接着以2的倍数元素为一组,设置坐标i为两组相邻元素的开头位置,找到left mid right 下标,将两组元素之间进行排序。以此类推,知道最后所有元素为一组时排序完成。

代码如下:

    //非递归实现归并排序public void mergeSortNo(int[] array) {int gap = 1;while(gap <= array.length) {for (int i = 0; i < array.length; i = i + 2*gap) {int left = i;int mid = left + gap - 1;if(mid >= array.length) {mid = array.length-1;}int right = mid + gap;if(right >= array.length) {right = array.length-1;}mergeChild(array,left,mid,right);}gap = 2 * gap;}}

3.5.3 归并排序的应用场景

当要处理很大容量的数据时,对其排序,内存中不能放下这么多的数据进行排序,我们可以利用归并排序的思想,将这些数据均分为等量的小组数据放入内存中进行排序,再将这些小组数据进行合并,就可以实现排序了。

4. 排序算法的总结

下面是个算法的时间复杂度和空间复杂度以及稳定性:

5. 其他非比较排序

5.1 计数排序

计数排序适用于数据都集中在一定的范围中,如果数据中存在差值比较大的数据,会导致空间复杂度太大,所以适用的场景比较单一。

代码实现:

       /*** 时间复杂度:O(max(n,范围))* 空间复杂度:O(创建数组范围)* 稳定性:稳定* @param array*///计数排序public void CountSort(int[] array) {int min = array[0];int max = array[0];for (int i = 1; i < array.length; i++) {if(min > array[i]) {min = array[i];}if(max < array[i]) {max = array[i];}}int[] tmp = new int[max-min+1];for (int i = 0; i < array.length; i++) {tmp[array[i]-min]++;}int j = 0;for (int i = 0; i < tmp.length; i++) {while(tmp[i] != 0) {array[j++] = i+min;tmp[i]--;}}}

5.2 基数排序

基数排序是先设置10个队列,然后将所有的数从个位数开始将数字放到对应下标的队列中,然后从0开始的队列依次取出数据,再比较十位上的数据,,循环往复,最后比较到这组数据的最高位数据时停止。

想了解更多的可以看下面的文章:

基数排序详解

5.3 桶排序

桶排序是在计数排序的基础上改善的,设置几个桶,每个桶对应一个范围,将数据按照桶的范围放入到桶中,将桶内部的数据进行排序,再将桶中的元素取出来放到数组里面。

    //桶排序public void bucketSort(int[] array) {//得到数据的最大值和最小值int min = array[0];int max = array[0];for (int i = 1; i < array.length; i++) {if(min > array[i]) {min = array[i];}if(max < array[i]) {max = array[i];}}//计算有几个桶int bucketNum = (max - min) / array.length + 1;ArrayList<ArrayList<Integer>> arr = new ArrayList<>(bucketNum);for (int i = 0; i < bucketNum; i++) {arr.add(new ArrayList<Integer>());}//将每个元素放到桶中for (int i = 0; i < array.length; i++) {int num = (array[i] - min) / array.length;arr.get(num).add(array[i]);}//对每个桶进行排序for (int i = 0; i < arr.size(); i++) {Collections.sort(arr.get(i));}//将桶里面元素放到原序列中int index = 0;for (int i = 0; i < arr.size(); i++) {for (int j = 0; j < arr.get(i).size(); j++) {array[index++] = arr.get(i).get(j);}}}

文章转载自:

http://u6mAItTs.kwjyt.cn
http://FlfA17No.kwjyt.cn
http://pr9JXQFH.kwjyt.cn
http://3NiJeBuw.kwjyt.cn
http://CpovT6jy.kwjyt.cn
http://9UqpbVao.kwjyt.cn
http://haOghgW6.kwjyt.cn
http://f61YDLbq.kwjyt.cn
http://AF5Ms6Yd.kwjyt.cn
http://Cm4cX9gW.kwjyt.cn
http://VUEPV7Ei.kwjyt.cn
http://rIF4P74B.kwjyt.cn
http://UhWYQDUE.kwjyt.cn
http://kYziJfOX.kwjyt.cn
http://mq9mYimo.kwjyt.cn
http://W7kQBNkz.kwjyt.cn
http://bOW2Nj1B.kwjyt.cn
http://kVaxSooY.kwjyt.cn
http://qrcyikIw.kwjyt.cn
http://nfmAGgVx.kwjyt.cn
http://2ccc0f4A.kwjyt.cn
http://eUwDwSLJ.kwjyt.cn
http://GG8B9OIH.kwjyt.cn
http://DHj5pYkk.kwjyt.cn
http://IBmDy3pQ.kwjyt.cn
http://CchqflAs.kwjyt.cn
http://YoTy9j7J.kwjyt.cn
http://9XJywgmg.kwjyt.cn
http://xMv1Zfuc.kwjyt.cn
http://TLruxCe2.kwjyt.cn
http://www.dtcms.com/a/383068.html

相关文章:

  • Python网络与多任务编程:TCP/UDP实战指南
  • Elasticsearch面试精讲 Day 17:查询性能调优实践
  • Go-zero 构建 RPC 与 API 服务全流程
  • CRI容器运行时接口
  • 《Python 自动化表单填写全攻略:从基础操作到实战案例》
  • 黑马程序员JVM基础学习笔记
  • 驰骋低代码BPM开发平台的组成部分
  • ubuntu22.04源码安装ffmpeg-4.4
  • 黑马Java进阶教程,全面剖析Java多线程编程,并发和并行,笔记02
  • 大数据毕业设计选题推荐-基于大数据的教育与职业成功关系可视化分析系统-Spark-Hadoop-Bigdata
  • Ubuntu Server 安装图形界面和通过Window远程桌面连接服务器(Xrdp)
  • 贪心算法在云计算虚拟机部署问题中的应用
  • macOS中找不到钥匙串访问
  • 基于FPGA实现LeNet-5(经典CNN识别手写数字)推理
  • 算法-双指针5.6
  • Eino Indexer 组件完全指南
  • 算法-双指针3.4
  • 【开题答辩全过程】以 “旧书驿站”微信小程序的设计与开发为例,包含答辩的问题和答案
  • Altium Designer使用精通教程 第七章(PCB输出)
  • 【秋招笔试】2025.09.13美团秋招算法岗真题\
  • LeetCode 2367.等差三元组的数目
  • 第16课:多模态Agent协作
  • 《网络攻防技术》第一章: 网络攻防概述
  • 消息语义一致性:Exactly-Once 之外的“效果等价”设计
  • SPI NOR Flash 的命令码详解
  • kafka--基础知识点--5.2--最多一次、至少一次、精确一次
  • Spark(1):不依赖Hadoop搭建Spark环境
  • Python快速入门专业版(三十):函数进阶:函数嵌套与作用域(内部函数访问外部变量)
  • LLaMA-Factory windows wls 安装vllm,并对比速度
  • 全排列问题深度解析:用 Python 玩转 DFS 回溯与迭代