常用的排序算法

我们常用的排序算法有插入排序、选择排序、交换排序和归并排序四种算法,这四种算法各自有各自的优缺点,接下来我们就仔细来看看这几种算法。

1.直接插入排序
 

常用的排序算法_第1张图片

 代码展示:

public  void insertSort(int[] array){
        for (int i = 1; i < array.length; i++) {
            int tmp = array[i];
            int j = i-1;
            for (; j >= 0 ; j--) {
               if(array[j] > tmp){
                   array[j+1] = array[j];
               }else {
                   //j回退的时候,遇到了比tmp小的元素就结束这次的比较
                   //array[j+1] = tmp;因为最外面也有这一步,因此这里省略
                   break;
               }
            }
            //有下面这一步是因为j回退到了小于0的位置的地方,上面的for循环进不去
            array[j+1] = tmp;
        }
    }
时间复杂度:O(N^2)即逆序的时候 
最好的情况是O(N):对于直接插入排序来说,最好的情况就是数据有序 
根据这个结论,推导出另一个结论,对于直接插入排序来说,数据越有序,排序速度越快 
空间复杂度:O(1) 
稳定性:稳定的 ,一个稳定的排序可以实现为不稳定的排序 ,但是一个本身就不稳定的排序,是不可能变成稳定的排序的。

2.希尔排序

        希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止。

常用的排序算法_第2张图片

 public  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) {
              if(array[j] > tmp){
                  array[j+gap] = array[j];
              }else {
                  break;
              }
            }
            array[j+gap] = tmp;
        }
    }
    //分组
    public  void shellSort(int[] array){
        int gap = array.length;
        while (gap > 1){
            shell(array,gap);
            gap /= 2;
        }
        shell(array,1);//保证最后是一组
    }

 其实就是一个直接插入排序

array:待排序序列      gap:组数  

时间复杂度是:O(N^1.3 - N^1.5)

空间复杂度:O(1)

稳定性:不稳定 看是否稳定主要是看在比较的过程中,如果发生了跳跃式的交换,那么就是不稳定的排序

3.选择排序

选择排序就是一组数据中,相邻两个数,互相比较,大的放到后面,小的放到前面。

我们先写一个交换函数:

 public void swap(int[] array,int i,int j){
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }

  public void selectSort1(int[] array){
        for (int i = 0; i < array.length; i++) {
            int j = i+1;
            for (; j < array.length; j++) {
                if(array[j] < array[i]){
                  swap( array,i,j);
                }
            }
        }
    }

但是这个代码我们还有优化得地方,我们不必要每个后面小得值都和前面交换,可以在后面找到最小值得下标,然后和前面的进行交换

 public void selectSort(int[] array){
        for (int i = 0; i < array.length; i++) {
            int minindex = i;
            for (int j = i + 1; j < array.length; j++) {
                //找到最小值下标
                if(array[j] < array[minindex]){
                    minindex = j;
                }
            }
            swap(array,i,minindex);
        }
    }
稳定性:不稳定

时间复杂度:O(n^2)

空间复杂度:O(1)

4.堆排序

常用的排序算法_第3张图片

public void heapSort(int[] array){
     createHeap(array);
     int end = array.length-1;
     while (end > 0){
         swap(array,0,end);
         shiftDown(array,0,end);
         end--;
     }
    }
    public void createHeap(int[] array){
        for (int parent = (array.length-1-1)/2; parent >= 0 ; parent--) {
          shiftDown(array,parent,array.length);
        }
    }
   public void shiftDown(int[] array,int parent,int len){
       int child = 2*parent+1;
       while (child < len){
           //这里条件child + 1 < len,是因为怕最后一棵树没有右孩子导致数组越界
           if(child+1 < len && array[child] < array[child+1]){
               //这里的child保证一定是孩子的最大值下标
               child++;
           }
           if(array[child] > array[parent]){
               swap(array,child,parent);
               //继续向下检测,防止有的父亲节点<孩子节点
               parent = child;
               child = 2 * parent + 1;
           }else {
               break;
           }
       }
   }

时间复杂度 = O(n * log n)
空间复杂度 = O(1)
稳定性:不稳定

 

5.冒泡排序

常用的排序算法_第4张图片

 优化前:

public void bubbleSort1(int[] array){
       //i表示趟数
       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]){
                   swap(array,j,j+1);
               }
           }
       }
   }

优化后:

 public void bubbleSort2(int[] array){
        //i表示趟数
        for (int i = 0; i < array.length-1; i++) {
            boolean flg = false;
            for (int j = 0; j < array.length-1-i; j++) {
                if(array[j] > array[j+1]){
                    swap(array,j,j+1);
                    flg = true;
                }
            }
            if(flg == false){
                break;
            }
        }
    }

6.快速排序

原理:(1) 从待排序区间选择一个数,作为基准值(pivot)

(2)Partition: 遍历整个待排序区间,将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的(可 以包含相等的)放到基准值的右边

(3)采用分治思想,对左右两个小区间按照同样的方式处理,直到小区间的长度 == 1,代表已经有序,或者小区间 的长度 == 0,代表没有数据

