关于排序的稳定性:
1.如果在排序的过程中,没有发生跳跃式的交换。那么是稳定的排序
2.一个稳定的排序,是可以被实现为不稳定的排序的,但是一个不稳定的排序是不可能变为稳定的排序的

一、直接插入排序

1.1 原理

将整个排序的区间,分为有序区间(刚开始一般只有0号下标元素,因为一个元素本身就是有序的)和无序区间,每次选择无序区间的第一个元素,插入到有序区间的合适位置,直到整个序列都变有序。

1.2 实现
public static void insertSort(int[] array){
        for (int i = 1; i < array.length; i++) {//循环无序元素
            int tmp = array[i];//临时变量来保存待插入元素
            int j = 0;
            for (j = i-1; j >= 0 ; j--) {//循环有序元素,每一躺j始终在i的前面一个位置
                if (array[j] > tmp){
                    array[j + 1] = array[j];
                }else {
                    break;//前面的已经有序了
                }
            }
            array[j+1] = tmp;//插入数据
        }
    }
1.3 性能分析

稳定性:稳定的
时间复杂度:最好情况O(n),最坏情况O(n^2),所以越有序的序列用直插排序越快。
空间复杂度: O(1)只用了一个数组

二、选择排序

2.1 原理

选择序列中最小的元素与当前元素进行比较并进行交换

2.2 实现
public static void selectSort(int[] array){
        for (int i = 0; i < array.length; i++) {//从一个元素开始
            for (int j = i + 1; j < array.length; j++) {//遍历该元素后面的每一个元素
                if (array[i] > array[j]){//如果小于该元素则交换
                    int tmp = array[j];
                    array[j] = array[i];
                    array[i] = tmp;
                }
            }
        }
    }
2.3 性能分析

①稳定性:不稳定的
②时间复杂度:O(n^2)
③空间复杂度: O(1)

三、堆排序

3.1 原理

建立一个堆(升序大堆,降序小堆)
将堆最后一个元素与堆顶的元素交换位置,再从堆顶向下调整,第二次将堆倒数第二的元素与堆顶元素交换位置,再向下调整…直到没有元素为止,排序就完成了

3.2 实现
//1.向下调整
public static  void  adjustDown(int[] array,int index,int size){
        int left = index*2+1;
        while(left<size){
        	int max = left;
        	int right = index*2+2;
        	//找左右子节点最大值
        	if(right<size){
        		if(array[right]>array[left]){
        			max = right;
        		}
        	}
        	//如果index是最大的那说明调整结束
        	if(array[index]>=array[max]){
        		break;
        	}
        	//交换 index 和 max下标的值
        	swap(array,index,max);
        	
        	//开启下一轮循环
        	index = max;
        	left= index*2+1;
        }
    }
    //2.使用向下调整将待排序数组建立一个堆
    //从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆
    public static void createHeap(int[] array){
        for (int i = (array.length -2)/2; i >= 0 ; i--) {
            	//根据child反推parent
            adjustDown(array,i,array.length);
        }
    }
    //3.排序
    public static void heapSort(int[] array){
        //建堆的时间复杂度是 n*log2n
        //堆的一次调整的时间复杂度是log2n
        creatHeap(array);
        int end = array.length - 1;
        while (end > 0){
            int tmp = array[end];
            array[end] = array[0];
            array[0] = tmp;
            adjustDown(array,0,end);//长度一定是end因为end之后的已经调整好了
            end--;//必须在adjustDown下边
        }
    }
3.3 性能分析

**①稳定性:**不稳定
②时间复杂度: O(nlog2n)
建立大根堆的时间复杂度是O(nlog2n)
一次调整的时间复杂度为O(log2n)
③空间复杂度: O(1)

四、冒泡排序

4.1 原理

两个循环,一个躺数,一个比较次数,紧挨着的两个数字进行比较,并进行交换,将最大的数字“沉”下去,较小的数字“冒”上来,图解如下:
在这里插入图片描述
可见一共进行了数组长度-1躺,第 i 趟进行了数组长度 -1 - i 次比较

4.2 实现
public static void bubbleSort(int[] array){
        boolean flg = false;
        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];
                    array[j] = array[j+1];
                    array[j+1] = tmp;
                    flg = true;//对排序的优化,当该躺已经有序时,标志位为false就不用进行比较
                }
            }
            if (!flg){
                break;
            }
        }
    }
4.3 性能分析

①稳定性:稳定的
②时间复杂度:O(n^2)
③空间复杂度: O(1)

五、快速排序

5.1 原理

1.从待排序区间选择一个数,作为基准值(pivot),将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的(可以包含相等的)放到基准值的右边
2.采用分治思想,对左右两个小区间按照同样的方式处理,直到小区间长度等于1,代表已经有序,或者小区间的长度等于0,代表没有数据了。
下图为一趟快速排序的操作过程:
在这里插入图片描述
到此,数据6有序了,然后开始递归,有新的low和high进入下一个分段,最终每个值都有序了。

5.2 实现

1、递归实现

public static void quickSort(int[] array){
       quick(array,0,array.length-1);
    }
//快排主函数
public static void quick(int[] array,int low,int high){//O(log2n)
        //终止条件
       if(low < high){
           //1.写函数将待排序序列分为两部分
           int pivot = partion(array,low,high);
           //2.开始递归(左或右都可以)
           quick(array,low,pivot - 1);
           quick(array,pivot + 1,high);
        }
    }


