一、介绍
排序是我们工作中经常碰到的一件事,基本每个项目都涉及到排序运算。一般,排序操作在数据处理过程中要话费许多时间。为了提高计算机的运行效率,人们提出不断改进各种各样的排序算法,而这些算法也从不同角度展示了算法设计的某些重要原则和技巧。
排序就是将一组对象按照规定的次序重新排列的过程,排序往往是为检索服务的。例如,学生档案系统里面的学生成绩信息就是按照学号、年龄或入学成绩等排序后的结果,在排好序的结果里面检索学生成绩信息效率就高很多了。如下表1-1就是按照年龄升序排列的学生信息列表。
表1-1 按学号升序排序学生成绩表
学号 |
姓名 |
性别 |
年龄 |
成绩 |
20060206 |
吴三 |
男 |
18 |
523 |
20060207 |
李四 |
男 |
19 |
450 |
20060208 |
王五 |
女 |
18 |
470 |
20060209 |
赵柳 |
女 |
17 |
485.5 |
表1-2 按学号升序排序学生成绩表
学号 |
姓名 |
性别 |
年龄 |
成绩 |
20060209 |
赵柳 |
女 |
17 |
485.5 |
20060208 |
王五 |
女 |
18 |
470 |
20060206 |
吴三 |
男 |
18 |
523 |
20060207 |
李四 |
男 |
19 |
450 |
可以看出学号为20060206和20060208的两位同学年龄都为18岁,表1-2按照年龄升序后相对位置发生变化了。对于这种相同键值的两个记录在排序前后相对位置变化情况是排序算法研究中经常关注的一个问题,该问题成为排序算法的稳定性。需要注意的是:稳定性是算法本身的特性,与数据无关。
排序算法分为内部排序和外部排序。内部排序是将待排序的记录全部放在计算机内存中进行的排序过程,其排序实现方法很多,主要有插入排序、选择排序、交换排序和归并排序等;如果待排序的记录数量很多,内存不能存储全部记录,需要对外存进行访问排序的过程,本次总结的是内部排序。
评价一个算法的优劣,通常是用时间复杂度和空间复杂度这两个指标。由于排序算法的多样性,很难确定一种公认的最好方法,应当根据实际应用选择不同的方法。例如,当待排序序列已基本有序时,插入排序和交换排序比较有效;当待排记录数量较多时,选择排序有效。
二、比较
对20000条记录进行排序:
名称 耗时(毫秒) 插入排序(直接插入) 1469 交换排序(冒泡排序) 5828 交换排序(快速排序) 1078 选择排序(直接选择排序) 2796 选择排序(堆排序) 16 归并排序(二路归并) 0 |
可以看出,在记录较多的情况下归并排序是效率最高的,其次是堆排序,再次到快速排序,而算法相对简单的排序方法反而效率很低,尤其是冒泡排序效率最低。
三、内部排序
(一)插入排序
它的基本思想是将记录分为有序区和无序区,将无序区中的记录依次插入到有序区中,并保持有序。常用的插入排序方法有直接插入排序、拆半插入排序、表插入排序和希尔排序等。如果待排记录较少且基本有序的话,可以考虑使用插入排序算法。
1.直接插入排序
根据插入排序算法的基本思想,图3-1便是直接插入排序算法的过程,中括号内是有序区,中括号外面是无序区。从无序区的前面或后面依次取出一个元素,在有序区找到一个合适的位置插入。这里所说的合适位置要看是升序还是降序。
图3-1 直接插入排序示意图
/** * 插入排序——直接插入排序 * @param arrays 待排序序列 * @dateTime 2014-2-16 上午09:15:08 * @author wst * @return void */ public static void insertSort(int[] arrays){ int i,j,n=arrays.length-1; for(i=1;i<=n;i++){//第一个元素无须排序 int temp=arrays[i];//当前比较元素 //在已排好序的序列里找到一个合适的位置存放temp for(j=i;j>0&&temp<arrays[j-1];j--){ arrays[j]=arrays[j-1];//将大的元素后移 } arrays[j]=temp; } }
(二)交换排序
交换排序的基本思想是两两比较待排序的记录,当记录之间值出现逆序时,则交换两个记录。常用的交换排序有冒泡排序和快速排序等。
1.冒泡排序
冒泡排序的过程是首先将第一个记录和第二个记录进行比较,若为逆序,则将这两个记录交换,然后继续比较第二个和第三个记录。依次类推,直到完成第n-1个记录和第n个记录比较交换为止。上述过程称为第一趟起泡,其结果使最大的记录已到了第n个位置上。重复以上起泡过程,当在一趟起泡过程中没有进行记录交换的操作时,整个排序过程终止。因为每趟起泡都有一个最大的记录沉到水底,所以整个排序过程最多需要进行n-1趟起泡。例如,图3-2是冒泡排序过程的示意图,图中只给出了第一趟起泡的过程,后面直接给出各趟起泡结果。在起泡结果里面,有黄色背景的记录已经排好序。
图3-2 冒泡排序示意图
从图3-2中可以看出,当第4趟起泡结束后已经没有需要交换的气泡了,第5、6趟起泡纯属多余,因为此时序列已经有序,因此在第4趟起泡结束后可以终止循环。起泡次数=4<n-1,符合上面的推理。
/** * 交换排序——冒泡排序 * @param arrays * @dateTime 2014-1-25 上午10:41:58 * @author wst * @return void */ public static void upBubbleSort(int[] arrays){ int i,j,n=arrays.length; for(i=0;i<n-1;i++){ boolean endsort=true;//是否需要交换 for(j=0;j<n-i-1;j++){ if(arrays[j]>arrays[j+1]){ int temp=arrays[j]; arrays[j]=arrays[j+1]; arrays[j+1]=temp; endsort = false; } } if(endsort){ break; } } }
2.快速排序
快速排序实质上是对冒泡排序的一种改进。其基本思想是:以选定的记录为基准,将待排序表划分为左、右两段,其中左边所有记录小于等于右边所有记录,然后,对左、右两段记录分别进行快速排序。如下图3-3是快速排序的示意图,下图给出了第一趟快速排序的完整过程,后面相继只给出各趟排序结果。红色记录表示各趟记录划分出的关键字,中括号内的记录表示根据关键字划分的无序区。
图3-3 快速排序示意图
/** * 交换排序——快速排序 * 不稳定。O(nlog2n)~O(n2)。 * 待排序列基本有序时效率低 * @param arrays * @param low * @param high * @dateTime 2014-2-16 上午09:34:24 * @author wst * @return int[] */ public static int[] quickSort(int[] arrays,int low,int high){ if(low<high){ //划分区域,找出关键字 int temp=quickPartition(arrays,low,arrays.length-1); //在关键字左侧进行排序 quickSort(arrays,low,temp-1); //在关键字右侧进行排序 quickSort(arrays,temp+1,high); } return arrays; } /** * 对子序列进行一趟快速排序 * @param arrays 待排序序列 * @param low 当前排序序列的首指针 * @param high 当前排序序列的末指针 * @dateTime 2014-2-16 上午09:38:01 * @author wst * @return int */ private static int quickPartition(int[] arrays,int low,int high){ int x=arrays[low];//初始值 while(low<high){ while(low<high&&(arrays[high]>=x)){ high--;//从末端找出一个比x小的数 } //将找到的比x小的元素移到low位置 arrays[low]=arrays[high]; while(low<high&&(arrays[low]<=x)){ low++;//从首端找出一个比x大的数 } //将找到的比x大的元素移到high位置 arrays[high]=arrays[low]; } //一趟快速排序结束后,将x置入low位置 arrays[low]=x; return low; }
(三)选择排序
选择排序的基本思想:每次从待排序列中选取记录最小或最大的放到适当位置。常用的选择排序法有直接选择排序和堆排序法等。选择排序适合待排记录较大的情况。
1.直接选择排序
直接选择排序算法的基本思想:在待排序的无序区中选择最小(大)的记录,并将该记录放到有序区的最前(后)端。图3-4是直接选择排序过程的示意图。中括号内是有序区,每次从中括号外面选择出一个最小或最大的记录放到有序区。该算法简单容易实现。
图3-4 直接选择排序示意图
/** * 选择排序——直接选择 * 不稳定。O(n2)。待排记录较多效率非常低下 * @param arrays * @dateTime 2014-2-16 上午09:57:13 * @author wst * @return void */ public static void selectSort(int[] arrays){ int n=arrays.length,minIndex=0,temp=0; if(arrays==null||n==0){ return; } for(int i=0;i<n;i++){ //每一趟都选择出一个最小值 minIndex=i; //待排区的最小元素下标 for(int j=i+1;j<n;j++){ //在待排区中找出最小元素 if(arrays[j]<arrays[minIndex]){ minIndex=j; } } if(minIndex!=i){//将找到的最小元素置入有序区 temp=arrays[i]; arrays[i]=arrays[minIndex]; arrays[minIndex]=temp; } } }
2.堆排序
堆排序是利用堆来选择最小(大)记录。对直接选择排序的分析我们可以知道,在n个记录中选出最小值,至少要进行n-1次比较。然而继续在剩余的n-1个记录中选出次小记录是否一定要进行n-2次比较呢?若能利用前n-1次比较所得信息,是否可以减少以后各次选择中的比较次数呢?答案是肯定的。堆有最小堆和最大堆,简单定义如下:
序列{k1,k2,…,kn}满足
其中,i=1,2,…,n/2,则称这个n个记录的序列{k1,k2,…,kn}为最小堆(或最大堆)。根据上述定义可以知道,最小堆可以看成是一棵以k1为根的完全二叉树,在这课二叉树中,任一结点的值都不大于它的两个孩子的值(若孩子存在的话);最大堆可以看成是一棵以k1为根的完全二叉树,在这棵二叉树中,任一结点的值都不小于它的两个孩子的值(若存在孩子的话)。
由此可知,依次输出堆顶元素就可以得到升序或降序的序列。因此,实现堆排序需要解决两个问题:
(1)如何由一个初始序列建成一个堆?
(2)如何在输出堆顶元素之后调整剩余元素成为一个新堆?
如图3-5可以回答第一个问题,图3-6则回答了第二个问题。以下是由初始序列{65,88,55,34,93,28,18,40}建立堆和调整堆的全排序过程。
图3-5将初始序列建立一个堆
图3-6调整剩余元素成为一个新堆
/** * 堆排序 * @param a * @dateTime 2014-3-5 下午07:12:31 * @author wst * @return void */ public static void heapSort(int[] a){ int n=a.length; int temp=0; //由初始序列建立一个堆 for(int i=n/2;i>0;i--){ sift(a,i-1,n); } //输出堆顶元素,调整剩余元素成为一个新堆 for(int i=n-2;i>=0;i--){ temp=a[i+1]; a[i+1]=a[0]; a[0]=temp; sift(a,0,i+1); } } /** * 执行一次筛选 * @param a 待排序列 * @param k 根元素下标 * @param n 待排序序列长度 * @dateTime 2014-3-5 下午07:07:34 * @author wst * @return void */ public static void sift(int[] a,int k,int n){ int temp=a[k]; //根 int j=2*k+1; //左孩子 while(j<=n-1){ if(j<n-1&&a[j]>=a[j+1]){ j++; } if(temp<a[j]){ break; //筛选结束 } a[(j-1)/2]=a[j];//根与左孩子交换 j=2*j+1; //继续从左孩子筛选 } a[(j-1)/2]=temp; }
(四)归并排序
归并排序是将两个或两个以上的有序表合并成一个有序表。归并排序法有二路归并排序等。
1.二路归并
二路归并的基本思想:假设序列中有n个记录,可看成是n个有序的子序列,每个序列的长度为1。首先将每相邻的两个记录合并,得到[n/2]个较大的有序子序列,每个子序列包含2个记录,再将上述序列两两合并,得到[[n/2]/2]个有序子序列,如此反复,直至得到一个长度为n的有序序列为止,排序结束。如图3-7是二路归并排序过程的示意图。
图3-7二路归并排序示意图
/** * 归并排序——二路归并 * @param a * @param n * @dateTime 2014-3-4 下午09:15:21 * @author wst * @return void */ static void margeSort(int[] a,int n){ int[] b=new int[n+1]; int h=1; while(h<=n){ mergePass(a,b,h,n); h=2*h; mergePass(b,a,h,n); h=2*h; } } /** * 执行一次归并 * 在含有n个记录的序列a中, * 将长度各为h的相邻两个有序子序列合并为长度2h的一个有序序列 * @param a 待排序列 * @param b 合并后的序列 * @param h 子序列长度 * @param n 总长度 * @dateTime 2014-3-4 下午09:02:39 * @author wst * @return void */ static void mergePass(int[] a,int[] b,int h,int n){ int i=0; while(i<n-2*h+1){ merge(a,b,i,i+h-1,i+2*h-1); i+=2*h; } if(i+h-1<n){ merge(a,b,i,i+h-1,n); }else{ for(int t=i;t<=n;t++){ b[t]=a[t]; } } } /** * 有序序列的合并 * 将a[h],...,a[m]和a[m+1],...,a[n]两个有序序列合并为一个有序序列 * @param a 待排序序列 * @param b 合并后的序列 * @param h 子序列长度 * @param m 序列1的结束位置 * @param n 序列2的结束位置 * @dateTime 2014-3-4 下午09:07:38 * @author wst * @return void */ static void merge(int[] a,int[] b,int h,int m,int n){ int k=h,j=m+1;//序列1的起始位置和序列2的起始位置 while((h<=m)&&(j<=n)){ if(a[h]<a[j]){ b[k]=a[h]; h++; }else{ b[k]=a[j]; j++; } k++; } while(h<=m){//将a[h],...,a[m]剩余序列插入末尾 b[k]=a[h]; h++; k++; } while(j<=n){//将a[h],...,a[m]剩余序列插入末尾 b[k]=a[j]; j++; k++; } }
辅助类:
package com.wusongti.util; import java.util.Random; /** * * @Utils.java * @description 工具类 * @date 2014-1-21 * @time 下午07:14:26 * @author wst * */ public class Utils { /** * 产生n个不重复的随机数 * @param n 产生n个随机数 * @param start 起始值 * @dateTime 2014-1-21 下午07:13:41 * @author wst * @return int[] 返回不重复的n个随机数 */ public static int[] getRandomNumber(int n,int start){ int[] arrays=new int[n]; Random rand=new Random(); boolean flag=false; for(int i=0;i<n;i++){ int num; do{ flag=false; num=rand.nextInt(n)+start; for(int j=0;j<i;j++){ if(num==arrays[j]){ flag=true; break; } } }while(flag); arrays[i]=num; } return arrays; } /** * 打印数组 * @param arrays * @dateTime 2014-1-21 下午07:23:25 * @author wst * @return void */ public static void printSort(int[] arrays){ int n=arrays.length; for(int i=0;i<n;i++){ if(i%15==0){ System.out.println(); } System.out.print(arrays[i]+" "); } } }
四、心得
有总结才会有反思,有反思才会有提高!
个人邮箱:wusongti062@163.com
五、参考文献
郑诚.数据结构导论[M].外语教学与研究出版社,2012
相关推荐
本文将深入探讨“排序思想”及其在实际应用中可能遇到的问题和解决方案。 首先,我们来看看几种常见的排序算法: 1. 冒泡排序:冒泡排序是最基础的排序算法之一,通过不断交换相邻的逆序元素逐步实现排序。其主要...
文章《对数据结构课中几种排序思想教学的探讨》主要探讨了如何教授排序思想,尤其是在数据结构课程中。 插入排序是一种简单直观的排序方法。该排序方法的工作原理是通过构建有序序列,对于未排序数据,在已排序序列...
十大经典排序思想
Python 算法 15归并排序思想.mp4
《排序思想》PDF文档主要涵盖了计算机科学中关于排序算法的核心概念和实现方法。排序是数据处理和计算机科学的基础,它涉及到将一组数据按照特定的顺序排列。在这个文档中,我们将会深入探讨各种排序算法,包括它们...
它的基本思想是:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。 #### ...
希尔排序的核心思想是“增量序列”(或称为“间隔序列”),它决定了元素如何被分组。经典的增量序列是序列的一半,即每次将间隔减半,但这不是唯一的选择。希尔排序的性能与选择的增量序列有很大关系,最优的增量...
堆排序是一种树形选择排序,它的基本思想是利用完全二叉树的特性来实现排序。首先构造一个大顶堆或小顶堆,然后将堆顶元素与最后一个元素交换,此时最后一个元素就是最大或最小值。接着对剩余的元素重新调整为堆,...
理解归并排序的算法思想及其算法设计 本资源摘要信息将深入讨论归并排序的算法思想及其算法设计,涵盖了归并排序的定义、算法思路、实现过程、时间复杂度分析等方面的内容。同时,本资源摘要信息还将介绍基数排序...
https://blog.csdn.net/apple_50661801/article/details/125922092 源代码
标题中的“扑克牌排序算法”实际上是指一种基于选择排序思想的算法,它的目的是将一个无序的序列按照一定的规则(通常是最小到最大)进行排序。在这个特定的Java实现中,算法采用了一种直观的方式,每次从数组的末尾...
可实现两个计算机间通信 并实现快速排序算法 2分钟内可排1000万个数 数是随机产生的
#### 三、使用快速排序思想解决第K大元素问题 接下来,我们将探索如何使用快速排序的思想,在O(n)的时间复杂度内找到数组中的第K大元素。 **基本思路**: 1. **选择基准**:从数组中随机选择一个元素作为基准。 2...
冒泡排序基本思想和算法 冒泡排序是交换排序的一种基本思想,通过比较和交换记录的关键字,达到排序的目的。下面对冒泡排序的基本思想、算法和性能进行详细的分析。 冒泡排序基本思想 冒泡排序的基本思想是:两两...
思想:首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序序列末尾。以此类推,直到所有元素均排序完毕。 时间复杂度:最好情况下:交换 0 次...
桶排序的基本思想是假设输入数据服从均匀分布,通过将数据分到有限数量的桶里,每个桶再分别排序,最后把所有桶中的数据连接起来,就能得到整体的有序序列。 桶排序的核心步骤如下: 1. 初始化:确定待排序元素的...
### Java的八大排序基本思想及实例解读 #### 一、直接插入排序 **基本思想**: 直接插入排序的核心思想是在已排序序列的基础上逐步构建更大的已排序序列。具体来说,假设数组中的前`n-1`个元素已经按照升序排列好...
快速排序由于排序效率在同为O(N*logN)的几种排序方法中效率较高,因此经常被采用,再加上快速排序思想----分治法也确实实用,因此很多软件公司的笔试面试,包括像腾讯,微软等知名IT公司都喜欢考这个,还有大大小的...
自然归并排序是一种基于归并排序思想的高效排序算法。它利用了数组中存在的自然有序子序列来进行排序,相较于传统归并排序,其减少了不必要的比较和移动操作。 **特点**: - **效率高**:对于部分有序的数组,其...