学习目标:
目标:熟练运用Java所学知识
学习内容:
本文内容:详解七种排序:插入排序、希尔排序、冒泡排序,选择排序,快速排序,堆排序,归并排序
一:插入排序
1.1 插入排序的实现原理
- 首先创建一个数字序列
- 将数组分为两个部分,一个是已排序部分,一个是未排序部分(将第一个元素默认成是已排序部分)
- 进行排序时,每次取未排序部分的第一个元素下标记作bound,与已排序部分的最后一个元素下标记作cur,进行比较,如果ary[cur]>ary[bound],则将ary[cur]向后移动,继续将ary[cur-1]与ary[bound]比较,直到找到小于ary[bound]的元素,将ary[bound]放在该元素的后一个位置
1.2 插入排序实现代码
public static void insert(int[] ary){
int bound=1;
for(;bound<ary.length;bound++){
int temp=ary[bound];//未排序的第一个元素
int cur=bound-1;//已排序的最后一个元素
for(;cur>=0;cur--){
if(ary[cur]>temp){
ary[cur+1]=ary[cur];//将ary[cur]向后移动一位
}else{
//当前ary[cur]<temp,找到了该在的位置
break;
}
}
ary[cur+1]=temp;//将temp放在cur的下一位
}
}
1.3插入排序性能分析
稳定性:稳定排序
二、希尔排序
2.1 希尔排序实现原理
希尔排序就是插入排序的优化,希尔排序将数组进行分组比较,第一次分两组、第二次分四组、第三次分八组,一直进行下去,直到每一组只有一个元素为止。
每一次分组进行一次插入排序,最终将数组排好序
如下图:
相同颜色的数字为同一组元素,将同一组元素进行插入排序
2.2 希尔排序实现代码
public static void shell(int[] ary){
int length=ary.length;
int gap=length/2;//分组依据
while(gap>=1){
_shell(ary,gap);//排序
gap/=2;//更新分组
}
}
public static void _shell(int[] ary,int gap){
int bound=gap;
for(;bound<ary.length;bound++){
int temp=ary[bound];//每一组元素未排序的第一个元素(一直是未排序的第一个元素,bound++后,就是下一组未排序的第一个元素)
int cur=bound-gap;//该组已排序元素的最后一个元素
for(;cur>=0;cur-=gap){
if(ary[cur]>temp){
ary[cur+gap]=ary[cur];//在该组元素中,将ary[cur]向后移动一位
}else{
break;
}
}
ary[cur+gap]=temp;//在该组元素中,将temp放在cur的下一位
}
}
2.3希尔排序性能分析
稳定性:不稳定排序
三、冒泡排序
3.1 冒泡排序实现原理
动图:
解释:冒泡排序每次比较相邻两个元素,升序排序时将较大元素后移,最终将最大的元素放到最后一个位置,多次循环后可实现排序
3.2 冒泡排序实现代码
public static void bubbleSort(int[] ary){
//外层循环记录未排序区间与已排序区间的边界
for(int bound =0;bound< ary.length;bound++){
//内层循环对未排序区间进行一次循环比较,将最小元素放置区间第一个位置
for(int cur=ary.length-1; cur>bound;cur--){
//交换操作
if(ary[cur]<ary[cur-1]){
int temp=ary[cur];
ary[cur]=ary[cur-1];
ary[cur-1]=temp;
}
}
}
}
2.3 冒泡排序性能分析
稳定性:稳定排序
四、选择排序
4.1 选择排序的实现原理
选择排序的过程可以想象成打擂台,每次取未排序区间的第一个位置下标记作bound,将该位置元素与后面每个元素相比下标记作cur,第一次比较ary[bound],和ary[cur],如果ary[bound]<ary[cur]则不交换,否则交换两个位置元素,下一次比较ary[bound]和ary[cur],规则不变,最终得到一个升序序列
4.2 选择排序实现代码
public static void select(int[] ary){
//外层循环每次取未排序区间第一个元素
for(int bound=0;bound<ary.length;bound++){
//内层循环将bound位置元素与后序元素一次比较
for(int cur=bound+1;cur<ary.length;cur++){
//交换操作
if(ary[cur]<ary[bound]){
int temp=ary[cur];
ary[cur]=ary[bound];
ary[bound]=temp;
}
}
}
}
4.3选择排序性能分析
稳定性:不稳定排序
五、快速排序
5.1 快速排序实现原理
快速排序核心就是:
- 将一个元素赋值为pivot
- 使用双指针前后遍历,从前向后找到大于pivot的元素,从后向前找到小于pivot的元素,交换两个元素
- 直到前后指针重合,将重合位置元素与pivot交换;
- 然后递归的将pivot左侧区间元素与pivot右侧区间元素重复1-3步骤,就能完成快速排序
5.2 快速排序实现代码
public static void quickSort(int[] ary) {
_quickSort(ary,0,ary.length-1);
}
public static void _quickSort(int[] ary,int left,int right){
if(left>=right){
//退出条件
return ;
}
int index=partition(ary,left,right);//得到上次排序得到的pivot位置
_quickSort(ary,left,index-1);//递归排序index左边元素
_quickSort(ary,index+1,right);//递归排序index右边元素
}
public static int partition(int[] ary,int left,int right){
int l=left;
int r=right;
int pivot=ary[right];//将ary[right]记作pivot
while(l<r){
while(l<r&&ary[l]<=pivot){
l++;
}
while(l<r&&ary[r]>=pivot){
r--;
}
int temp=ary[l];
ary[l]=ary[r];
ary[r]=temp;
}
//将pivot和r与l相遇位置元素交换
int temp=ary[r];
ary[r]=ary[right];
ary[right]=temp;
return l;//返回相遇位置,既此时的pivot位置
}
5.3 快速排序性能分析
稳定性:不稳定排序
5.4快速排序的非递归版本
public static void quickByNoR(int[] ary){
Stack<Integer> stack=new Stack<>();
stack.push(0);
stack.push(ary.length-1);
while(!stack.isEmpty()){
int right=stack.pop();
int left=stack.pop();
if(right<=left){
continue;
}
int index=partition(ary,left,right);
stack.push(left);
stack.push(index-1);
stack.push(index+1);
stack.push(right);
}
}
六、堆排序
6.1 堆排序实现原理
先来让我们了解一下堆的原理:
堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:
- 堆中某个结点的值总是不大于或不小于其父结点的值;
- 堆总是一棵完全二叉树。
将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆。
若将和此次序列对应的一维数组(即以一维数组作此序列的存储结构)看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有非终端结点的值均不大于(或不小于)其左、右孩子结点的值。
堆排序的原理:
- 首先将原无序数组进行一次建堆操作,既将该数组的顺序变成堆的序列(大顶堆)
- 此时堆顶元素,既序列第一个元素是原数组的最大元素
- 接下来进行删除堆顶元素操作,将堆顶和堆尾元素交换
- 再对除过堆尾元素的其他元素进行建堆操作(此时只需要将堆顶元素向下调整,找到合适的位置),这样新的堆顶元素又是当前所有元素的最大值,也是原数组的第二大元素
- 这样循环进行下去。就得到了一个升序数组
6.2堆排序实现代码
public static void heapSort(int[] arr) {
creatHeap(arr);//将数组进行建堆操作
int heapSize = arr.length;//获取数组长度
for (int i = 0; i < arr.length; i++) {
swap(arr, 0, heapSize-1);//交换数组第一个元素和最后一个元素
heapSize--;//将堆的实际大小减一
shiftDown(arr, 0, heapSize);//对堆顶元素向下调整
}
}
public static void creatHeap(int[] arr) {
//建堆循环开始的第一个元素可以直接从最后一个不是叶子结点的位置开始
for (int i = (arr.length - 1 - 1) / 2; i >= 0; i--) {
shiftDown(arr, i, arr.length);
}
}
//向下调整
public static void shiftDown(int[] arr, int i, int size) {
int parent = i;
int child = 2 * parent + 1;
while (child < size) {
//获取儿子位置较大的元素
if (child + 1 < size && arr[child + 1] > arr[child]) {
child = child + 1;
}
//将child和parent位置元素较大的一个放在父亲节点
if (arr[parent] < arr[child]) {
swap(arr, parent, child);
} else {
break;
}
//parent,child位置递归向下调整
parent = child;
child = 2 * parent + 1;
}
}
6.3 堆排序性能分析
稳定性:不稳定排序
七、归并排序
7.1 归并排序实现原理
归并排序图解
7.2 归并排序实现代码
public static void merge(int[] ary){
_merge(ary,0,ary.length);//辅助排序的方法
}
public static void _merge(int[] ary, int left, int right){
if(left>=right-1){
//退出条件
return ;
}
int mid=(left+right)/2;
_merge(ary,left,mid);
_merge(ary,mid,right);
sort(ary,left,mid,right);
}
public static void sort(int[] ary,int left,int mid,int right){
int size=right-left;//存放原数组left到right长度排序后的新数组
int l=left;
int r=mid;
int i=0;
int[] res=new int[size];
while(l<mid&&r<right){
//将数组元素从小到大一次放入新数组
if(ary[l]<=ary[r]){
res[i++]=ary[l++];
}else{
res[i++]=ary[r++];
}
}
//存放left到mid未存放完毕的元素
while(l<mid){
res[i++]=ary[l++];
}
//存放mid到right未存放完毕的元素
while(r<right){
res[i++]=ary[r++];
}
//将排序后的元素拷贝到原数组
for(int j=0;j<size;j++){
ary[left+j]=res[j];
}
}
7.3 归并排序性能分析
7.4 归并排序的非递归版本
public static void marge(int[] ary){
for(int gap=1;gap<ary.length;gap*=2){
for(int i=0;i<ary.length;i+=2*gap){
int left=i;
int mid=i+gap;
if(mid>=ary.length){
mid=ary.length;
}
int right=i+2*gap;
if(right>=ary.length){
right=ary.length;
}
_merge(ary,left,mid,right);
}
}
}