//寻找基准
public static int partion(int[] array,int start,int end){//O(n)
        int tmp = array[start];
        while (start < end){
        	//1.在后面找到比tmp小的
			while(start<end && array[end]>=tmp){
				end--;
			}
			if(start >= end){
				break;
			}else{
				array[start] = array[end];
			}	
			//2.在前面找到比tmp大的
			while(start<end && array[start]<=tmp){
				start++;
			}
			if(start >= end){
				break;
			}else{	
				array[end] = array[start];
			}		
        }
        array[start]  = tmp;
        return start;
    }

2、非递归实现

public static void quickSortNor(int[] array){
        Stack<Integer> stack = new Stack<>();
        stack.push(array.length - 1);
        stack.push(0);

        while (!stack.isEmpty()){
            int left = stack.pop();
            int right = stack.pop();
            if(left >= right){
                continue;
            }

            int pivot = partion(array,left,right);//还是要调用partion
            stack.push(right);
            stack.push(pivot + 1);

            stack.push(pivot -1);
            stack.push(left);
        }
    }
5.3 性能分析

①稳定性:不稳定
②时间复杂度:O(nlog2n)
注:看着和堆排一样快,但是比堆排序快一些因为前面系数小
③空间复杂度: O(log2n)
注: 就是二叉树的高度

六、归并排序

6.1 原理

给定一组待排序列, 首先,将其划分为一个一个有序的,然后进行二路递归。整体来说,递:分成单个有序。归:合并为整体有序序列。也是分治的思想
在这里插入图片描述

6.2 实现

1、递归实现

//入口函数
  public static void mergeSort1(int[] array){
       mergeSortInternal(array,0,array.length - 1);
    }
    
//分治函数
  public static void mergeSortInternal(int[] array,int low,int high){
        if(low >= high){ //low一直在high的左边
              		   //当low小于high的时候递归,其他情况停止
            return;
        }
        int mid = (low + high) / 2;
        mergeSortInternal(array,low,mid);
        mergeSortInternal(array,mid+1,high);
        //进行合并
        merge1(array,low,mid,high);
    }

//合并函数
public static  void merge1(int[] array,int low,int mid,int high){
        int s1 = low;//第一个归并段的开始
        int s2 = mid + 1;//第二个归并段的开始
      /*思路:每次比较s1和s2下标的元素大小,把比较小拿到新的数组里,小的归并段下标(s1或s2)
      往后走一个元素,重复该过程。
      * 直到一个归并段走完了,把另一个归并段的剩余数据放到新数组后面。
      * 再把新数组的数据方放回原数组。
      * */
        int[] tmpArr = new int[high-low+1];//工具数组的长度
        int i = 0;//tmpArr的数组下标
        //当两个归并段都有数据的时候
        while (s1 <= mid && s2 <= high){//说明段里都有数据才可以比较
            //如果是小于,就不稳定了
            if (array[s1] <= array[s2]){
                tmpArr[i++] = array[s1++];
            }else{
                tmpArr[i++] = array[s2++];
            }
        }
        //退出循环的两种情况
        while (s1 <= mid ){//有可能第一个归并段中还余了数据
            //把第一个段中的所有数据拷贝到工具数组中
            tmpArr[i++] = array[s1++];
        }
        while (s2 <= high){//第二个归并段中余了数据
            tmpArr[i++] =   array[s2++];
        }
        //tmpArr存放的是最终有序的数据
        //这一步将tmpArr的有效有序数据放回到array里面
        for (int j = 0; j < tmpArr.length; j++) {
            array[low + j] = tmpArr[j];//要看上一步递归时,传进来的下标low
        }
    }
    

2、非递归实现

  //思路:先划分单个有序,然后两个有序,然后四个有序....最后整体有序(个数大于等于长度)
    public static void mergeSort2(int[] array){
        for (int i = 1; i < array.length; i *= 2) {
            merge(array,i);
        }
    }

     public static void merge(int[] array,int gap){//他的工作就是谁小拉谁下来
        int[] tmpArr = new int[array.length];
        int k = 0; //下标

         int s1 = 0;
         int e1 = s1 + gap - 1;
         int s2 = e1 + 1;
         int e2 = s2 + gap - 1 < array.length ? s2 + gap - 1 : array.length - 1;
         //如果超了那么等于最后一个下标

         //此时两个归并段是都有数据的
         while (s2 < array.length){
             //且在比较的时候两个归并段是有数据的
             while (s1 <= e1 && s2 <= e2){
                 if (array[s1] <= array[s2]){
                     tmpArr[k++] = array[s1++];}
                 else{
                     tmpArr[k++] = array[s2++];
                 }
             }
             //说明退出循环是第二个归并段走完了
             while (s1 <= e1){
                 tmpArr[k++] = array[s1++];
             }
             //说明第一个归并段走完了
             while (s2 <= e2){
                 tmpArr[k++] = array[s2++];
             }
             s1 = e2 + 1;
             e1 = s1 + gap -1;
             s2 = e1 + 1;
             e2 = s2 + gap - 1 < array.length ? s2 + gap - 1 : array.length - 1;
         }
         //判断是不是还有一个归并段,且这个归并段一定是s1的那个段,直接小于e1可能会越界
         while (s1 <= array.length -1){
             tmpArr[k++] = array[s1++];
         }

         //拷贝到array
         for (int i = 0; i < tmpArr.length; i++) {
             array[i] = tmpArr[i];
         }
    }
6.3 性能分析

①稳定性:稳定的
②时间复杂度:n*log2n
③空间复杂度: O(n)

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