数据结构(六) 排序
一、排序(Sorting)
概念: 重排数据元素,使其按关键字有序。
内部排序:待排序的数据元素全部存入计算机内存中,排序过程中不需要访问外存;
外部排序:待排序的数据元素不能全部装入内存,排序过程中需要不断访问外存。
主要讨论内部排序。
按排序过程中依据的原则不同:插入排序、交换排序、选择排序、归并排序。
按排序过程的工作量:
简单排序,时间复杂度O(n^2);
先进排序,时间复杂度O(n logn);
基数排序,时间复杂度O(d*n)。
对于排序表,假设每个数据元素可以与一个关键字相关,并且记录都可以按关键字进行比较,可以使用类类型转换函数自动将数据元素转换为关键字类型,从而实现对数据元素的比较自动转换为关键字的比较。
二、插入排序
直接插入排序:
一种简单的排序算法,基本思想是将第一个数据元素看成是一个有序子序列,再依次从第二个数据元素起逐个插入到这个有序的子序列中。
第i趟插入排序InsertSort,当elem[i]比它前面的元素小时,就向前移动此元素,直到遇到一个比它小或相等的元素为止。
时间复杂度:O(n^2)
插入排序一般是低效的。
插入排序的第n躺排序,可以有两种方式把元素放到正确的位置:第一种是比较一次交换一次,直到正确的位置;第二种是先比较,然后右移不满足比较条件的元素(不交换),最后再将元素放入正确的位置。
/*比较一次,交换一次*/ public static void insertion_sort( int[] arr ){for( int i=0; i<arr.length-1; i++ ) { for( int j=i+1; j>0; j-- ) {if( arr[j-1] <= arr[j] )break;int temp = arr[j];arr[j] = arr[j-1];arr[j-1] = temp;}} }/*比较一次,右移一次元素,直到正确的位置*/ public static void directInsert(int arr[]){for(int i=1;i<arr.length;i++){int temp=arr[i];int j=i-1;for(;(j>=0&&temp<arr[j]);j--){arr[j+1]=arr[j];}arr[j+1]=temp;} }
Shell希尔排序
也称递减增量排序算法,对插入排序的一种更高效的改进版本,非稳定算法。插入排序最坏情况下时间复杂度为O(n^2),最好情况下时间复杂度为O(n)。
希尔排序基于以下两点性质而提出改进:
若待排序元素按关键字基本有序,插入排序的效率将大大提高,达到线性排序的效率;
元素个数n较小的时候,效率也会比较高。
基本思想:
先将整个待排序的全部数据元素序列分割成几个区域(若干个子序列),然后分别对各子序列进行插入排序,这样做可以让一个元素一次性地朝最终位置前进一大步。接着算法再取越来越小的步长进行排序,最后再对全体数据元素进行一次直接插入排序,这时整个序列已经“基本有序”了。
假设有一个很小的数据在一个已按升序排好序的数组的末端。如果用复杂度为O(n^2)的排序(冒泡排序或插入排序),可能会进行n次的比较和交换才能将该数据移至正确位置。而希尔排序会用较大的步长移动数据,所以小数据只需进行少数比较和交换即可到正确位置。
一个更好理解的希尔排序实现:
可以认为将数组列在一个表中并对列排序(用插入排序)。重复这过程,不过每次用更长的列来进行。最后整个表就只有一列了。将数组转换至表是为了更好地理解这算法,算法本身仅仅对原数组进行排序(通过增加索引的步长,例如是用i += step_size而不是i++)。
对于一般的排序算法,排序过程是从增量由(n/2)到1的过程。增量是子序列中相邻元素的下标差。当步长为1时,算法变为插入排序,这就保证了数据一定会被排序。
关于步长的选择,参考wiki。
/*希尔排序*/ public static void shell_sort(int[] arr) {int gap = 1;int i, j;int len = arr.length;int temp;while (gap < len / 3)gap = gap * 3 + 1; // <O(n^(3/2)) by Knuth,1973>: 1, 4, 13, 40, 121, ...for (; gap > 0; gap /= 3)for (i = gap; i < len; i++) {temp = arr[i];for (j = i - gap; j >= 0 && arr[j] > temp; j -= gap)arr[j + gap] = arr[j];arr[j + gap] = temp;} }
三、交换排序
最简单的是起泡排序(Bubble Sort),最先进的是快速排序(Quick Sort)。
Bubble Sort
将序列中的第1个元素与第2个元素比较,若前>后,交换位置,否则不交换;再将第2个元素与第3个元素比较,若前>后,交换位置,否则不交换;
……依此进行,直到第n-1个元素与第n个元素比较。
经过一趟排序后,使得n个元素最大者被安置在第n个位置上。
这时,再对前n-1个元素进行同样的过程~
直到对前2个元素完成排序。
起泡排序的最好、最坏和平均情况下的时间复杂度是相同的:O(n^2)。
/*冒泡排序*/ public static void bubbleSort(int arr[]){int len=arr.length;while(len>0) {for (int i = 0; i < len-1; i++) {if (arr[i] > arr[i + 1]) {//交换两个元素exChange(arr,i,i+1);}}len--;}}
Quick Sort
快速排序又称划分交换排序
基本思想:任选序列中的一个数据元素(通常选第1个)作为枢轴(pivot),以它和所有剩余数据元素进行比较,将所有比它小的数据元素排在它前面,将比它大的数据元素排在它后面。经过一趟排序,可按此数据元素所在位置为界,将序列划分为两个部分,再对这两个部分重复上述过程至每一部分中只剩下一个数据元素为止。
平均时间复杂度:O(nlogn)
最坏:O(n^2)
快排平均时间性能最快,但是在初始序列有序的情况下,快排时间性能最差,退化为起泡排序。
关于快排中序列的切分,一般策略是先随意地取第一个元素作为切分元素,这个元素会在一次排序后位置被排定。然后我们从数组的左端开始扫描直到找到一个大于等于它的元素,再从数组的右端开始向左扫描直到找到一个小于等于它的元素,交换他们的位置。这样我们完全可以保证左指针左边的元素都不大于切分元素,右指针右边的元素都不小切分元素。当两个指针相遇时,只需要将切分元素与左子数组最右侧的元素(a[j])交换,然后返回j。
/*快排*/ public static void qSort(int arr[] ){sort(arr,0,arr.length-1);}private static void sort(int []arr,int head,int tail ){if(head>=tail)return;int j=partition(arr,head,tail);sort(arr,head,j-1);sort(arr,j+1,tail);}private static int partition(int []a,int head,int tail){//将数组切分为a[head, ... ,i-1],a[i],a[i+1, ... ,tail]int i=head,j=tail+1;int pivot=a[head]; //切分元素while(true) {while (a[++i] < pivot) {if (i == tail)break;}while (a[--j] > pivot) {if (j == head)break;}if (i >= j)break;SortHelper.exChange(a,i,j); //交换元素 }//将pivot放入正确的位置 SortHelper.exChange(a,head,j);return j;}
四、选择排序
选择排序的基本思想是:每一趟在n-i个元素中选择最小的数据元素作为有序序列的第i个数据元素。
简单选择排序
O(n^2)
从未排序的序列中选择最小元素,接着是次小的,依此类推。为寻找下一个最小元素,需检索数组整个未排序部分,但只一次交换即将待排序元素放到正确位置上。
形象来说,选择排序是固定位置,选择元素。从第一个位置起寻找对应的元素。
/*选择排序*/ /*每次选择最大的元素放入数组末尾*/ public static void selectSort(int arr[]){int n=arr.length-1;while(n>=0){int max=0;for(int i=0;i<=n;i++){if(arr[i]>arr[max]){max=i;}}SortHelper.exChange(arr,n,max);n--;}}
堆排序(Heap Sort)
基于选择排序的先进排序方法,只需要一个元素的辅助存储空间。
堆:
定义:n个元素的序列{k1,k2,...,kn}当且仅当满足以下关系:
{k(i)<=k(2i),k(i)<=k(2i+1)}
or {k(i)>=k(2i),k(i)>=k(2i+1)}
若将满足堆定义的关系的序列的一维数组看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有非终端结点的值均不大于(或不小于)其左、右孩子结点的值。
小顶堆、大顶堆:
若序列{k1,k2,...,kn}是堆,则堆顶元素(完全二叉树的根)必为序列中n个元素的最小值(or最大值)。
对于大顶堆,堆顶元素最大,在输出堆顶元素之后,如果能使剩下的n-1个元素重新构成一个堆,则可以得到次大的元素,照此进行,将得到有序序列。
因此,实现堆排序需要实现如下算法:
(1) 将一个无序序列构成一个堆;
(2) 在输出堆顶元素后,调整剩余元素成为一个新的堆。
在最坏的情况下时间复杂度为O(nlogn),相对快排,这是最大的优势。而且只占用一个用于交换元素的临时存储空间,在实现时比快排用栈更节约存储空间。
import java.util.Arrays;public class HeapSort {private int[] arr;public HeapSort(int[] arr){this.arr = arr;}/*** 堆排序的主要入口方法,共两步。*/public void sort(){/** 第一步:将数组堆化* beginIndex = 第一个非叶子节点。* 从第一个非叶子节点开始即可。无需从最后一个叶子节点开始。* 叶子节点可以看作已符合堆要求的节点,根节点就是它自己且自己以下值为最大。*/int len = arr.length - 1;int beginIndex = (len - 1) >> 1; for(int i = beginIndex; i >= 0; i--){maxHeapify(i, len);}/** 第二步:对堆化数据排序* 每次都是移出最顶层的根节点A[0],与最尾部节点位置调换,同时遍历长度 - 1。* 然后从新整理被换到根节点的末尾元素,使其符合堆的特性。* 直至未排序的堆长度为 0。*/for(int i = len; i > 0; i--){swap(0, i);maxHeapify(0, i - 1);}}private void swap(int i,int j){int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}/*** 调整索引为 index 处的数据,使其符合堆的特性。* * @param index 需要堆化处理的数据的索引* @param len 未排序的堆(数组)的长度*/private void maxHeapify(int index,int len){int li = (index << 1) + 1; // 左子节点索引int ri = li + 1; // 右子节点索引int cMax = li; // 子节点值最大索引,默认左子节点。if(li > len) return; // 左子节点索引超出计算范围,直接返回。if(ri <= len && arr[ri] > arr[li]) // 先判断左右子节点,哪个较大。cMax = ri;if(arr[cMax] > arr[index]){swap(cMax, index); // 如果父节点被子节点调换,maxHeapify(cMax, len); // 则需要继续判断换下后的父节点是否符合堆的特性。 }}/*** 测试用例* * 输出:* [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9]*/public static void main(String[] args) {int[] arr = new int[]{3,5,3,0,8,6,1,5,8,6,2,4,9,4,7,0,1,8,9,7,3,1,2,5,9,7,4,0,2,6}; new HeapSort(arr).sort(); System.out.println(Arrays.toString(arr));}}
五、归并排序(Merging Sort)
归并是指将两个有序子序列合并成一个新的有序子序列。设在初始序列里有n 个元素,归并排序的基本思想是:
将序列看成n个有序的子序列,每个序列长度为1,然后两两归并……重复下去,直到得到一个长度为n的有序序列。这是2-路归并排序。
如果每次将3个有序子序列合并为一个新的有序序列,则称为3-路归并排序。只不过2路完全可以满足内部排序的需要,
归并排序的时间代价并不依赖于待排序列数组的初始情况,也就是归并排序的最好、平均、最坏的时间复杂度都为O(nlogn),这一点比快排更好,而且归并排序是稳定的。当然,在平均情况下,快排最快~
归并排序的实现有递归和迭代两种方式:
/*递归方式*/ static void merge_sort_recursive(int[] arr, int[] result, int start, int end) {if (start >= end)return;int len = end - start, mid = (len >> 1) + start;int start1 = start, end1 = mid;int start2 = mid + 1, end2 = end;merge_sort_recursive(arr, result, start1, end1);merge_sort_recursive(arr, result, start2, end2);int k = start;while (start1 <= end1 && start2 <= end2)result[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];while (start1 <= end1)result[k++] = arr[start1++];while (start2 <= end2)result[k++] = arr[start2++];for (k = start; k <= end; k++)arr[k] = result[k]; } public static void merge_sort(int[] arr) {int len = arr.length;int[] result = new int[len];merge_sort_recursive(arr, result, 0, len - 1); }/*迭代方式*/ public static void merge_sort(int[] arr) {int len = arr.length;int[] result = new int[len];int block, start;// 原版代码的迭代次数少了一次,没有考虑到奇数列数组的情况for(block = 1; block < len; block *= 2) {for(start = 0; start <len; start += 2 * block) {int low = start;int mid = (start + block) < len ? (start + block) : len;int high = (start + 2 * block) < len ? (start + 2 * block) : len;//两个块的起始下标及结束下标int start1 = low, end1 = mid;int start2 = mid, end2 = high;//开始对两个block进行归并排序while (start1 < end1 && start2 < end2) {result[low++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];}while(start1 < end1) {result[low++] = arr[start1++];}while(start2 < end2) {result[low++] = arr[start2++];}}int[] temp = arr;arr = result;result = temp;}result = arr; }
六、内部排序方法讨论
关于排序算法的稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj ,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。
平均时间性能快排最优,但最坏情况下,快排不如堆排序和归并排序。n较大时,归并排序所需时间较堆排序较少,但它所需的辅助存储量最多。
当序列中的记录基本有序或n较小时,直接插入排序是最佳的排序方法,因此常将它与快排、归并排序结合一起使用。
转载于:https://www.cnblogs.com/bigbigbigo/p/8616873.html
数据结构(六) 排序相关推荐
- 【图解数据结构】排序全面总结(一)
目录 一.前言 学习目标: 二.基本概念 1.排序 2.排序方法的稳定性 3.内部和外部排序 三.插入类排序 1.直接插入排序 2.折半插入排序 3.希尔排序 四.交换类排序 1.冒泡排序 2.快速排 ...
- 【数据结构】排序算法及优化整理
排序算法 排序算法 选择排序 Selection Sort 插入排序 Insertion Sort 归并算法 Merge Sort 快速排序 Quick Sort 堆排序 Heap Sort 二叉堆的 ...
- 【数据结构】排序相关题目及各种排序方法的总结
[数据结构之排序] 常用的排序方法有:直接插入排序.希尔排序.冒泡排序.快速排序.简单选择排序.树形选择排序.堆排序.归并排序.基数排序 提示:如有不理解的知识点,请看B站最好的数据结构老师王卓老师的 ...
- 鸡尾酒排序算法c语言,[golang] 数据结构-鸡尾酒排序
吐个槽 又是一个不正经取名的排序算法.真要说和鸡尾酒间的关系,大概就是想喝到鸡尾酒(得到排序好的队列)就要摇晃酒杯让不同的成分混合均匀(向两个方向冒泡排序) 原理 鸡尾酒排序(Cocktail Sor ...
- 数据结构------选择排序
数据结构------选择排序 原理:参考趣学数据结构 代码: #include<stdio.h> #include<stdlib.h> void simpleSelectSor ...
- 数据结构-王道-排序
排序 关于排序算法的视频演示 直接插入排序 从上面的插入排序思想中,不难得到一种简单直接的插入排序算法.假设待排序表在某次过程中属于这种情况. |有序序列\(L[1\ldots i-1]\)|L(i) ...
- 希尔排序python 简书_数据结构_排序_直接插入+希尔排序
数据结构_排序_直接插入排序+希尔排序 其实主要是为了讲述希尔排序,不过插入排序是希尔排序的基础,因此先来讲直接插入排序. 一.直接插入排序 1.原理 下标 0 1 2 3 4 5 6 7 8 -- ...
- 常用数据结构以及数据结构的排序算法
2019独角兽企业重金招聘Python工程师标准>>> 数组 (Array) 在程序设计中,为了处理方便, 把具有相同类型的若干 变量按有序的形式组织起来.这些按序排列的同类数据元素 ...
- 【数据结构(C语言)】数据结构-内部排序
内部排序 文章目录 内部排序 一.概述 (1)排序定义 (2)稳定性 (3)内部排序和外部排序 (4)两种基本操作 (5)数据类型定义 二.分类 (1)插入排序 (2)交换排序 (3)选择排序 (4) ...
- 数据结构----各种排序方法总结
数据结构----各种排序方法总结 提示:数据结构中的排序方法包括插入排序 文章目录 数据结构----各种排序方法总结 一.插入排序 1.直接插入排序 2.折半插入排序 3.希尔插入排序 二.交换排序 ...
最新文章
- 序列化在反序列化时无法加载程序集的问题
- java-数组排序--冒泡排序、鸡尾酒排序、地精排序
- 关于graphviz绘制的点(dot)图在显示时中文乱码问题的解决方法(亲测)
- sqli-labs 30到65关
- Cross-Validation(交叉验证)详解
- nohup: 忽略输入重定向错误到标准输出端_Linux 重定向与管道
- 第四大运营商正式申请5G牌照,三大运营商如何应对?
- 【软件架构】软件架构设计常用概念、原则与思想
- 父窗体中弹出新窗体,然后获取弹出窗体的返回值。
- html dom子节点,HTML DOM 节点
- C Tricks(十八)—— 整数绝对值的实现
- 单样本t检验之汽车引擎排放是否达标
- 色彩构成与搭配——人对色彩的感觉
- excel如何调整日期格式的方法
- iPhone预计移除SIM卡槽,用户需通过eSIM激活网络
- PDF删除水印怎么操作
- ChatGPT有效提问技巧
- Afnetworking访问遇到3840_错误
- BT和eMule下载协议的比较和分析
- options请求是什么?
热门文章
- NOIP练习赛题目6
- UI: 自定义 UISegmentedControl
- MAGENTO MOUDLE WEB FRONTEND
- TFS 2010 备份和恢复的完整解决方案
- AAAI2021 大运动“视频超分辨”中的对偶子网与多阶通信上采样方案
- CVPR2021 FGVC8植物病理识别挑战赛,冠军方案解读
- 打遍天下无敌手,却说它只是个baseline!多目标跟踪FairMOT的烦恼
- 谷歌官方推出 TensorFlow 中文视频:机器学习从零到一(系列之二)
- fiddler汉化版可以改成英文吗_可以把推拉门改成平开窗吗?推拉门和平开窗哪个更好?...
- 计算机视觉论文-2021-07-08