常用的排序算法_第5张图片          

public void quickSort(int[] array){

        quick(array,0,array.length-1);
    }
    //递归左边和右边
     public void quick(int[] array,int left,int right){
       //left和right相遇时,递归结束
        if(left >= right){
            return;
        }
        int pivot = partition(array,left,right);//找基准
        quick(array,left,pivot-1);
        quick(array,pivot+1,right);

     }
    //找基准
    private int partition(int[] array,int start,int end){
      int tmp = array[start];
       while (start < end){
           while (start < end && array[end] >= tmp){
               end--;
           }
           //end下标就遇到了 < tmp的值
           array[start] = array[end];
           while (start < end && array[start] <= tmp){
               start++;
           }
           //start下标就遇到了 > tmp的值
           array[end] = array[start];
       }
       array[start] = tmp;
       return start;
    }

时间复杂度:最好的情况(每次都可以均匀的分割待排序序列)O(N * log 2 n)

最坏的情况:数据有序或者逆序的情况下O(N^2)

空间复杂度: 最好的情况下:O(logn)

最坏的情况下:O(n) 成为单分支的一棵树

稳定性:不稳定的排序

6.1快排的优化

可是我们仔细思考一下上面的代码还能再优化吗?

常用的排序算法_第6张图片

public void quickSort(int[] array){

        quick(array,0,array.length-1);
    }
    //递归左边和右边
     public void quick(int[] array,int left,int right){
       //left和right相遇时,递归结束
        if(left >= right){
            return;
        }
         //1.找基准之前,我们找到中间大小的值-使用三数取中法
         int midValIndex = findMidValIndex(array,left,right);
         swap(array,midValIndex,left);        
         
         int pivot = partition(array,left,right);//找基准
         quick(array,left,pivot-1);
         quick(array,pivot+1,right);

     }
    //找基准
    private int partition(int[] array,int start,int end){
      int tmp = array[start];
       while (start < end){
           while (start < end && array[end] >= tmp){
               end--;
           }
           //end下标就遇到了 < tmp的值
           array[start] = array[end];
           while (start < end && array[start] <= tmp){
               start++;
           }
           //start下标就遇到了 > tmp的值
           array[end] = array[start];
       }
       array[start] = tmp;
       return start;
    }

6.2快排的再优化      

 继承上面的优化,我们排序一组数据,如果我们找到一个基准,在这组数据中,有和这个基准相同的数据,我们就把这个数据放到这个基准的旁边,直接递归剩下的数据就行。

       或者我们的待排序数据小于一个给定的数据总数时,我们可以采取直接插入排序就行。

   public void quickSort(int[] array){

            quick(array,0,array.length-1);
    }
   public  void insertSort3(int[] array,int start,int end){
        for (int i = 1; i <= end; i++) {
            int tmp = array[i];
            int j = i-1;
            for (; j >= start ; j--) {
                if(array[j] > tmp){
                    array[j+1] = array[j];
                }else {
                    //j回退的时候,遇到了比tmp小的元素就结束这次的比较
                    //array[j+1] = tmp;因为最外面也有这一步,因此这里省略
                    break;
                }
            }
            //有下面这一步是因为j回退到了小于0的位置的地方,上面的for循环进不去
            array[j+1] = tmp;
        }
    }
    //递归左边和右边
     public void quick(int[] array,int left,int right){
       //left和right相遇时,递归结束
        if(left >= right){
            return;
        }
       //如果区间内的数据,在排序的过程当中,小于某个范围了,可以直接使用插入排序
        if(right - left+1 <= 140){
            //使用直接插入排序
            insertSort3(array,left,right);

        }
         //1.找基准之前,我们找到中间大小的值-使用三数取中法
         int midValIndex = findMidValIndex(array,left,right);
         swap(array,midValIndex,left);        
         
         int pivot = partition(array,left,right);//找基准
         quick(array,left,pivot-1);
         quick(array,pivot+1,right);

     }
    //找基准
    private int partition(int[] array,int start,int end){
      int tmp = array[start];
       while (start < end){
           while (start < end && array[end] >= tmp){
               end--;
           }
           //end下标就遇到了 < tmp的值
           array[start] = array[end];
           while (start < end && array[start] <= tmp){
               start++;
           }
           //start下标就遇到了 > tmp的值
           array[end] = array[start];
       }
       array[start] = tmp;
       return start;
    }

6.3快排的非递归

常用的排序算法_第7张图片

代码实现

 public void quickSort(int[] array){
        Stack stack = new Stack<>();
        int left = 0;
        int right = array.length-1;
        int pivot = partition(array,left,right);
        //左边有两个元素
        if(pivot > left+1){
            stack.push(left);
            stack.push(pivot-1);
        }
        //右边有两个元素
        if(pivot < right-1){
            stack.push(pivot+1);
            stack.push(right);
        }
        while (!stack.isEmpty()){

             right = stack.pop();
             left = stack.pop();
             pivot = partition(array,left,right);
            //左边有两个元素
            if(pivot > left+1){
                stack.push(left);
                stack.push(pivot-1);
            }
            //右边有两个元素
            if(pivot < right-1){
                stack.push(pivot+1);
                stack.push(right);
            }
        }
    }

