七大排序详解
七大排序算法简介+排序原理图解+代码实现+复杂度分析
大家好呀,在今天我们学习目前阶段中,我们最常用的七种排序——插入排序,希尔排序,选择排序,堆排序,冒泡排序,快速排序,归并排序,强烈建议大家配合代码和图片一起食用
一,排序简介
二,插入排序
排序思想
直接插入排序是一种简单的插入排序法,其基本思想是:
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列
1.1直接插入排序
排序原理
给定一个数组和一个临时变量tmp,直接插入排序大概分以下几步,其中变量 i 从数组下标 1 开始遍历数组,变量j每次从 i-1 开始向前调整,直到 i 前面的数据有序
代码实现
public class Main {
public static void main(String[] args) {
int[] array={10,2,3,7,9,8,6,59,40};
Main.DirectSort(array);
System.out.println(Arrays.toString(array));
}
public static void DirectSort(int[] array){//遍历数组的i
for (int i = 1; i <array.length; i++) {
int tmp=array[i];
int j = i-1;
for (; j>=0 ; j--) {//向前调整的j
if(array[j]>tmp){
array[j+1]=array[j];
}else {
break;//这个时候j不用向下调整了
}
}//for循环走完后j=-1;
array[j+1]=tmp;
}
}
}
复杂度分析
时间复杂度:O(N^2)
空间复杂度O(1)
稳定性:稳定
2.2 希尔排序
排序原理
希尔排序也可以说是对直接插入排序的优化,用一个需要排序的数据之间的下表距离变量Gap先把数据分为一组组数据,这样数据组数在变多,但是每组数据更加有序,在数据不可再分时,这个时候也就有序了。可以达到让直接插入排序时间复杂度降低的效果
代码实现
这个方法对我们刚才写的直接插入排序也有一定修改,具体见代码
public static void ShellSort(int[] array){
int Gap=array.length;
while(Gap>=1){
shell(array,Gap);
Gap/=2;//调整Gap
}
}
/*
这里的i和j都要以Gap为单位调整了,但是任然要i++,
无非就是每组交替排序,这样可以一次排序更多组数据
* */
private static void shell(int[] array,int Gap){
for (int i = Gap; i <array.length; i++) {
int tmp=array[i];
int j = i-Gap;
for (; j>=0 ; j-=Gap) {//向前调整的j
if(array[j]>tmp){
array[j+Gap]=array[j];
}else {
break;//这个时候j不用向下调整了
}
}
array[j+Gap]=tmp;
}
}
复杂度分析
希尔排序的时间复杂度分析是一个非常麻烦的过程,会根据Gap的不同而不同,这里只给出一般认为的时间负载度,具体大家可自行了解
时间复杂度:O(N^1.3)
空间复杂度O(1)
稳定性:不稳定
容易看出,希尔排序比直接插入排序还是快上不少的
三,选择排序
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完
3.1 直接选择排序
排序原理
1.在元素集合array[i]--array[n-1]中选择关键码最大(小)的数据元素
2.若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换
3.在剩余的array[i]--array[n-2](array[i+1]--array[n-1])集合中,重复上述步骤,直到集合剩余1个元素
这里我们用midIndex记录最小数据对应的下标,然后用两个指针i完成对数组的遍历,j完成最小数据下标的寻找
代码实现
public static void SelectSort(int[] array) {
for (int i = 0; i < array.length; i++) {//i用于遍历数组
int minIndex = i;
for (int j = i; j < array.length; j++) {//j一直向前找一个最小值对应的下标
if (array[j] < array[minIndex]) {
minIndex = j;
}
}
//交换操作
int tmp = array[i];
array[i]=array[minIndex];
array[minIndex]=tmp;
}
}
复杂度分析
时间复杂度:O(N^2)
空间复杂度O(1)
稳定性:不稳定
3.2 堆排序
排序原理
堆排序原理比较复杂,主要是围绕建创建堆来的,创建堆这部分我们在之前的博客中有介绍过,详情大家可以看看CSDN这篇文章,这里就不多赘述了,主要是在于如何通过创建好的堆来实现一个堆排序,我们这里以创建好一个大堆为例:
代码实现
/*
堆排序
**/
public static void HeapSort(int[] array){
CreateHeap(array);
int flag=array.length-1;
while(flag>0){
swap(array,0,flag);
shiftDown(0,array,flag);
flag--;
}
}
//建堆
public static void CreateHeap(int[] array){
int i = (array.length-2)>>1;
for ( ; i >=0 ; i--) {
shiftDown(i,array,array.length);
}
}
private static void shiftDown(int parent,int[] array,int length){
int child=parent*2+1;
while(child<length){
if(child+1<length&&array[child+1]>array[child]){
child++;
}
if(array[parent]>=array[child]){
break;
}else {
swap(array,parent,child);
parent=child;
child=parent*2+1;
}
}
}
public static void swap(int[] array,int x,int y){
int tmp = array[x];
array[x]=array[y];
array[y]=tmp;
}
复杂度分析
四,交换排序
4.1 冒泡排序
排序原理
冒泡排序相信大家都很熟悉,排序原理就在与通过两个循环一次只把一个数据放在正确的位置
代码实现
public static void BubbleSort(int[] array){
for (int i = 0; i <array.length-1 ; i++) {
for (int j = 0; j <array.length-1-i ; j++) {
if(array[j]>array[j+1]){
int tmp=array[j+1];
array[j+1]=array[j];
array[j]=tmp;
}
}
}
}
冒泡排序优化
当然,我们也可以用标记法优化一下冒泡排序,如果在一次 j 向后走过程中没法生任何交换的话说明原来的数据已经有序了
public static void BubbleSort(int[] array){
for (int i = 0; i <array.length-1 ; i++) {
boolean flag=true;
for (int j = 0; j <array.length-1-i ; j++) {
if(array[j]>array[j+1]){
int tmp=array[j+1];
array[j+1]=array[j];
array[j]=tmp;
flag=false;
}
}
if(flag){
break;
}
}
}
复杂度分析
时间复杂度:O(N^2)
空间复杂度O(1)
稳定性:稳定
4.2 快速排序
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:
1.任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列
2.通过交换让左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值
3.左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
排序原理
代码实现
public static void QuickSort(int[] array){//这里多一步是为了和之前的方法参数统一
quickSort(array,0,array.length-1);
}
public static void quickSort(int[] array,int start,int end){
if(start>=end){//结束条件
return;
}
int div=getDiv(array,start,end);
quickSort(array,start,div-1);//递归排序
quickSort(array,div+1,end);
}
public static int getDiv(int[] array,int left,int right){
int tmp=array[left];
int LeftFlag=left;
while(left<right){//从右往左,遇到比基准值小的停
while(left<right&&array[right]>=tmp){
right--;
}//必须先从右往左找否则交换的时候可能会出问题
while(left<right&&array[left]<=tmp){//从左往右,遇到比基准值大的停
left++;
}
swap(array,left,right);
}
swap(array,LeftFlag,left);
return left;
}
public static void swap(int[] array,int x,int y){
int tmp = array[x];
array[x]=array[y];
array[y]=tmp;
}
快速排序优化
快速排序的缺点在与在排序有序的数据时,时间复杂度会达到O(N^2), 这似乎很奇怪,其实关键就在于基准值的选取,基准值最好是一个比较折中的数,但是数据有序显然不满足这种要求,这个时候我们提出下面两种解决办法
1. 三数取中法选key
顾名思义,选取最左,最右,和最中间的数,并选择他们中第二大的数为基准
代码实现
private static int getMiddle(int[] array,int left,int right){
int mid=(left+right)/2;
if(array[left]<array[right]){
if(array[mid]<array[left]){
return left;
}else return mid;
}else {
if(array[mid]<array[right]){
return right;
}else return mid;
}
}
//加入了求中间值的方法
public static void QuickSort(int[] array){//这里多一步是为了和之前的接口统一
quickSort(array,0,array.length-1);
}
public static void quickSort(int[] array,int start,int end){
if(start>=end){//结束条件
return;
}
int MiddleIndex=getMiddle(array,start,end);
swap(array,start,MiddleIndex);//这里有所改动
int div=getDiv(array,start,end);
quickSort(array,start,div-1);//递归排序
quickSort(array,div+1,end);
}
public static int getDiv(int[] array,int left,int right){
int tmp=array[left];
int LeftFlag=left;
while(left<right){//从右往左,遇到比基准值小的停
while(left<right&&array[right]>=tmp){
right--;
}//必须先从右往左找否则交换的时候可能会出问题
while(left<right&&array[left]<=tmp){//从左往右,遇到比基准值大的停
left++;
}
swap(array,left,right);
}
swap(array,LeftFlag,left);
return left;
}
public static void swap(int[] array,int x,int y){
int tmp = array[x];
array[x]=array[y];
array[y]=tmp;
}
2. 递归到小的子区间时,可以考虑使用插入排序
在区间长度较小时,选择插入排序效率高于快排,所以这时可以采用快排减少递归深度,达到提高效率的效果,注意直接插入排序需要相应改动,配合上刚才的优化便是完整版快排
private static int getMiddle(int[] array,int left,int right){
int mid=(left+right)/2;
if(array[left]<array[right]){
if(array[mid]<array[left]){
return left;
}else return mid;
}else {
if(array[mid]<array[right]){
return right;
}else return mid;
}
}
public static void QuickSort(int[] array){//这里多一步是为了和之前的接口统一
quickSort(array,0,array.length-1);
}
public static void quickSort(int[] array,int start,int end){
if(start>=end){//结束条件
return;
}
if(end-start+1<=3){
DirectSortRange(array,start,end);
return;
}
int MiddleIndex=getMiddle(array,start,end);
swap(array,start,MiddleIndex);
int div=getDiv(array,start,end);
quickSort(array,start,div-1);//递归排序
quickSort(array,div+1,end);
}
public static int getDiv(int[] array,int left,int right){
int tmp=array[left];
int LeftFlag=left;
while(left<right){//从右往左,遇到比基准值小的停
while(left<right&&array[right]>=tmp){
right--;
}//必须先从右往左找否则交换的时候可能会出问题
while(left<right&&array[left]<=tmp){//从左往右,遇到比基准值大的停
left++;
}
swap(array,left,right);
}
swap(array,LeftFlag,left);
return left;
}
public static void swap(int[] array,int x,int y){
int tmp = array[x];
array[x]=array[y];
array[y]=tmp;
}
public static void DirectSortRange(int[] array,int start,int end){
for (int i = start+1; i <= end; i++) {
int tmp = array[i];
int j = i - 1;
for (; j >= start; j--) {//向前调整的j
if (array[j] > tmp) {
array[j + 1] = array[j];
} else {
break;//这个时候j不用向下调整了
}
}//for循环走完后j=-1;
array[j + 1] = tmp;
}
}
4.3 快速排序非递归
刚才的快排涉及到递归,数据量一多很容易栈溢出,因此这里给出非递归代码实现方式,主要是用栈模拟了递归过程
排序原理
初始化栈来模拟递归过程,需要用到上面用于调整数组的Getdiv方法,推荐配合代码食用
代码实现
public static void QuickSortNor(int[] array){
quickSortNor(array,0,array.length-1);
}
public static void quickSortNor(int[] array,int start,int end){
Stack<Integer> stack=new Stack<>();
int div=getDiv(array,start,end);
if(div>start+1){
stack.push(start);
stack.push(div-1);
}
if(div<end-1){
stack.push(div+1);
stack.push(end);
}
while(!stack.empty()){
end=stack.pop();
start=stack.pop();
div=getDiv(array,start,end);
if(div>start+1){
stack.push(start);
stack.push(div-1);
}
if(div<end-1){
stack.push(div+1);
stack.push(end);
}
}
4.4 快速排序总结
快速排序整体性能和应用场景都是比较好的
时间复杂度: O(N*logN)
空间复杂度:O(logN)
稳定性:不稳定
五,归并排序
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使
子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤
排序原理
核心原理就在与先将数组分为一个个小块,再调用一个合并有序数组的方法将各个数组合并和方法,先让每个子序列有序,再合并起来
这里合并两个有序数组的算法可以见. - 力扣(LeetCode)必刷的简单算法,就不赘述了
代码实现
public static void MergeSort(int[] array){
mergeSort(array,0,array.length-1);
}
private static void mergeSort(int[] array, int left, int right) {
if(left>=right){
return;
}
int mid=(left+right)/2;
mergeSort(array,left,mid);
mergeSort(array,mid+1,right);
merge(array,left,mid,right);
}
private static void merge(int[] array, int left, int mid, int right) {
int[] newArray=new int[right-left+1];
int start1=left;
int start2=mid+1;
int index=0;
while(start1<=mid&&start2<=right) {
if (array[start1] < array[start2]) {
newArray[index++] = array[start1++];
} else {
newArray[index++] = array[start2++];
}
}
while(start1<=mid){
newArray[index++]=array[start1++];
}
while(start2<=right ){
newArray[index++]=array[start2++];
}
//在将新数组的值赋给原数组时必须在原数组下标加上left,否则会出现每次都从新数组0开始赋值给原数组,不
//合理
for (int i = 0; i <index; i++) {
array[i+left]=newArray[i];
}
}
}
复杂度分析
时间复杂度: O(N*logN)
空间复杂度:O(N)
稳定性:稳定
六,总结
好啦,以上就是我们最常见的七大算法啦,有误请在大家指正,感谢大家观看,我们下期见~
更多推荐
所有评论(0)