参考文章:
十大经典排序算法(动图演示)
十大经典排序算法
堆排序
#0.排序算法分类
- 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
- 非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。
- 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
- 不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
#1. 交换排序
##1.1 冒泡排序
步骤:
- 比较相邻的元素。如果第一个比第二个大,就交换它们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 重复上述步骤,直到排序完成。
动图演示:
代码实现:
public class BubbleSort{
public void bubbleSort(int[] array){
int length=array.length;
for(int i=0;i<length-1;i++){
for(int j=0;j<length-1;j++){
if(array[j+1]<array[j]){//比较
int tmp=array[j+1];//交换
array[j+1]=array[j];
array[j]=tmp;
}//if
}//for
}//for
return;
}
}
##1.2 快速排序
思想:
通过一趟排序将待排序记录分隔为独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
步骤:
- 从数列中挑出一个元素,称为 “基准”(pivot)。
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
动图演示:
代码实现:
public class QuickSort{
public void quickSort(int[] array, int left, int right){
if(left<right){
int mid=partition(array, left, right);
quickSort(array, left, mid-1);
quickSort(array, mid+1, right);
}
return;
}
private int partition(int[] array, int left, int right){
int pivot=array[left];
while(left<right){
while(left<right&&array[right]>=pivot)//right向左找到第一个小于pivot的元素
right--;
array[left]=array[right];
left++;
while(left<right&&array[left]<=pivot)//left向右找到第一个大于pivot的元素
left++;
array[right]=array[left];
right--;
}
//此时left=right=mid
array[left]=pivot;
return left;
}
}
#2. 插入排序
##2.1 简单插入排序
- 从第一个元素开始,该元素可以认为已经被排序。
- 取出下一个元素,在已经排序的元素序列中从后向前扫描。
- 如果该元素(已排序)大于新元素,将该元素移到下一位置。
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置。
- 将新元素插入到该位置后。
动图演示:
代码实现:
public class InsertSort{
public void insertSort(int[] array){
for(int i=1; i<array.length; i++){
int j=i-1;
int tmp=array[I];
while(j>=0&&array[j]>tmp){
array[j+1]=array[j];
j--;
}//while
array[j+1]=tmp;
}//for
return;
}
}
##2.2 希尔排序
- 无论是插入排序还是冒泡排序,如果数组的最大值刚好是在第一位,要将它挪到正确的位置就需要 n-1次移动。也就是说,原数组的一个元素如果距离它正确的位置很远的话,则需要与相邻元素交换很多次才能到达正确的位置,这样是相对比较花时间了。
- 希尔排序的思想是采用插入排序的方法,先让数组中任意间隔为 h 的元素有序,刚开始 h 的大小可以是 h = n / 2,接着让 h = n / 4,让 h 一直缩小,当 h = 1 时,也就是此时数组中任意间隔为1的元素有序,此时的数组就是有序的了。
示意图:
代码实现:
public class ShellSort{
public void shellSort(int[] array){
int length=array.length;
if(length<2)
return;
for(int h=n/2;h>0;h/=2){//缩小增量
//分组为[i,i+h,...,i+jh,...,<n]
for(int i=0;i<h;i++){//对每组
for(j=1;i+j*h<n;j++){//每组内进行插入排序
int index=i+j*h;
int tmp=array[index];
int k=index-h;
while(k>=i&&tmp<array[k]){
array[k+h]=array[k];
k-=h;
}//while
array[k+h]=tmp;
}//for3
}//for2
}//for1
}
}
#3.选择排序
##3.1 简单选择排序
思想:
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
步骤:
- 初始状态:无序区为R[1…n],有序区为空。
- 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R(i…n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1…i]和R[i+1…n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。
- n-1趟结束,数组有序化了。
动图演示:
代码实现:
public class SelectionSort{
public void selectionSort(int[] array){
int length=array.length;
for(int i=0;i<length-1;i++){
int min=I;
for(int j=i+1; j<length; j++){
if(array[j]<array[min]){
min=j;
}//if
}//for2
int tmp=array[min];
array[min]=array[I];
array[i]=tmp;
}//for1
return;
}
}
##3.2 堆排序
-
堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点,又称为大顶堆(小顶堆)。
-
把这种逻辑结构映射到数组中,如下:
9 | 5 | 8 | 2 | 3 | 4 | 7 | 1 |
---|
1 | 3 | 5 | 4 | 2 | 8 | 9 | 7 |
---|
- 这两个数组逻辑上就分别是一个堆。从这里我们可以得到堆数组的以下性质:
- 对于大顶堆:arr[i]>=arr[2i+1] && arr[i]>=arr[2i+2]
- 对于小顶堆:arr[i]<=arr[2i+1] && arr[i]<=arr[2i+2]
步骤(升序-大顶堆):
- 将无序序列构建成一个大顶堆。
首先我们将现有的无需序列堪称一个堆结构,即一个没有规则的二叉树,将序列里的值从上往下、从左往右依次填到二叉树中。
然后根据大顶堆的性质,从下往上、从右往左遍历所有非叶子结点,调整他们的父子关系。第一个叶子结点的索引为array.length/2,最后一个非叶子结点的索引为array.length/2-1。 - 排序序列,将堆顶的元素值和尾部的元素交换。然后通过调整新的根结点以及其调整后影响的子节点,将剩余的的元素重新构建大顶堆。
- 重复步骤2,直到序列排序完成。
动图演示:
代码实现:
public class HeapSort{
public void heapSort(int[] array){
int length=array.length;
//构建大顶堆
for(int i=length/2-1;i>=0;i--)//调整每个非叶子节点和子节点的关系
downAdjust(array, i, length);
for(int I=length-1;i>0;i--){//交换根节点和末尾节点,调整剩下元素
int tmp=array[0];
array[0]=array[I];
array[I]=tmp;
downAdjust(array,0,i);
}
return;
}
private void downAdjust(int[] array, int index, int n){//调整非叶子结点array[index]和它的子节点的关系//堆结构为array[0:n]
int tmp=array[index];
int child=index*2+1;
if(tmp>=array[child]){
if(child+1>=n||tmp>=array[child+1])
return;//符合大顶堆的性质返回
}//if
else if(child+1<n&&array[child+1]>array[child])//右孩子比左孩子大
child++;//将待上浮结点定位到右子节点
array[index]=array[child];
array[child]=tmp;
if(child*2+1<n)
downAdjust(array, child,n);
}
}
#4. 归并排序
步骤:
- 把长度为n的输入序列分成两个长度为n/2的子序列。
- 对这两个子序列分别采用归并排序。
- 将两个排序好的子序列合并成一个最终的排序序列。
动图演示: