排序-算法

排序就是使一串数据,按照其中某个或某些关键字的大小,递增或递减排列起来的操作

稳定性:

内部排序:数据元素全部放在内存中的排序

外部排序:数据元素太多不能同时放在内存中,根据排序要求在内外存之间移动数据的排序

插入排序

基本思想: 把待排序的数据按照其关键码值的大小逐个插入到一个已经排好序的有序数组中,直到所有的记录插完为止,得到一个新的有序数列

直接插入排序

java 复制代码
 public static void insertSort(int[] array){
         int i=1;
         while(i<array.length) {
            int j = i - 1;
             int tmp = array[i];
             if (array[j] > tmp) {
                 for(;j>=0;j--){
                     if(array[j]>tmp){
                         array[j+1]=array[j];
                     }else{
                         array[j+1]=tmp;
                         break;
                     }
                 }
                 array[j+1]=tmp;
             }
             i++;
         }
    }

时间复杂度O(N^2) 空间复杂度0(1) 稳定 数据越有序,直接插入排序越快

希尔排序

希尔排序又叫缩小增量法 。基本思路就是将原数据进行分组 。然后对每组进行插入排序,最后每个元素单独一组,进行插入排序。本质上是插入排序的优化,因为数据越趋近于有序,插入排序效率越高,演示:

第一次分组(五组):

第二次分组(二组):

最后一次不进行分组,直接进行插入排序

可以发现,希尔排序并不是相邻元素为一组,这样的好处是可以使小的元素移到更前面,大的元素移到更后面

java 复制代码
    private static void Sort(int gap,int[] array){
        for(int i=gap;i<array.length;i++){
            int tmp=array[i];
            int j=i-gap;
            for(;j>=0;j-=gap){
                if(array[j]>tmp){
                    array[j+gap]=array[j];
                }else{
                    array[j+gap]=tmp;
                    break;
                }
            }
            array[j+gap]=tmp;
        }
    }
    public static void shellSort(int[] array){
        int gap=array.length;
        while(gap!=1){
            gap/=2;
            Sort(gap,array);
        }
        Sort(1,array);
    }
}

希尔排序的特性总结:

1.希尔排序是对直接插入的优化

2.gap>1时都是预排序 ,目的是让数组更接近于有序。当gap==1时,数组已经接近有序了。但是希尔排序的时间复杂度不好计算,因为gap的取值可以有很多,这里给出一些主流的gap分法和时间复杂度分析

3.希尔排序是不稳定的

选择排序

直接选择排序

基本思想:以升序为例,遍历数组找到最小的数据 ,将其与第一个数据交换 。然后从第二个数据开始接着遍历,找到最小的数据,将其与数组第二个数据进行交换,以此类推。

java 复制代码
    public static void selectSort(int[] array){
        int minindex=0;
        int len=array.length;
        for(int i=0;i<array.length;i++){
            minindex=i;
            for(int j=i+1;j<len;j++){
                if(array[j]<array[minindex]) minindex=j;
            }
            Swap(array,i,minindex);
        }
    }
    private static void Swap(int[] array,int i,int j){
        int tmp=array[j];
        array[j]=array[i];
        array[i]=tmp;
    }

时间复杂度O(N^2) 不稳定

也可以进行优化,遍历一次同时寻找最大和最小的数据

java 复制代码
    private static void Swap(int[] array,int i,int j){
        int tmp=array[j];
        array[j]=array[i];
        array[i]=tmp;
    }
    public static void selectSort1(int[] array){
        int minIndex=0;
        int maxIndex=0;
        int left=0;
        int len=array.length;
        int right=len-1;
        while(left<right){
            minIndex=left;
            maxIndex=left;
            for(int j=left+1;j<right;j++){
                if(array[j]>array[maxIndex]) maxIndex=j;
                if(array[j]<array[minIndex]) minIndex=j;
            }
            if(maxIndex==left) maxIndex=minIndex;//防止maxIndex的下标改变
            Swap(array,left,minIndex);
            Swap(array,right,maxIndex);
            left++;
            right--;
        }
    }

时间复杂度O(N^2)

堆排序

排升序建大堆,排降序建小堆

java 复制代码
    private static void creatHeap(int[] array){
        int len=array.length;
        int parent=(len-1-1)/2;
        for(int i=parent;i>=0;i--){
                shiftDown(array,parent,len);
        }
    }
    private static void shiftDown(int[] array,int parent,int len) {
        int child = 2*parent+1;
        while (child<len) {
            if(child<len-1&&array[child]<array[child+1]){
                child++;
            }
            if(array[child]>array[parent]){
                Swap(array,child,parent);
                parent=child;
            }else{
                break;
            }
            child = 2*parent+1;
        }
    }
    public static void heapSort(int[] array){
            creatHeap(array);
            int len=array.length;
            for(int i=len-1;i>0;i--){
                Swap(array,0,i);
                shiftDown(array,0,i);
            }
    }