6.4归并排序

在进行归并排序之前,我们先来看一个合并两个有序数组为一个有序数组

6.4.1合并数组

常用的排序算法_第8张图片

 代码展示:

public int[] mergeArray(int[] array1, int[] array2) {
        int i = 0;//表示tmp数组的下标i
        int s1 = 0;
        int e1 = array1.length - 1;
        int s2 = 0;
        int e2 = array2.length - 1;
        int[] tmp = new int[array1.length + array2.length];

        while (s1 <= e1 && s2 <= e2) {
            if (array1[s1] <= array2[s2]) {
                tmp[i++] = array1[s1++];
                //i++;
                //s1++;
            } else {
                tmp[i++] = array2[s2++];
                //i++;
                //s2++;
            }
        }
        while (s1 <= e1) {
            tmp[i++] = array1[s1++];
        }
        while (s2 <= e2) {
            tmp[i++] = array2[s2++];
        }
        return tmp;
    }

6.4.2递归实现归并排序

常用的排序算法_第9张图片

 

代码展示:

 public void mergeSort(int[] array){

        mergeSortInternal(array,0,array.length-1);
    }
    private void mergeSortInternal(int[] array,int low,int high){
          if(low >= high){
              return;
          }
        int mid = low + ((high - low) >>> 1);
        //左边
        mergeSortInternal(array,low,mid);
        //右边
        mergeSortInternal(array,mid+1,high);
        //合并
        merge(array,low,mid,high);
    }
    private void merge(int[] array,int low,int mid,int high){
        int[] tmp = new int[high-low+1];
        int i = 0;//表示tmp数组的下标i
        int s1 = low;
        int e1 = mid;
        int s2 = mid+1;
        int e2 = high;
        while (s1 <= e1 && s2 <= e2) {
            if (array[s1] <= array[s2]) {
                tmp[i++] = array[s1++];
                //i++;
                //s1++;
            } else {
                tmp[i++] = array[s2++];
                //i++;
                //s2++;
            }
        }
        while (s1 <= e1) {
            tmp[i++] = array[s1++];
        }
        while (s2 <= e2) {
            tmp[i++] = array[s2++];
        }
        //拷贝tmp数组的元素,放入原来的数组array中
        for (int j = 0; j < tmp.length; j++) {
            array[j+low] = tmp[j];
        }
    }

结论:归并排序 时间复杂度:O(N*logN) 时间复杂度是O(N*logN)的一共有快排、归并和堆排,三个里面快排最快,堆排空间复杂度最低,归并最稳定 空间复杂度:O(N) 稳定性:稳定的排序 如果array[s1] <= array[s2] 不去等号,那么就是不稳定的排序 目前为止,我们学过的排序只有冒泡、插入、归并是稳定的。

 6.4.3非递归实现归并排序

常用的排序算法_第10张图片

 public void mergeSort2(int[] array){
        int nums = 1;//每组的数据个数

        while (nums < array.length){
           //数组每次都要从i进行遍历,确定要归并的区间
            for (int i = 0; i < array.length; i += nums*2) {
              int left = i;
              int mid = left+nums-1;
                //防止越界
              if(mid >= array.length){
                  mid = array.length-1;
              }
              int right = mid + nums;
              //防止越界
              if(right >= array.length){
                  right = array.length-1;
              }
              //下标确定之后,进行合并
                merge(array,left,mid,right);
            }
             nums *= 2;
        }
    }

7.计数排序

常用的排序算法_第11张图片 代码展示:

 public void countingSort(int[] array){

        //假设开始最大和最小
        int maxVal = array[0];
        int minVal = array[0];
        for(int i = 1;i < array.length;i++){
            if(array[i] < minVal){
                minVal = array[i];
            }
            if(array[i] > maxVal){
                maxVal = array[i];
            }
        }
        //说明已经找到最小值和最大值
        int[] count = new int[maxVal-minVal+1];//计数数组默认都是0
        //统计array数组当中,每个数据出现的次数
        for(int i = 0;i < array.length;i++ ){
            int index = array[i];
            //为了空间得合理化使用,这里需要index-minVal,防止945-900
            count[index-minVal]++;
        }
        //说明在计数数组当中,已经把array数组中,每个数据出现的次数都已经算好了
        //接下来,只需要遍历计数数组,把数据写回array
        int indexArray = 0;
        for (int i = 0; i < count.length; i++) {
            while (count[i] > 0){
              //这里一定要加minVal,因为不一定就是i出现了count[i]
                array[indexArray] =i+minVal;
                count[i]--;//拷贝一个之后,次数也就减少一个
                indexArray++;//下标得向后移动
            }
        }
    }

 计数排序,一般适用于 有n个数,数据范围在0-n之间的数

时间复杂度:O(N)

空间复杂度:O(M)M代表当前数据的范围,900-999,范围就是100

稳定性:当前代码不稳定,

你可能感兴趣的:(数据结构,排序算法,算法,数据结构)