八个常用排序

一、插入排序

1.1 直接插入排序

直接插入排序算法的原理如下

插入排序是指在待排序的元素中,假设前面n-1(其中n>=2)个数已经是排好顺序的,现将第n个数插到前面已经排好的序列中,然后找到合适自己的位置,使得插入第n个数的这个序列也是排好顺序的。按照此法对所有元素进行插入,直到整个序列排为有序的过程。

public class InsertionSort {
    public static void main(String[] args) {
        int arr[]={3,9,-1,10,-2};

        int i,j,temp;

        for( i=1;i< arr.length;i++){
            if(arr[i]<arr[i-1]){ //前有序表的最后一个元素
                // 大于后一个无序表中元素,要把此元素插入到前面有序表,使其依然有序
                temp=arr[i]; // 后续需要移位,故A[i]会被破坏,要暂存A[i]
                for (j=i-1;j>=0 && arr[j]>temp;j--){ //有序表从后往前遍历
                    arr[j+1]= arr[j]; // 所有大于temp的元素都后移一位
                }
                arr[j+1]=temp; // 复制到插入位置
                        // 如果在for循环中声明j,此操作就会出错
            }
        }
        System.out.println(Arrays.toString(arr));
    }
}

1.2 直接插入排序(优化 利用折半查找-找到要插入的位置)

public class BInsertionSort {
    public static void main(String[] args) {
        int arr[]={3,9,-1,10,-2};

        int i,j,temp,low,high,mid;

        for( i=1;i< arr.length;i++){
            if(arr[i]<arr[i-1]){ //前有序表的最后一个元素
                // 大于后一个无序表中元素,要把此元素插入到前面有序表,使其依然有序
                temp=arr[i]; // 后续需要移位,故A[i]会被破坏,要暂存A[i]

                low=0;
                high=arr.length-1;
                                    // 最后要查找后 指针 high在low左边一位
                while (low<=high){  // 找到要插入的位置,[low,i-1]全部后移
                    mid=(low+high)/2;
                    if (arr[mid]<temp){
                        low=mid+1;
                    }else {
                        high=mid-1;
                    }
                }

                for (j=i-1;j>=low ;j--){ //有序表从后往前遍历 low==high+1
                    arr[j+1]= arr[j]; // 所有大于temp的元素都后移一位
                }
                arr[j+1]=temp; // 复制到插入位置
                // 如果在for循环中声明j,此操作就会出错
            }
        }
        System.out.println(Arrays.toString(arr));
    }
}

2 希尔排序

(先追求局部有序,再逼近全局有序—增量d不断变小)

希尔排序算法的原理如下:

先将待排序表分割成若干形如 L [ i,i+d,i+2d,…,i+kd ]的“特殊”子表,对各个子表分别进行直接插入排序缩小增量d,重复上述过程,直到d=1为止。