时间复杂度:O(N*logN) 不稳定

交换排序

冒泡排序

java 复制代码
    public static void bubbleSort(int[] array){
        int len=array.length;
        boolean flag=true;
        for(int i=0;i<=len-1;i++){
            for(int j=0;j<len-i-1;j++){
                if(array[j]>array[j+1]){
                    Swap(array,j,j+1);
                    flag=false;
                }
            }
            if(flag) break;
        }
    }

时间复杂度:O(N^2) 稳定

优化前 时间复杂度最好和最坏 的情况都是O(N^2) 优化后最好 的情况可以达到O(N) 最坏 的情况认为O(N^2)

快速排序(分治法)

以升序为例,首先取待排序数组中的一个数据作为基准值 (一般取最左边数据)。然后将这个数组转换成基准值的左边都比基准值小,基准值的右边都比基准值大。从基准值开始,分成两个数组,继续重复,排成左小右大的形式。

Hoare版(其次)

图解:

一:

二:

代码:

java 复制代码
    private static int partionHoare(int[] array,int left,int right){
        int tmpleft=left;
        int tmpval=array[left];
        left++;
        while(left<right){
            while(left<right&&array[right]>=tmpval){
                right--;
            }
            while(left<right&&array[left]<=tmpval){
                left++;
            }

            swap(array,left,right);
        }
        swap(array,tmpleft,left);
        return left;
    }
    private static void swap(int[] array,int left,int right){
            int tmp=array[left];
            array[left]=array[right];
            array[right]=tmp;
    }
    private static void Fast(int[] array,int left,int right){
        if(left>=right) return;
        int div=partion(array,left, right);
        Fast(array,left,div-1);
        Fast(array,div+1,right);
    }
    public static void fastSort(int[] array){
        Fast(array,0,array.length-1);
    }

注意:必须先从后向左寻找 ,而且当数组的值与标准值相等时不进行交换,继续寻找

挖坑法(优先使用):

以升序为例:先将**(基准值)** 最左值存储起来,这是认为原位置是一个坑 ,然后right从后往前 进行遍历。遇到小于基准值的数据,将其放进左边的坑 中,这时右边也产生了一个坑,这时left从前向后遍历 ,遇到大于基准值的数据,将其放入左边的坑 中,右边又产生一个坑,以此类推,直到left==right

java 复制代码
 private static int partition(int[] array,int left,int right){
        int tmp=array[left];
        while(left<right){
            while(left<right&&array[right]>=tmp){
                right--;
            }
            array[left]=array[right];
            while(left<right&&array[left]<=tmp){
                left++;
            }
            array[right]=array[left];
        }
        array[left]=tmp;
        return left;
    }

前后指针法(最次):

array[cur]<array[left]且cur和prev不相邻时,进行交换。直到cur走到空,此时将array[left]与array[prev]进行交换

java 复制代码
    private static int partition2(int[] array,int left,int right){
        int cur=left+1;
        int prev=left;
        while(cur<=right){
            if(array[cur]<array[left]&&array[++prev]!=array[cur]){
                swap(array,cur,prev);
            }
            cur++;
        }
        swap(array,prev,left);
        return prev;
    }

时间复杂度:最坏情况:当数据完全正序或者逆序时 ,时间复杂度为O(N^2)一般不考虑 这种情况,因为快排并不适用这种数据。最好情况:每次都是均分,正好形成一个完全二叉树(满二叉树),此时时间复杂度为O(N*logN) 。一般我们**都以O(N*logN)**为快排的时间复杂度

空间复杂度: 数据完全正序或者完全逆序:O(N) 最好情况 O(logN)(默认)

因为是递归 进行,所以当数据量过于庞大的时候,这种方法就不再适用(会出现栈溢出),所以这时就需要进行优化

快速排序的优化

1.三数取中法选key

找到数组的头部,尾部,中间数据的中位数 ,将其与数组最左边元素进行交换,将其作为基准 ,目的是让每一个分支都尽量均分,避免极端情况下只出现单分支

java 复制代码
        private static int getMidindex(int[] array,int left,int right){
        int Midindex=(right+left)/2;
        if(array[left]<array[right]){
            if(array[Midindex]<array[left]){
                Midindex=left;
            }else if(array[Midindex]>array[right]){
                Midindex=right;
            }
        }else{
            if(array[Midindex]>array[left]){
                Midindex=left;
            }else if(array[Midindex]<array[right]) {
                Midindex=right;
            }
        }
        return Midindex;
    }    
    private static void Fast(int[] array,int left,int right){
        if(left>=right) return;
        int midIndex=getMidindex(array,left,right);
        swap(array,left,midIndex);
        int div=partition2(array,left, right);
        Fast(array,left,div-1);
        Fast(array,div+1,right);
    }

2 递归到较小区间时,使用插入排序

