概述
排序(Sort)是将一组数据按照一定的规则进行排列的,一般按递增或递减的顺序来进行排列。排序算法是一种最基本的算法。排序虽然看似是一个很简单的问题,但是在实际的应用场合往往面临一些困难。这是因为实际应用中的数据量往往很庞大,这种算法的效率和排序的速度就是一个很大的问题。我们往往需要寻找一个高效的排序算法,因此便演变出了多种排序算法。
基本排序
交换排序
冒泡排序
冒泡排序算法通过多次比较和交换来实现排序,其排序流程如下:
(1)对数组中的各数据,依次比较相邻的两个元素的大小。
(2)如果前面的数据大于后面的数据,就交换这两个数据。经过第一轮的多次排序后,便可把最小的数据排好。
(3)再用同样的方法把剩下的数据逐个进行比较,最好便可按照从小到大的顺序排好数组各数据的顺序。
整个排序过程可以形象地类似于水泡的浮起过程,故因此而得名。冒泡排序算法在对N个数据进行排序时,无论原数据有无顺序,都需要进行N-1步的中间排序。这种排序方法思路简单直观,但是缺点就是执行的步骤有点长,效率不是很高。
一种改进的方法,就是在每次中间排序之后,比较一下数据是否已经按照顺序排列完成。如果排列完成则退出排序过程,否则便继续进行冒泡排序。这样,对于数据比较有规则的,可以加速算法的执行过程。
java
static final int SIZE=10;
public static void main(String[] args) {
int[] shuzu=new int[SIZE];
for(int i = 0; i < SIZE; i++) //初始化数组
{
shuzu[i]=(int)(100 + Math.random() * (100 + 1));
}
System.out.println("排序前的数组为:"); //输出排序前的数组
for(int i = 0; i < SIZE; i++)
{
System.out.print(shuzu[i] + " ");
}
System.out.println("");
bubbleSort(shuzu); //排序操作
System.out.println("排序后的数组为:");
for(int i = 0;i < SIZE; i++)
{
System.out.print(shuzu[i] + " "); //输出排序后的数组
}
System.out.println("");
}
public static void bubbleSort(int[] a) { //冒泡排序算法
int temp;
for (int i = 1; i < a.length; i++) {
//将相邻两个数进行比较,较大的数往后冒泡
for (int j = 0; j < a.length - i; j++) {
if (a[j] > a[j + 1]) {
//交换相邻两个数
temp=a[j];
a[j]=a[j+1];
a[j+1]=temp;
}
}
System.out.print("第"+i+"步排序结果:"); //输出每步排序的结果
for(int k = 0;k < a.length; k++)
{
System.out.print(" " + a[k]); // 输出
}
System.out.println("");
}
}
快速排序
快速排序(Quick Sort)法和冒泡排序算法类似,都是基于交换排序思想的。快速排序对冒泡排序法进行了改进,从而具有更高的执行效率。
快速排序算法通过多次比较和交换来实现排序,其排序流程如下:
(1)首先设定一个分界值,通过该分界值将数组分成左右两部分。
(2)将大于等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于等于分界值,而右边部分中各元素都大于等于分界值。
(3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样将左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
(4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。
java
static final int SIZE=18;
public static void main(String[] args)
{
int[] shuzu=new int[SIZE];
for(int i = 0; i < SIZE; i++)
{
shuzu[i] = (int)(100 + Math.random()*(100 + 1)); //初始化数组
}
System.out.print("排序前的数组为:\n"); //输出排序前的数组
for(int i = 0; i < SIZE; i++)
{
System.out.print(shuzu[i]+" ");
}
System.out.print("\n");
quickSort(shuzu, 0, SIZE-1); //排序操作
System.out.print("排序后的数组为:\n");
for(int i = 0; i < SIZE; i++)
{
System.out.print(shuzu[i] + " "); //输出排序后的数组
}
System.out.print("\n");
}
static void quickSort(int[] arr, int left, int right) //快速排序算法
{
int f, t;
int rtemp, ltemp;
ltemp = left;
rtemp = right;
f = arr[(left + right) / 2]; //分界值
while(ltemp < rtemp)
{
while(arr[ltemp] < f)
{
++ltemp;
}
while(arr[rtemp] > f)
{
--rtemp;
}
if(ltemp <= rtemp)
{
t = arr[ltemp];
arr[ltemp] = arr[rtemp];
arr[rtemp] = t;
--rtemp;
++ltemp;
}
}
if(ltemp == rtemp)
{
ltemp++;
}
if(left < rtemp)
{
quickSort(arr, left, ltemp - 1); //递归调用
}
if(ltemp < right)
{
quickSort(arr, rtemp + 1, right); //递归调用
}
}
选择排序
选择排序
选择排序(Selection Sort)也是比较简单的排序算法,思路也比较直观。选择排序算法在每一步中选取最小值来重新排列,从而达到排序的目的。
选择排序算法通过选择和交换来实现排序,其排序流程如下:
(1)首先从原始数组中选择最小的1个数据,将其和位于第1个位置的数据交换。
(2)接着从剩下的n-1个数据中选择次小的1个元素,将其和第2个位置的数据交换。
(3)然后,这样不断重复,直到最后两个数据完成交换。至此,便完成了对原始数组的从小到大的排序。
java
static final int SIZE=10;
public static void main(String[] args)
{
int[] shuzu=new int[SIZE];
for(int i = 0; i < SIZE; i++) //初始化数组
{
shuzu[i]=(int)(100 + Math.random() * ( 100 + 1 ));
}
System.out.println("排序前的数组为:"); //输出排序前的数组
for(int i = 0; i < SIZE; i++)
{
System.out.print(shuzu[i] + " ");
}
System.out.println("");
selectSort(shuzu); //选择排序操作
System.out.println("排序后的数组为:");
for(int i = 0; i < SIZE; i++)
{
System.out.print(shuzu[i] + " "); //输出排序后的数组
}
System.out.println("");
}
public static void selectSort(int[] a) //选择排序操作
{
int index, temp;
for (int i = 0; i < a.length-1; i++)
{
index = i;
for (int j = i + 1; j < a.length; j++) //找出最小值下标
{
if (a[j] < a[index])
{
index = j;
}
}
//交换两个数
if(index != i)
{
temp = a[i];
a[i] = a[index];
a[index] = temp;
}
System.out.print("第"+i+"步排序结果:"); //输出每步排序的结果
for(int h = 0; h < a.length; h++)
{
System.out.print(" " + a[h]); //输出
}
System.out.println("");
}
}
堆排序
堆排序的关键是首先构造堆结构。那么什么是堆结构呢?堆结构是一种树结构,准确地说是一个完全二叉树。在这个树中每个结点对应于原始数据的一个记录,并且每个结点应满足以下条件:
- 如果按照从小到大的顺序排序,要求非叶结点的数据要大于或等于其左、右子结点的数据。
- 如果按照从大到小的顺序排序,要求非叶结点的数据要小于或等于其左、右子结点的数据。
构造堆结构就是把原始的无序数据按前面堆结构的定义进行调整。首先,需要将原始的无序数据放置到一个完全二叉树的各个结点中;然后,由完全二叉树的下层向上层逐层进行父子结点的数据进行比较,使父结点的数据大于子结点的数据。这里需要使用“筛”运算进行结点数据的调整,直到使所有结点最后满足堆结构的条件为止。筛运行主要针对非叶结点进行调整。
java
static final int SIZE=10;
public static void main(String[] args)
{
int[] shuzu=new int[SIZE];
int i;
for(i=0;i<SIZE;i++)
{
shuzu[i]=(int)(100+Math.random()*(100+1)); //初始化数组
}
System.out.print("排序前的数组为:\n"); //输出排序前的数组
for(i=0;i<SIZE;i++)
{
System.out.print(shuzu[i]+" ");
}
System.out.print("\n");
heapSort(shuzu,SIZE); //排序操作
System.out.print("排序后的数组为:\n");
for(i=0;i<SIZE;i++)
{
System.out.print(shuzu[i]+" "); //输出排序后的数组
}
System.out.print("\n");
}
static void heapSort(int a[],int n) //堆排序
{
int t;
for(int i=n/2-1;i>=0;i--) //将a[0,n-1]建成大根堆
{
int j=2*i+1 ; //第i个结点有子树
if((j+1)<n)
{
if(a[j]<a[j+1]) //左子树小于右子树,则需要比较右子树
j++; //序号增加1,指向右子树
}
if(a[i]<a[j]) //比较i与j为序号的数据
{
t=a[i]; //交换数据
a[i]=a[j];
a[j]=t;
}
}
for(int i=n-1;i>0;i--)
{
t=a[0]; //与第i个记录交换
a[0] =a[i];
a[i] =t;
for(int k=i/2-1;k>=0;k--) //将a[0,i]建成大根堆
{
int j=2*k+1 ; //第k个结点有子树
if((j+1)<i) //判断右子树存在或可比
{
if(a[j]<a[j+1]) //左子树小于右子树,则需要比较右子树
j++; //序号增加1,指向右子树
}
if(a[k]<a[j]) //比较i与j为序号的数据
{
t=a[k]; //交换数据
a[k]=a[j];
a[j]=t;
}
}
}
}
插入排序
插入排序
插入排序(Insertion Sort)法通过对未排序的数据执行逐个插入至合适的位置而完成排序工作。插入排序算法的思路也是比较简单的,使用比较多。
插入排序算法通过比较和插入来实现排序,其排序流程如下:
(1)首先对数组的前两个数据进行从小到大的排序。
(2)接着将第3个数据与排好序的两个数据比较,将第3个数据插入到合适的位置。
(3)然后,将第4个数据插入到已排好序的前3个数据中。
(4)不断重复上述过程,直到把最后一个数据插入合适的位置。最后,便完成了对原始数组从小到大的排序。
java
static final int SIZE=10;
public static void main(String[] args)
{
int[] shuzu=new int[SIZE];
for(int i = 0; i < SIZE; i++)
{
shuzu[i]=(int)(100 + Math.random() * (100 + 1)); //初始化数组
}
System.out.println("排序前的数组为:"); //输出排序前的数组
for(int i = 0; i < SIZE; i++)
{
System.out.print(shuzu[i] + " ");
}
System.out.println("");
insertionSort(shuzu); //排序操作
System.out.println("排序后的数组为:");
for(int i = 0; i < SIZE; i++)
{
System.out.print(shuzu[i] + " "); //输出排序后的数组
}
System.out.println("");
}
static void insertionSort(int[] a) //插入排序
{
int temp;
for (int i = 1; i < a.length; i++)
{
temp = a[i];
int j = i - 1;
while(j >= 0 && temp < a[j])
{
a[j+1] = a[j];
j--;
}
a[j+1] = temp;
System.out.print("第" + i + "步排序结果:"); //输出每步排序的结果
for(int h = 0;h < a.length; h++)
{
System.out.print(" " + a[h]); //输出
}
System.out.println("");
}
}
Shell排序
冒泡排序算法、选择排序算法和插入排序算法,虽然思路比较直观,但是排序的效率比较低。对于大量的数据需要排序的时候,我们往往需要寻求其他更为高效的排序算法。Shell排序算法便是其中一种。
Shell排序算法严格来说基于插入排序的思想,其又称为希尔排序或者缩小增量排序。Shell排序算法的排序流程如下:
(1)将有n个元素的数组分成n/2个数字序列,第1个数据和第n/2+1个数据为一对,……
(2)一次循环使每一个序列对排好顺序
(3)然后,再变为n/4个序列,再次排序
(4)不断重复上述过程,随着序列减少最后变为一个,也就完成了整个排序
java
static final int SIZE=10;
public static void main(String[] args)
{
int[] shuzu=new int[SIZE];
for(int i = 0; i < SIZE; i++)
{
shuzu[i]=(int)(100 + Math.random() * (100 + 1)); //初始化数组
}
System.out.println("排序前的数组为:"); //输出排序前的数组
for(int i = 0; i < SIZE; i++)
{
System.out.print(shuzu[i] + " ");
}
System.out.println("");
shellSort(shuzu); //排序操作
System.out.println("排序后的数组为:");
for(int i = 0; i < SIZE; i++)
{
System.out.print(shuzu[i] + " "); //输出排序后的数组
}
System.out.println("");
}
static void shellSort(int[] a) //Shell排序
{
int temp;
int x=0;
for(int r = a.length / 2; r >= 1; r /= 2) //划组排序
{
for(int i = r; i < a.length; i++)
{
temp = a[i];
int j = i - r;
while(j >= 0 && temp < a[j])
{
a[j + r] = a[j];
j -= r;
}
a[j + r] = temp;
}
x++;
System.out.print("第" + x + "步排序结果:"); //输出每步排序的结果
for(int h=0; h < a.length; h++)
{
System.out.print(" " + a[h]); //输出
}
System.out.println("");
}
}
合并排序
一个待排序的原始数据序列进行合并排序的基本思路是,首先将含有n个结点的待排序数据列看做有n个长度为1的有序子表组成,将他们依次两两合并,得到长度为2的若干有序子表;然后,再对这些子表进行两两合并,得到长度为4的若干有序子表;……,重复上述过程,一直重复到最后的子表长度为n,从而完成排序过程。
排序算法的效率
排序算法有很多种,每种算法都有其优缺点,可适应不同的场合。速度是决定排序算法的最主要因素,其是排序效率的一个重要指标。一般来说,我们可从以下几方面判断一个排序算法的优劣:
- 计算的复杂度:为了全面考虑,往往从最差,平均和最好三种情况进行评价。
- 系统资源的占用:主要包括内存以及其他资源的占用。一个好的排序算法应用占用少的内存资源。在以上排序算法中,大部分排序算法都只需要使用1个元素的存储单元,用来交换数据。而合并排序算法需使用与原始序列一样长的n个元素的存储单元,用来保存多遍合并操作。因此,合并排序算法的系统资源占用要大。
对于计算的复杂度,一般依据排序数据量的大小n来度量,主要表征了算法执行速度。这是算法优劣的一个重要指标。 - 冒泡排序法,平均速度为O(n^2),最坏情况下的速度为O(n^2);
- 快速排序法,平均速度为O(nlogn),最坏情况下的速度为O(n^2);
- 选择排序法,平均速度为O(n^2),最坏情况下的速度为O(n^2);
- 堆排序法,平均速度为O(nlogn),最坏情况下的速度为O(nlogn);
- 插入排序法,平均速度为O(n^2),最坏情况下的速度为O(n^2);
- Shell排序法,平均速度为O(n^3/2),最坏情况下的速度为O(n^2);
- 合并排序法,平均速度为O(nlogn),最坏情况下的速度为O(nlogn);
在排序算法中还有一个特殊的概念,那就是稳定排序算法。稳定排序算法主要是依照相等的关键字维持记录的相对次序来进行排序。通俗地讲,当有两个有相等关键字的数据D1和D2,在待排序的数据中D1出现在D2之前,在排序过后的数据中D1也在D2之前,那么这就是一个稳定排序算法。
那么在上述算法中,冒泡排序法、插入排序法和合并排序法都是稳定排序算法,而选择排序法、Shell排序法、快速排序法和堆排序法都不是稳定排序算法。
其实,没有某一种排序算法是绝对好的,不同的排序算法各有优劣。在实际应用中,需要根据实际的问题来选择合适的排序算法。如果数据量n较小的时候,可采用插入排序法或选择排序法;当数据量n较大的时候,则采用时间复杂度为O(nlogn)的排序方法,如快速排序、堆排序或合并排序。如果待排序的原始数据呈随机分布,那么快速排序算法的平均时间最短。