2.1 交换法 (先分组后冒泡

此方案比直插还慢
代码如下:

public class ShellSort {
    public static void main(String[] args) {
        int arr[]={3,9,-1,10,-2,5,6,2,5,7,8};

        int i,j,d,temp;  // d表示步长
        for(d=arr.length/2;d>=1;d/=2){  // 初始的步长为数组的一半,后面不断减半
            for (i=d;i< arr.length;i++){ //d+1从子表的第二个开始比较插入
            // 遍历各组中所有的元素,步长为d
               for (j=i-d;j>=0 ;j-=d){ 
               		// 如果当前元素大于加上步长后的那个元素,说明交换
               		if(arr[j]>arr[j+d]){
               			temp=arr[j];
               			arr[j]=arr[j+d];
               			arr[j+d]=temp;
               		}  
                }
            }
        }
        System.out.println(Arrays.toString(arr));
    }
}

2.2 移位法 (先分组后直插

代码如下:

public class ShellSort {
    public static void main(String[] args) {
        int arr[]={3,9,-1,10,-2,5,6,2,5,7,8};

        int i,j,d,temp;  // d表示步长
        for(d=arr.length/2;d>=1;d/=2){  // 初始的步长为数组的一半,后面不断减半
            for (i=d;i< arr.length;i++){ //d+1从子表的第二个开始比较插入
                if (arr[i]<arr[i-d]){       // i++是不断切换子表操作
                    temp=arr[i];            // 直接插入排序
                    for (j=i-d;j>=0 && arr[j]>temp;j-=d){
                        arr[j+d]=arr[j];
                    }
                    arr[j+d]=temp;
                }
            }
        }
        System.out.println(Arrays.toString(arr));
    }
}

二、交换排序

1、冒泡排序

(每趟中两两对比-可能交换,把最值放到一端)

冒泡排序算法的原理如下:

1、比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2、对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
3、针对所有的元素重复以上的步骤,除了最后随趟数增加每趟找到的最大或最小不再参与排序)个。
4、持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
5、优化:通过表示变量flag,在进入交换后改变其值,如果flag值没有改变则标志没有数据交换(已排序好)

标识变量优化

public class BubbleSort {
    public static void main(String[] args) {
        int arr[]={3,9,-1,10,-2};
		int temp=0; // 临时变量
        boolean flag=false; // 标识变量,表示是否进行过交换

        for(int i= arr.length-1;i>=0;i--){

            for (int j=0;j< i;j++){
                // 如果前面比后面的小则交换
                if(arr[j]<arr[j+1]){
                    flag=true; // 进入过交换则把标识变量赋值为true

                    temp=arr[j];
                    arr[j]=arr[j+1];
                    arr[j+1]=temp;
                }
            }
            if(flag==false){ // 在此趟排序中一次交换都没有发生过
                break;
            }else {
                flag=false; // 如果交换,重置flag,进行下趟判断是否交换
            }

        }
        System.out.println(Arrays.toString(arr));
    }
}

2、快速排序(分治策略)

双指针快速排序基本原理:

1、设置两个变量 low、high,排序开始时:low=0,high=size-1。
2、整个数组找基准正确位置,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面

2.1)默认数组的第一个数为基准数据,赋值给key,即key=array[low]。
2.2)因为默认数组的第一个数为基准,所以从后面开始向前搜索(high–),找到第一个小于key的array[high],就将 array[high] 赋给 array[low],即 array[low] = array[high]。(循环条件是 array[high] >= key;结束时 array[high] < key)
2.3)此时从前面开始向后搜索(low++),找到第一个大于key的array[low],就将 array[low] 赋给 array[high],即 array[high] = array[low]。(循环条件是 array[low] <= key;结束时 array[low] > key)
2.4)循环 2-3 步骤,直到 low=high,该位置就是基准位置。
2.5)把基准数据赋给当前位置。

3、第一趟找到的基准位置,作为下一趟的分界点。

4、递归调用(recursive)分界点前和分界点后的子数组排序,重复2.2、2.3、2.4的步骤。

5、最终就会得到排序好的数组。

public class QuickSort {
    public static void main(String[] args) {
        int arr[]={3,9,-1,10,-2,5,6,2,5,7,8};
        Quicksort(arr,0,arr.length-1);
        System.out.println(Arrays.toString(arr));
    }

    public static void Quicksort(int arr[],int low,int high){
        if (low<high){
            int keypos=Partition(arr,low,high);  //获取划分最后key的索引位置
            Quicksort(arr,low,keypos-1);    // 递归划分key索引左表
            Quicksort(arr,keypos+1,high);   // 递归划分key索引右表
        }
    }


    public static int Partition(int arr[],int low ,int high){
        int key=arr[low]; // 保存第一个元素作为基准
        while (low<high){
            while (low<high && arr[high]>=key){
                high--;
            }
            // 出现比key小的数,将其移到左边
            arr[low]=arr[high];  //因为arr[low]已经保存下来,可以被覆盖

            // 左右两边来回切换

            while (low<high && arr[low]<key){
                low++;
            }
            // 出现比key大的数,将其移到右边
            arr[high]=arr[low]; //因为arr[high]已经被上述arr[low]保存下来,
                                // 可以被覆盖
        }
        // 大循环的停止时是 --- low==high
        arr[low]=key;  // 把基准赋给到最终low/high的位置

        return low;  // 返回当前位置,以便后面递归分组
    }
}

单指针快速排序基本原理:

出现小的移动指针,大的则不移动,故当出现小的时,mark移动到大于基准的索引位置,然后与现在的值arr[i]交换,使得小的值移到前面,大的值移到后面,最后基准与mark位置交换,并返回mark指针对应的索引位置,以便后续递归划分左右表。

public class QuickSort {
    public static void main(String[] args) {
        int arr[]={3,9,-1,10,-2,5,6,2,5,7,8};
        Quicksort2(arr,0,arr.length-1);
        System.out.println(Arrays.toString(arr));
    }
    