原理:越排越有序并且二叉树越大,后面的节点越多,递归的次数越多。此时可以考虑使用插入排序,来降低递归深度

java 复制代码
        if((right-left+1)>=7){
            insertSort(array,left,right);
            return;
        }

非递归实现快速排序

使用栈将每组的left和right下标进行存储 ,来代替递归。当栈为空时完成排序

java 复制代码
    private static void FastNor(int[] array,int left,int right){
        Stack<Integer> stack=new Stack<>();
        int div=partition(array,left,right);
        if(div>left+1){
            stack.push(left);
            stack.push(div-1);
        }
        if(div<right-1){
            stack.push(div+1);
            stack.push(right);
        }
        while(!stack.isEmpty()){
           div=partition(array,left,right);
            if(div>left+1){
                stack.push(left);
                stack.push(div-1);
            }
            if(div<right-1){
                stack.push(div+1);
                stack.push(right);
            }
            right=stack.pop();
            left=stack.pop();
        }
    }

归并排序

归并排序是分治法 的典型应用,先使每一个子序列有序 ,然后将子序列合并为一个序列(二路归并),从而得到完全有序的序列

java 复制代码
 private static void Merge(int[] array,int left,int mid,int right){
        int[] tmp=new int[right-left+1];
        int lefttmp=left;
        int k=0;
        int as=mid+1;
        while(left<=mid&&as<=right){
            if(array[left]<array[as]){
                tmp[k++]=array[left++];
            }else{
                tmp[k++]=array[as++];
            }
        }
        while(left<=mid){
            tmp[k++]=array[left++];
        }
        while(as<=right){
            tmp[k++]=array[as++];
        }
        if(k>0) System.arraycopy(tmp, 0, array, lefttmp, k);
    }
    private static void mergeSortTmp(int[] array,int left,int right){
        if(left>=right) return;
        int mid=(left+right)/2;
        mergeSortTmp(array,left,mid);
        mergeSortTmp(array,mid+1,right);
        Merge(array,left,mid,right);

    }
    public static void mergeSort(int[] array){
        mergeSortTmp(array,0,array.length-1);
    }

非递归实现:

每个单独的元素是有序 的,所以直接进行二路归并

java 复制代码
 private static void mergeSortNor(int[] array){
        int gap=1;
        int len=array.length;
        while(gap<len){
            for(int i=0;i<len;i+=(gap*2)){
                int mid=i+gap-1;
                if(mid>len) mid=len-1;
                int right= mid+gap;
                if(right>len) right=len-1;
                Merge(array,i,mid,right);
            }
            gap*=2;
        }
    }

时间复杂度:O(N*logN) 空间复杂度O(N) 稳定

归并排序思考更多的是解决在磁盘外中排序的问题

海量数据的排序问题

外部排序:排序过程需要在外部储存进行的排序(例:内存只有1G 而需要排100G文件)

首先将文件分成200份 ,每份512mb,然后分别对每份进行排序 ,这样就得到局部有序数列 。最后进行二路归并 ,合成一个有序文件,因为归并的时候是一个一个元素进行比较的,所以不用担心内存溢出的问题。

总结

非基于比较的排序

计数排序

专门用于处理大小在某个范围内的数据:

原数据**(0,10]**

计数

打印:

java 复制代码
    public static void countSort(int[] array){
        int min=array[0];
        int max=array[0];
        int len=array.length;
        for(int i=1;i<len;i++){
            if(min>array[i]) min=array[i];
            if(max<array[i]) max=array[i];
        }
        int index=0;
        int[] count=new int[max-min+1];
        for(int i=0;i<len;i++){
            count[(array[i]-min)]++;
        }
        //将排好序的数据放回原数组
        int counts=0;
        for(int i=0;i<(max-min+1);i++){
            counts=count[i];
            while(counts!=0){
                array[index++]=i+min;
                counts--;
            }
        }
    }

时间复杂度O(范围) 空间复杂度O(范围) 稳定(但是本文中实现的方法不稳定)

相关推荐
WongKyunban1 小时前
插入排序的原理和示例
数据结构·算法·排序算法
flashlight_hi1 小时前
LeetCode 分类刷题:404. 左叶子之和
javascript·算法·leetcode
聪明绝顶的小Yya2 小时前
数据结构万字解读
数据结构
小白程序员成长日记2 小时前
2025.11.19 力扣每日一题
算法·leetcode·职场和发展
迈巴赫车主3 小时前
蓝桥杯 20541魔法科考试
java·数据结构·算法·蓝桥杯
star learning white4 小时前
xmC语言8
c语言·开发语言·算法
青小俊4 小时前
【代码随想录c++刷题】-二分查找 移除元素 有序数组的平方 - 第一章 数组 part 01
c++·算法·leetcode
ytttr8734 小时前
基于MATLAB实现晶体共晶凝固模拟
开发语言·算法·matlab
倦王5 小时前
力扣日刷251120
算法·leetcode·职场和发展