    public static void Quicksort2(int arr[],int low,int high){
        if (low<high){
            int keypos=partition2(arr,low,high);  //获取划分最后key的索引位置
            Quicksort2(arr,low,keypos-1);    // 递归划分key索引左表
            Quicksort2(arr,keypos+1,high);   // 递归划分key索引右表
        }
    }
	
	// 单边循环 单指针快排
    public static int partition2(int[] arr ,int low ,int high){
        int pivot = arr[low]; // 取第一个元素为基准
        int mark = low;
        for (int i=low+1;i<=high;i++){
            if(arr[i]<pivot){  // 出现小的移动指针,大的则不移动,故当出现小的时,mark移动到大于基准的索引位置,然后与现在的值arr[i]交换,使得小的值移到前面,大的值移到后面,最后基准与mark位置交换,并返回mark指针。
                mark++;
                int temp=arr[mark];
                arr[mark]=arr[i];
                arr[i]=temp;
            }
        }
        // mark与一开始基准元素交换,确定新基准
        arr[low]=arr[mark];
        arr[mark]=pivot;
        return mark;
    }

三、选择排序

1、简单选择排序

(每趟对比起始位置和后续的值,得到后续中最小的值与起始位置交换【可能最小的就是起始位】)

选择排序算法的原理如下:

工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。

1、选择排序一共有数组大小-1论排序
2、每一轮排序,又是一个循环,循环的规则(代码)
2.1 先假定当前这个数是最小数
2.2 然后和后面的每个数进行比较,如果发现有比当前数更小的数,就重新确定最小数,并得到下标
2.3 当遍历到数组的最后时,就得到本轮最小数和下标
2.4 交换当前数和最小数(代码)
重复以上操作

public class SelectionSort {
    public static void main(String[] args) {
        int arr[]={3,9,-1,10,-2};

        for (int i=0 ;i<arr.length;i++){
            int min=i;  // 临时变量 存放临时最小值的的索引
            for(int j=i+1;j< arr.length;j++){ // 从当前数的下一个开始遍历
                if(arr[j]<arr[min]){  // 遍历得到最小值的索引
                    min=j;
                }
            }
            if(min!=i){
                swap(arr,i,min); //如果遍历后 最小值的索引改变,
                        // 则交换当前i索引对应的值和遍历得到最小值索引的最小值
            }
        }
        System.out.println(Arrays.toString(arr));
    }

    public static void swap(int arr[],int i,int min){
        int temp =arr[i];
        arr[i]=arr[min];
        arr[min]=temp;
    }
}

2、堆排序

堆的概述:
若n个关键字序列L[1…n] 满足下面某一条性质,则成为堆(Heap):
① 若满足:L(i) >= L(2i) 且 L(i) >= L(2i+1) (1 <= i <= n/2)—大根堆(大顶堆)
② 若满足:L(i) <= L(2i) 且 L(i) <= L(2i+1) (1 <= i <= n/2)—小根堆(小顶堆)

建立大根堆:
思路把所有非终端结点都检查一遍,是否满足大根堆的要求,如果不满足,则进行调整。如果小于左右孩子,把两个中更大的与其交换
注意:当前结点处理后,更小的元素下坠,可能导致下一层子树不符合大根堆的要求,故需要继续往下调整。
在顺序存储的完全二叉树中,非终端结点编号i<=n/2 —向下取整

建立大根堆(代码)

	// 建立大根堆
	public static void BuildMaxHeap(int arr[],int len){
        // arr元素索引是从0开始的,所以最后一个非终端结点len/2-1
                                    // ---即arr.length/2-1
        for(int i=len/2-1;i>=0;i--){  // 从后往前遍历调整所有非终端结点
            HeadAdjust(arr,i,len);    //调整堆
        }
    }
	
	// 把数组调整为大根堆
    // k代表非终端结点 ,len代表树中元素的个数(数组的长度)
    public static void HeadAdjust(int arr[],int i,int len){
        int temp=arr[i];  // temp暂存子树的根结点,后续可能被交换
        for (int k=2*i+1;k<len;k=2*k+1){
            // //2*i+1为左子树i的左子树(因为i是从0开始的),2*k+1为k的左子树
            // 沿值较大的子结点向下筛选
            if (k+1<len && arr[k]<arr[k+1]){  //如果有右子树,并且右子树大于左子树
                k++;    // 取到当前非终端结点的左右孩子值最大对应的索引
            }
            if (temp>=arr[k]){
                break;      // 根结点比左右孩子中最大的都大,不用交换,结束循环
            }else {   // 如果小于最大的就要交换
                arr[i]=arr[k];  // 把当前结点的左右孩子中最大的赋给当前结点
                i=k;    // 当前结点的值下坠,可能导致下层不满足大根堆,
                    // 因此从被交换的孩子索引(交换后当前结点对应的位置),继续向下筛选
            }
        }
        arr[i]=temp; // 最后把当前结点的值赋给最后k停留的位置
    }

堆排序:(代码如下)

每一趟将堆顶元素加入有序子序列(与待排序序列中的最后一个元素交换),并将待排序元素序列再次调整为大根堆(小元素不断“下坠”)

public static void Heapsort(int arr[],int len){
        BuildMaxHeap(arr,len);          // 初始建堆
        for (int i=len-1;i>0;i--){        // n-1趟交换和建堆过程
            swap(arr,0,i);          // 堆顶元素索引0和堆底元素交换
            HeadAdjust(arr,0,i); // 把剩余的待排序元素整理成大根堆
             // 索引0位置的最大值被换到i(len-1),剩余[0,len-1]调整为大根堆
        }
    }

    /**
     * 交换元素
     * @param arr
     * @param a 元素的下标
     * @param b 元素的下标
     */
    public static void swap(int[] arr, int a, int b) {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }

四、归并排序

分治策略,字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。

归并操作的工作原理如下:

1、创建一个辅助数组,使其大小为原序列的长度,该空间用来存放合并后的序列

2、递归划分左右序列–直到每个左右只有一个元素

3、执行合并操作
3.1)设定两个指针i、j,最初位置分别为两个序列的起始位置low、mid+1
3.2)比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
重复步骤3.2直到某一指针超出序列尾
将另一序列剩下的所有元素直接复制到合并序列尾

代码如下:

public class MergeSort {
    public static void main(String[] args) {
        int arr[]={3,9,-1,10,-2,5,6,2,5,7,8};
        int[] temp = new int[arr.length]; //创建辅助数组
        Mergesort(arr,0,arr.length-1,temp);
        
        System.out.println(Arrays.toString(arr));
    }

    public static void Mergesort(int arr[],int low,int high,int temp[]){
        if (low<high){
            int mid=(low+high)/2;           // 从中间划分
            Mergesort(arr,low,mid,temp);   // 递归对左半部分划分归并排序
            Mergesort(arr,mid+1,high,temp); // 递归对右半部分划分归并排序
            Merge(arr,low,mid,high,temp); // 把当前划分后的元素,归并排序
        }
    }

    /*
    * arr:排序的原始数组
    * low:初始索引
    *  mid:中间索引
    *  high:最后索引
    *   temp:中转数组
    * */
    public static void Merge(int arr[] ,int low,int mid ,int high,int temp[]){
        int i,j,k; // i(low):左边有序序列的初始索引
                    // j(mid+1):右边有序序列的初始索引
                    // k:指向temp数组的当前索引
        for (k=low;k<=high;k++){  //把原数组的元素复制到temp数组中
            temp[k]=arr[k];
        }
        for (i=low,j=mid+1,k=i;i<=mid && j<=high ;k++){
            if (temp[i]<temp[j]){
                arr[k]=temp[i++];  // 将较小的值重新赋给arr数组
            }else {
                arr[k]=temp[j++];
            }
        }
        // for循环结束是由于其中一个子序列归并完,故需要把另一个剩余部分归并到尾部
        while (i<=mid) arr[k++]=temp[i++];
        while (j<=high) arr[k++]=temp[j++];
    }
}

五、基数排序

基数排序是效率高的稳定性的排序,是桶排序的扩展。

基数排序基本思想

将所有待比较数值统一为同样的数位长度,数位较短的前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。(LSD—最低为优先)
1)首先要得到最大数,确定最大数是几位数
2)定义一个二维数组,表示10个桶(每位取值范围0-9),每个桶就是一个一维数组,同时要定义一个一维数组来记录每个桶中存放数据的个数。在每轮大循环过后,都需要把其置为0
3)通过循环从个位到百位放入桶中,再依桶的顺序取出存放再每个桶中的数据,存到原始数组中(覆盖)。
特别注意:每一轮处理后,要把每个桶中存放数据的个数置零,bucketElementCounts[k]=0 !!!

public class RadixSort {
    public static void main(String[] args) {
        int arr[]={53,3,542,748,14,214};
        radixSort(arr);
        System.out.println(Arrays.toString(arr));
    }

    // 基数排序
    public static void radixSort(int[] arr){

        // 1、得到数组中最大数的位数
        int max=arr[0]; // 假定第一个数就是最大的数
        for (int i=1;i< arr.length;i++){
            if (arr[i]>max){
                max=arr[i];
            }
        }
        // 得到最大数是几位数
        int maxLength=(max + "").length();

        //定义一个二维数组,表示10个桶,每个桶就是一个一维数组
        // 说明
        // 1、二维数组包含10个一维数组
        // 2、防止在放入数的时候,数据溢出,每个一维数组(桶),大小定为arr.length
        // 3、故可以看出,基数排序是使用空间换时间的经典算法
        int[][] bucket = new int[10][arr.length];

        // 为了记录每个桶中,实际存放多少数据,定义一个一维数组来记录每个桶的数据个数
        // 例如 bucketElementCounts[0]记录就是bucket[0]桶中数据个数
        int[] bucketElementCounts = new int[10];


        // 使用循环处理
        for (int i=0,n=1 ;i<maxLength;i++,n*=10){ // 数取模用到 n
            // 针对每个元素对应的位进行排序,个、十、百。。。
            for (int j=0 ;j< arr.length;j++){
                // 去除每个元素的对应位的值
                int digitofElement = arr[j] / n % 10;
                // 放入到对应的桶中   bucketElementCounts[digitofElement]默认0,即桶下标0存第一个数据
                bucket[digitofElement][bucketElementCounts[digitofElement]] = arr[j];
                bucketElementCounts[digitofElement]++;   // 记录的是每个桶存数据的个数
            }

            // 按照桶的顺序(一维数组的下标依次取出数据,放入原来数组)
            int index = 0;
            // 遍历每一个桶,并将桶中数据,放回原数组
            for(int k=0 ;k< bucketElementCounts.length;k++){
                //如果桶中有数据,我们才放入到原数组
                if (bucketElementCounts[k]!=0){  // 不为0,即存入了和数据
                    //循环该桶即第k个桶(即第k个一维数组)
                    for (int l=0;l< bucketElementCounts[k];l++){
                        // 取出元素放入到arr
                        arr[index++]=bucket[k][l];
                    }
                }
                // 每一轮处理后,要把每个桶中存放数据的个数置零,bucketElementCounts[k]=0 !!!
                bucketElementCounts[k]=0;
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言中常用排序算法有多种,以下是其中几种常见的: 1. **冒泡排序(Bubble Sort)**:通过反复交换相邻元素使其逐渐有序的过程,时间复杂度一般为O(n^2)。 2. **选择排序(Selection Sort)**:每次从未排序部分找到最小值,放到已排序部分的末尾。时间复杂度始终为O(n^2)。 3. **插入排序(Insertion Sort)**:将待排序的元素逐个插入到已排序序列中的适当位置。对于接近有序的数组效率较高,时间复杂度平均为O(n^2),最好为O(n)。 4. **快速排序(Quick Sort)**:基于分治法,选取一个基准元素,将数组分为两部分,小于基准的放在左边,大于的放在右边,然后递归地对左右两部分进行排序。平均时间复杂度为O(n log n),最坏情况下为O(n^2)。 5. **归并排序(Merge Sort)**:也是分治策略,将数组分成两半,分别排序再合并。时间复杂度始终为O(n log n)。 6. **堆排序(Heap Sort)**:利用堆这种数据结构进行排序,构建最大堆或最小堆,然后将堆顶元素与最后一个元素交换,调整堆,重复此过程。时间复杂度为O(n log n)。 7. **计数排序(Counting Sort)**:适用于整数范围不大的情况,统计每个元素的频数,然后直接计算出每个元素在新数组中的位置。时间复杂度为O(n + k),k是数据范围。 8. **基数排序(Radix Sort)**:按照位数从低到高对数字进行排序,适用于非负整数。时间复杂度取决于数字的最大位数,可以达到线性时间。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值